
# Runtime Interface


## 相关链接

代码文件：
- {{ '[aimrt_core.h]({}/src/runtime/core/aimrt_core.h)'.format(code_site_root_path_url) }}

参考示例：
- {{ '[helloworld]({}/src/examples/cpp/helloworld)'.format(code_site_root_path_url) }}
  - {{ '[helloworld_app_registration_mode]({}/src/examples/cpp/helloworld/app/helloworld_app_registration_mode)'.format(code_site_root_path_url) }}
  - {{ '[helloworld_app]({}/src/examples/cpp/helloworld/app/helloworld_app)'.format(code_site_root_path_url) }}
  - {{ '[normal_publisher_app]({}/src/examples/cpp/pb_chn/app/normal_publisher_app)'.format(code_site_root_path_url) }}
  - {{ '[normal_subscriber_app]({}/src/examples/cpp/pb_chn/app/normal_subscriber_app)'.format(code_site_root_path_url) }}


## 简介

如果说逻辑开发阶段的 C++ 接口主要是让用户开发具体的业务逻辑，那么本文档所介绍的**运行时接口**则是让用户决定如何部署、集成、运行这些业务逻辑。

AimRT 提供了两种部署集成方式：
- **App 模式**：开发者在自己的 Main 函数中注册/创建各个模块，编译时直接将业务逻辑编译进主程序；
- **Pkg 模式**：使用 AimRT 提供的**aimrt_main**可执行程序，在运行时根据配置文件加载动态库形式的`Pkg`，导入其中的`Module`；


两者的优劣势和适用场景请参考[AimRT 中的基本概念](../concepts/concepts.md)文档里的说明。


无论采用哪种方式都不影响业务逻辑，且两种方式可以共存，也可以比较简单的进行切换，实际采用哪种方式需要根据具体场景进行判断。


关于`aimrt::CoreRef`句柄的相关信息，请参考[CoreRef](./core_ref.md)文档。

## App 模式

开发者直接引用 CMake Target：**aimrt::runtime::core**，然后即可使用{{ '[core/aimrt_core.h]({}/src/runtime/core/aimrt_core.h)'.format(code_site_root_path_url) }}文件中的`aimrt::runtime::core::AimRTCore`类，在 App 模式下需要使用的核心接口如下：

```cpp
namespace aimrt::runtime::core {

class AimRTCore {
 public:
  struct Options {
    std::string cfg_file_path;
  };

 public:
  void Initialize(const Options& options);

  void Start();
  std::future<void> AsyncStart();

  void Shutdown();

  // ...

  module::ModuleManager& GetModuleManager();

  // ...
};

}  // namespace aimrt::runtime::core
```

接口使用说明如下：
- `void Initialize(const Options& options)`：用于初始化框架。
  - 接收一个`AimRTCore::Options`作为初始化参数。其中最重要的项是`cfg_file_path`，用于设置配置文件路径。
  - 如果初始化失败，会抛出一个异常。
- `void Start()`：启动框架。
  - 如果启动失败，会抛出一个异常。
  - 必须在 Initialize 方法之后调用，否则行为未定义。
  - 如果启动成功，会阻塞当前线程，并将当前线程作为本`AimRTCore`实例的主线程。
- `std::future<void> AsyncStart()`：异步启动框架。
  - 如果启动失败，会抛出一个异常。
  - 必须在 Initialize 方法之后调用，否则行为未定义。
  - 如果启动成功，会返回一个`std::future<void>`句柄，在外部可以调用该句柄的`wait`方法阻塞等待结束。
  - 该方法会在内部新启动一个线程作为本`AimRTCore`实例的主线程。
- `void Shutdown()`：停止框架。
  - 可以在任意线程、任意阶段调用此方法，也可以调用任意次数。
  - 调用此方法后，`Start`方法将在执行完主线程中的所有任务后，退出阻塞。
  - 需要注意，有时候业务会阻塞住主线程中的任务，导致`Start`方法无法退出阻塞、优雅结束整个框架，此时需要在外部强制 kill。


开发者可以在自己的 Main 函数中创建一个`AimRTCore`实例，依次调用其`Initialize`、`Start`/`AsyncStart`方法，并可以自己捕获`Ctrl-C`信号来调用`Shutdown`方法，以实现`AimRTCore`实例的优雅退出。

`AimRTCore`类型的`GetModuleManager`方法可以返回一个`ModuleManager`句柄，可以用来注册或创建模块，App 模式下需要使用其提供的`RegisterModule`接口或`CreateModule`接口：
```cpp
namespace aimrt::runtime::core::module {

class ModuleManager {
 public:
  void RegisterModule(const aimrt_module_base_t* module);

  const aimrt_core_base_t* CreateModule(std::string_view module_name);
};

}  // namespace aimrt::runtime::core::module
```

