什么是CyberRT
关于cyber的程序入口在cyber.h中,涉及到日志,节点,组件,任务调度,定时器。
#ifndef CYBER_CYBER_H_
#define CYBER_CYBER_H_
#include <memory>
#include <string>
#include <utility>
#include "cyber/common/log.h"
#include "cyber/component/component.h"
#include "cyber/init.h"
#include "cyber/node/node.h"
#include "cyber/task/task.h"
#include "cyber/time/time.h"
#include "cyber/timer/timer.h"
namespace apollo {
namespace cyber {
std::unique_ptr<Node> CreateNode(const std::string& node_name,
const std::string& name_space = "");
} // namespace cyber
} // namespace apollo
#endif // CYBER_CYBER_H_
node节点
#ifndef CYBER_NODE_NODE_H_
#define CYBER_NODE_NODE_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "cyber/node/node_channel_impl.h"
#include "cyber/node/node_service_impl.h"
namespace apollo {
namespace cyber {
template <typename M0, typename M1, typename M2, typename M3>
class Component;
class TimerComponent;
/**
* @class Node
* @brief Node is the fundamental building block of Cyber RT.
* every module contains and communicates through the node.
* A module can have different types of communication by defining
* read/write and/or service/client in a node.
* @warning Duplicate name is not allowed in topo objects, such as node,
* reader/writer, service/clinet in the topo.
*/
class Node {
public:
template <typename M0, typename M1, typename M2, typename M3>
friend class Component;
friend class TimerComponent;
friend bool Init(const char*);
friend std::unique_ptr<Node> CreateNode(const std::string&,
const std::string&);
}
在Apollo通信中,参照ros的节点概念,任何模块在通信中都需要node节点的方式,可以通过node节点实现不同的通信模式,比方说常见的pub、sub,ser、client。在node节点中存在一些与通讯相关的成员变量。
private:
explicit Node(const std::string& node_name,
const std::string& name_space = "");
std::string node_name_;
std::string name_space_;
std::mutex readers_mutex_;
std::map<std::string, std::shared_ptr<ReaderBase>> readers_;
std::unique_ptr<NodeChannelImpl> node_channel_impl_ = nullptr;
std::unique_ptr<NodeServiceImpl> node_service_impl_ = nullptr;
Reader
template <typename MessageT>
class Reader : public ReaderBase {
public:
using BlockerPtr = std::unique_ptr<blocker::Blocker<MessageT>>;
using ReceiverPtr = std::shared_ptr<transport::Receiver<MessageT>>;
using ChangeConnection =
typename service_discovery::Manager::ChangeConnection;
using Iterator =
typename std::list<std::shared_ptr<MessageT>>::const_iterator;
/**
* Constructor a Reader object.
* @param role_attr is a protobuf message RoleAttributes, which includes the
* channel name and other info.
* @param reader_func is the callback function, when the message is received.
* @param pending_queue_size is the max depth of message cache queue.
* @warning the received messages is enqueue a queue,the queue's depth is
* pending_queue_size
*/
explicit Reader(const proto::RoleAttributes& role_attr,
const CallbackFunc<MessageT>& reader_func = nullptr,
uint32_t pending_queue_size = DEFAULT_PENDING_QUEUE_SIZE);
}
reader通过订阅channel来获取对应的数据信息,在收到信息后触发订阅时注册的回调函数。Reader 和 Channel 是相关联的,由 ChannelManager 进行管理。至于网络通信是去节点化的有向图 DAG,该部分与ros的实现理念相似。
Component组件
template <typename M0>
class Component<M0, NullType, NullType, NullType> : public ComponentBase {
public:
Component() {}
~Component() override {}
bool Initialize(const ComponentConfig& config) override;
bool Process(const std::shared_ptr<M0>& msg);
private:
virtual bool Proc(const std::shared_ptr<M0>& msg) = 0;
};
template <typename M0, typename M1>
bool Component<M0, M1, NullType, NullType>::Initialize(
const ComponentConfig& config) {
// 1. 创建Node
node_.reset(new Node(config.name()));
LoadConfigFiles(config);
// 2. 调用用户自定义初始化Init()
if (!Init()) {
AERROR << "Component Init() failed.";
return false;
}
bool is_reality_mode = GlobalData::Instance()-> ();
ReaderConfig reader_cfg;
reader_cfg.channel_name = config.readers(1).channel();
reader_cfg.qos_profile.CopyFrom(config.readers(1).qos_profile());
reader_cfg.pending_queue_size = config.readers(1).pending_queue_size();
// 3. 创建reader1
auto reader1 = node_->template CreateReader<M1>(reader_cfg);
...
// 4. 创建reader0
if (cyber_likely(is_reality_mode)) {
reader0 = node_->template CreateReader<M0>(reader_cfg);
} else {
...
}
readers_.push_back(std::move(reader0));
readers_.push_back(std::move(reader1));
auto sched = scheduler::Instance();
// 5. 创建回调,回调执行Proc()
std::weak_ptr<Component<M0, M1>> self =
std::dynamic_pointer_cast<Component<M0, M1>>(shared_from_this());
auto func = [self](const std::shared_ptr<M0>& msg0,
const std::shared_ptr<M1>& msg1) {
auto ptr = self.lock();
if (ptr) {
ptr->Process(msg0, msg1);
} else {
AERROR << "Component object has been destroyed.";
}
};
std::vector<data::VisitorConfig> config_list;
for (auto& reader : readers_) {
config_list.emplace_back(reader->ChannelId(), reader->PendingQueueSize());
}
// 6. 创建数据访问器
auto dv = std::make_shared<data::DataVisitor<M0, M1>>(config_list);
// 7. 创建协程,协程绑定回调func(执行proc)。数据访问器dv在收到订阅数据之后,唤醒绑定的协程执行任务,任务执行完成之后继续休眠。
croutine::RoutineFactory factory =
croutine::CreateRoutineFactory<M0, M1>(func, dv);
return sched->CreateTask(factory, node_->Name());
}
总结以下component的流程。
- 创建node节点(1个component只能有1个node节点,之后用户可以用node_在init中自己创建reader或writer)。
- 调用用户自定义的初始化函数Init()(子类的Init方法)
- 创建reader,订阅几个消息就创建几个reader。
- 创建回调函数,实际上是执行用户定义算法Proc()函数
- 创建数据访问器,数据访问器的用途为接收数据(融合多个通道的数据),唤醒对应的协程执行任务。
- 创建协程任务绑定回调函数,并且绑定数据访问器到对应的协程任务,用于唤醒对应的任务。