0. 前置知识
这部分内容详见第三章:Component组件认知与实践https://apollo.baidu.com/community/article/1103
0.1 什么是 Component
Apollo 的 Cyber RT 框架是基于组件(component)概念来构建的。每个组件都是 Cyber RT 框架的一个特定的算法模块, 处理一组输入并产生其输出数椐,配合Component对应的DAG文件,Cyber RT可实现对该模块的动态加载。
0.2 Component 的类型
Component分为2类:一类是消息驱动的Component(即消息到来时,才会调用proc()),第二类是定时调用的TimerComponent。定时调度模块没有绑定消息收发,需要用户自己创建reader来读取消息,如果需要读取多个消息,可以创建多个reader。
Component提供消息融合机制,最多可以支持 4 路消息融合,当 从多个 Channel 读取数据的时候,以第一个 Channel 为主 Channel。当主 Channel 有消息到达,Cyber RT会调用 Component的Proc()进行一次数据处理。
TimerComponent不提供消息融合,与Component不同的是TimeComponent的 Proc()函数不是基于主channel触发执行,而是由系统定时调用,开发者可以在配置文件中确定调用的时间间隔。
0.3 Component 的创建及工作流程
1、包含头文件;
2、定义一个类,并继承Component或者time Component;根据Component功能需要,选择继承Component或者继承TimeComponent。
3、重写Init()和Proc()函数;Init()函数在 Component 被加载的时候执行,用来对Component进行初始化,如Node创建,Node Reader创建,Node Writer创建等等;Proc()函数是实现该Component功能的核心函数,其中实现了该Component的核心逻辑功能。
4、在Cyber RT中注册该Component,只有在Cyber RT中注册了该Component,Cyber RT才能对其进行动态的加载,否则,cyber RT动态加载时报错。
见2.1头文件部分具体实现
0.4 Component 如何被加载
在 Cyber RT中,所有的 Comopnent 都会被编译成独立的.so文件,Cyber RT 会根据开发者提供的配置文件,按需加载对应的 Component。所以,开发者需要为.so文件编写好配置文.dag文件和.launch文件,以供 Cyber RT正确的加载执行Component。
Cyber RT提供两种加载启动Component的方式,分别是使用cyber_launch工具启动
component对应的launch文件,和使用mainboard启动component对应的dag文件。
cyber_launch工具可以启动dag文件和二进制文件,而mainboard执行启动dag文件。
0.5 Component 的优点
可以通过配置 launch 文件加载到不同进程中,可以弹性部署。
可以通过配置 DAG 文件来修改其中的参数配置,调度策略,Channel 名称。
可以接收多个种类的消息,并有多种消息融合策略。
接口简单,并且可以被 Cyber 框架动态地加载,更加灵活易用。
要创建并启动一个算法组件,需要通过以下 4 个步骤:
初始化组件的目录结构
实现组件类
设置配置文件
启动组件
1. 初始化组件的目录结构
以example-component 为例.(以下案例请先暂时忽略timer部分)
├── BUILD
├── cyberfile.xml
├── example-components.BUILD
├── example.dag
├── example.launch
├── proto
│ ├── BUILD
│ └── examples.proto
└── src
├── BUILD
├── common_component_example.cc
├── common_component_example.h
├── timer_common_component_example.cc
└── timer_common_component_example.h
C++头文件: common_component_example.h
C++源文件: common_component_example.cc
Bazel 构建文件: BUILD
DAG 文件: examples.dag
Launch 文件: examples.launch
2. 实现组件类
2.1 头文件
实现common_component_example.h有以下步骤:包含头文件
基于模板类 Component 派生出组件类CommonComponentSample
在派生类中定义自己的 Init 和 Proc 函数。Proc 需要指定输入数椐类型。
使用CYBER_REGISTER_COMPONENT宏定义把组件类注册成全局可用。
#pragma once
#include <memory>
#include "cyber/component/component.h"
#include "example_components/proto/examples.pb.h"
// CommonComponentSample类不能被继承
class CommonComponentSample : public apollo::cyber::Component<example::proto::Driver, example::proto::Driver> {
//有几个数据就有几个example::proto::Driver
public:
bool Init() override;
bool Proc(const std::shared_ptr<example::proto::Driver>& msg0,
const std::shared_ptr<example::proto::Driver>& msg1) override;
};
CYBER_REGISTER_COMPONENT(CommonComponentSample)
模板类Component的定义在cyber/component/component.h中.
template <typename M0 = NullType, typename M1 = NullType,
typename M2 = NullType, typename M3 = NullType>
class Component : public ComponentBase {
public:
Component() {}
~Component() override {}
/**
* @brief init the component by protobuf object.
*
* @param config which is defined in 'cyber/proto/component_conf.proto'
*
* @return returns true if successful, otherwise returns false
*/
bool Initialize(const ComponentConfig& config) override;
bool Process(const std::shared_ptr<M0>& msg0, const std::shared_ptr<M1>& msg1,
const std::shared_ptr<M2>& msg2,
const std::shared_ptr<M3>& msg3);
private:
/**
* @brief The process logical of yours.
*
* @param msg0 the first channel message.
* @param msg1 the second channel message.
* @param msg2 the third channel message.
* @param msg3 the fourth channel message.
*
* @return returns true if successful, otherwise returns false
*/
virtual bool Proc(const std::shared_ptr<M0>& msg0,
const std::shared_ptr<M1>& msg1,
const std::shared_ptr<M2>& msg2,
const std::shared_ptr<M3>& msg3) = 0;
};
由代码可见,Component类最多接受4个模板参数,每个模板参数均表示一种输入的消息类型,这些消息在Proc函数中被周期性地接收并处理.
2.2 源文件
对于源文件 common_component_example.cc, Init 和 Proc 这两个函数需要实现。
#include "example_components/src/common_component_example.h"
bool CommonComponentSample::Init() {
AINFO << "Commontest component init";
return true;
}
bool CommonComponentSample::Proc(const std::shared_ptr<example::proto::Driver>& msg0,
const std::shared_ptr<example::proto::Driver>& msg1) {
AINFO << "Start common component Proc [" << msg0->msg_id() << "] ["
<< msg1->msg_id() << "]";
return true;
}
2.3 创建 BUILD 文件
可见基于common_component_example_lib库最终生成了一个共享库文件libcommon_component_example.so,而该共享库通过Cyber RT调度程序mainboard动态加载运行
load("//tools:cpplint.bzl", "cpplint")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "libcomponent_examples.so",
linkshar