`RegisterModule`和`CreateModule`代表了 App 模式下编写逻辑的两种方式：**注册模块**方式与**创建模块**方式，前者仍然需要编写一个继承于`ModuleBase`类的业务模块类，后者则更加自由。

### 注册模块

通过`RegisterModule`可以直接注册一个标准模块。开发者需要先实现一个继承于`ModuleBase`基类的`Module`，然后在`AimRTCore`实例调用`Initialize`方法之前注册该`Module`实例，在此方式下仍然有一个比较清晰的`Module`边界。


关于`ModuleBase`基类的相关信息，请参考[ModuleBase](./module_base.md)文档。


以下是一个简单的例子，开发者需要编写的`main.cc`文件如下：
```cpp
#include <csignal>

#include "core/aimrt_core.h"
#include "aimrt_module_cpp_interface/module_base.h"

AimRTCore* global_core_ptr_ = nullptr;

void SignalHandler(int sig) {
  if (global_core_ptr_ && (sig == SIGINT || sig == SIGTERM)) {
    global_core_ptr_->Shutdown();
    return;
  }
  raise(sig);
};

class HelloWorldModule : public aimrt::ModuleBase {
 public:
  HelloWorldModule() = default;
  ~HelloWorldModule() override = default;

  ModuleInfo Info() const override {
    return ModuleInfo{.name = "HelloWorldModule"};
  }

  bool Initialize(aimrt::CoreRef core) override { return true; }
  bool Start() override { return true; }
  void Shutdown() override {}
};

int32_t main(int32_t argc, char** argv) {
  signal(SIGINT, SignalHandler);
  signal(SIGTERM, SignalHandler);

  // Create AimRTCore ins
  AimRTCore core;
  global_core_ptr_ = &core;

  // Register module
  HelloWorldModule helloworld_module;
  core.GetModuleManager().RegisterModule(helloworld_module.NativeHandle());

  // Initialize AimRTCore ins
  AimRTCore::Options options;
  options.cfg_file_path = "path/to/cfg/xxx_cfg.yaml";
  core.Initialize(options);

  // Start AimRTCore ins, will block here
  core.Start();

  // Shutdown AimRTCore ins
  core.Shutdown();

  return 0;
}
```

编译上面示例的`main.cc`，直接启动编译后的可执行文件即可运行进程，按下`ctrl-c`后即可停止进程。


详细示例代码请参考：
- {{ '[helloworld_app_registration_mode]({}/src/examples/cpp/helloworld/app/helloworld_app_registration_mode)'.format(code_site_root_path_url) }}


### 创建模块

在`AimRTCore`实例调用`Initialize`方法之后，通过`CreateModule`可以创建一个模块，并返回一个`aimrt::CoreRef`句柄，开发者可以直接基于此句柄调用一些框架的方法，比如 RPC 或者 Log 等。在此方式下没有一个比较清晰的`Module`边界，不利于大型项目的组织，一般仅用于快速做一些小工具。



以下是一个简单的例子，实现了一个发布 channel 消息的功能，开发者需要编写的`main.cc`文件如下：
```cpp
#include "core/aimrt_core.h"

#include "aimrt_module_cpp_interface/core.h"
#include "aimrt_module_protobuf_interface/channel/protobuf_channel.h"

#include "event.pb.h"

int32_t main(int32_t argc, char** argv) {
  // Create AimRTCore ins
  AimRTCore core;

  // Initialize AimRTCore ins
  AimRTCore::Options options;
  options.cfg_file_path = "path/to/cfg/xxx_cfg.yaml";
  core.Initialize(options);

  // Create module
  aimrt::CoreRef core_handle(core.GetModuleManager().CreateModule("HelloWorldModule"));

  // Register a msg type for publish
  auto publisher = core_handle.GetChannelHandle().GetPublisher("test_topic");
  aimrt::channel::RegisterPublishType<ExampleEventMsg>(publisher);

  // Start AimRTCore ins
  auto fu = core.AsyncStart();

  // Publish a message
  ExampleEventMsg msg;
  msg.set_msg("example msg");
  aimrt::channel::Publish(publisher, msg);

  // Wait for seconds
  std::this_thread::sleep_for(std::chrono::seconds(5));

  // Shutdown AimRTCore ins
  core.Shutdown();

  // Wait for complete shutdown
  fu.wait();

  return 0;
}
```

编译上面示例的`main.cc`，直接启动编译后的可执行文件即可运行进程，该进程将在发布一个消息后，等待一段时间并退出。

