Apollo source code analysis system, CyberRT

What is CyberRT

Insert image description here
The program entry for cyber is in cyber.h, which involves logs, nodes, components, task scheduling, and timers.

#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_

nodenode

#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&);
 }

In Apollo communication, referring to the node concept of ros, any module requires node nodes in communication. Different communication modes can be implemented through node nodes, such as common pub, sub, ser, and client. There are some communication-related member variables in the node 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);
}

The reader obtains the corresponding data information by subscribing to the channel, and triggers the callback function registered when subscribing after receiving the information. Reader and Channel are related and managed by ChannelManager. As for network communication, it is a node-free directed graph DAG. This part is similar to the implementation concept of ros.
Insert image description here

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());
}

Summarize the process of the following components.

  1. Create a node node (one component can only have one node node, and then the user can use node_ to create a reader or writer in init).
  2. Call the user-defined initialization function Init() (Init method of subclass)
  3. Create a reader and create several readers by subscribing to several messages.
  4. Create a callback function, which actually executes the user-defined algorithm Proc() function
  5. Create a data accessor. The purpose of the data accessor is to receive data (fusion of data from multiple channels) and wake up the corresponding coroutine to perform tasks.
  6. Create a coroutine task binding callback function, and bind the data accessor to the corresponding coroutine task to wake up the corresponding task.

TimerComponent

scheduler

mainboard

Guess you like

Origin blog.csdn.net/neuzhangno/article/details/132204997