更多示例请参考：
- {{ '[helloworld_app]({}/src/examples/cpp/helloworld/app/helloworld_app)'.format(code_site_root_path_url) }}
- {{ '[normal_publisher_app]({}/src/examples/cpp/pb_chn/app/normal_publisher_app)'.format(code_site_root_path_url) }}
- {{ '[normal_subscriber_app]({}/src/examples/cpp/pb_chn/app/normal_subscriber_app)'.format(code_site_root_path_url) }}


## Pkg 模式

### 创建 Pkg

开发者可以引用 CMake Target：**aimrt::interface::aimrt_pkg_c_interface**，在其中的头文件{{ '[aimrt_pkg_c_interface/pkg_main.h]({}/src/interface/aimrt_pkg_c_interface/pkg_main.h)'.format(code_site_root_path_url) }}中，定义了几个要实现的接口：

```cpp
#ifdef __cplusplus
extern "C" {
#endif

// Get the num of modules in the pkg
size_t AimRTDynlibGetModuleNum();

// Get the list of module names in the pkg
const aimrt_string_view_t* AimRTDynlibGetModuleNameList();

// Create a module with the name
const aimrt_module_base_t* AimRTDynlibCreateModule(aimrt_string_view_t module_name);

// Destroy module
void AimRTDynlibDestroyModule(const aimrt_module_base_t* module_ptr);

#ifdef __cplusplus
}
#endif
```

其中，`aimrt_module_base_t`可以由继承了`ModuleBase`基类的派生类获得。关于`ModuleBase`基类的相关信息，请参考[ModuleBase](./module_base.md)文档。


通过这些接口，AimRT 框架运行时可以从 Pkg 动态库中获取想要的模块。开发者需要在一个`C/CPP`文件中实现这些接口来创建一个 Pkg。


这些接口是纯 C 形式的，所以理论上只要开发者将 Pkg 的符号都隐藏起来，不同 Pkg 之间可以做到较好的兼容性。如果开发者使用 C++，也可以使用{{ '[aimrt_pkg_c_interface/pkg_macro.h]({}/src/interface/aimrt_pkg_c_interface/pkg_macro.h)'.format(code_site_root_path_url) }}文件中的一个简单的宏来封装这些细节，用户只需要实现一个包含所有模块构造方法的静态数组即可。


以下是一个简单的示例，开发者需要编写如下的`pkg_main.cc`文件：

```cpp
#include "aimrt_pkg_c_interface/pkg_macro.h"
#include "bar_module.h"
#include "foo_module.h"

static std::tuple<std::string_view, std::function<aimrt::ModuleBase*()>> aimrt_module_register_array[]{
    {"FooModule", []() -> aimrt::ModuleBase* { return new FooModule(); }},
    {"BarModule", []() -> aimrt::ModuleBase* { return new BarModule(); }}};

AIMRT_PKG_MAIN(aimrt_module_register_array)
```

### 启动 Pkg

将上面的示例`pkg_main.cc`编译为动态库后，即可使用 AimRT 提供的**aimrt_main**可执行程序启动进程，通过配置中指定的路径来加载 Pkg 动态库。示例配置如下：
```yaml
aimrt:
  module:
    pkgs:
      - path: /path/to/your/pkg/libxxx_pkg.so
```

有了配置文件之后，通过以下示例命令启动 AimRT 进程，按下`ctrl-c`后即可停止进程：
```shell
./aimrt_main --cfg_file_path=/path/to/your/cfg/xxx_cfg.yaml
```


AimRT官方提供**aimrt_main**可执行程序接收一些参数作为 AimRT 运行时的初始化参数，这些参数功能如下：

|  参数项               |  类型     | 默认值                |作用  | 示例 |
|  ----                 | ----      | ----                | ----  | ----  |
| cfg_file_path         | string    | ""                  | 配置文件路径。  |  --cfg_file_path=/path/to/your/xxx_cfg.yaml |
| dump_cfg_file         | bool      | false               | 是否 Dump 配置文件。 |  --dump_cfg_file=true |
| dump_cfg_file_path    | string    | "./dump_cfg.yaml"   | Dump 配置文件的路径。 |  --dump_cfg_file_path=/path/to/your/xxx_dump_cfg.yaml |
| dump_init_report      | bool      | false               | 是否 Dump 初始化报告。<br>请注意，仅当初始化成功后才有初始化报告。 |  --dump_init_report=true |
| dump_init_report_path | string    | "./init_report.txt" | Dump 初始化报告的路径。 |  --dump_init_report_path=/path/to/your/xxx_init_report.txt |
| register_signal       | bool      | true                | 是否注册 sigint 和 sigterm 信号，用于触发 Shutdown。 |  --register_signal=true |
| running_duration      | int32     | 0                   | 本次运行时间，单位：s。如果为 0 则表示一直运行。 |  --running_duration=10 |


开发者也可以使用`./aimrt_main --help`命令查看这些参数功能。

