Runtime Interface
Introduction
If the C++ interfaces during the logic development phase mainly enable users to develop specific business logic, then the runtime interfaces introduced in this document allow users to determine how to deploy, integrate, and run this business logic.
AimRT provides two deployment and integration methods:
App Mode: Developers register/create modules in their own Main function, compiling the business logic directly into the main program during compilation;
Pkg Mode: Uses the aimrt_main executable provided by AimRT to load dynamically linked
Pkg
libraries at runtime based on configuration files, importing theModule
within them.
For the advantages, disadvantages, and applicable scenarios of both methods, please refer to the explanations in the Basic Concepts in AimRT document.
Neither method affects the business logic, and both can coexist or be switched between relatively easily. The actual choice depends on specific scenarios.
For information about the aimrt::CoreRef
handle, please refer to the CoreRef document.
App Mode
Developers directly reference the CMake Target: aimrt::runtime::core, and then can use the aimrt::runtime::core::AimRTCore
class from the core/aimrt_core.h file. The core interfaces required for App Mode are as follows:
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
Interface usage instructions:
void Initialize(const Options& options)
: Used to initialize the framework.Accepts an
AimRTCore::Options
as initialization parameters. The most important item iscfg_file_path
, which sets the configuration file path.Throws an exception if initialization fails.
void Start()
: Starts the framework.Throws an exception if startup fails.
Must be called after the Initialize method; otherwise, behavior is undefined.
If successful, it blocks the current thread and uses it as the main thread for this
AimRTCore
instance.
std::future<void> AsyncStart()
: Asynchronously starts the framework.Throws an exception if startup fails.
Must be called after the Initialize method; otherwise, behavior is undefined.
If successful, returns a
std::future<void>
handle. Thewait
method of this handle must be called to block and wait for completion after calling theShutdown
method.This method internally starts a new thread as the main thread for this
AimRTCore
instance.
void Shutdown()
: Stops the framework.Can be called from any thread at any stage and can be called any number of times.
After calling this method, the
Start
method will exit the blocking state after completing all tasks in the main thread.Note: Sometimes business logic may block tasks in the main thread, preventing the
Start
method from exiting the blocking state and gracefully terminating the framework. In such cases, an external force kill may be required.
Developers can create an AimRTCore
instance in their Main function, sequentially call its Initialize
, Start
/AsyncStart
methods, and capture the Ctrl-C
signal themselves to call the Shutdown
method, enabling graceful exit of the AimRTCore
instance.
The GetModuleManager
method of the AimRTCore
type returns a ModuleManager
handle, which can be used to register or create modules. In App Mode, the RegisterModule
or CreateModule
interfaces are required:
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
and CreateModule
represent two ways of writing logic in App Mode: Module Registration and Module Creation. The former still requires writing a business module class that inherits from ModuleBase
, while the latter offers more freedom.### Registration Module
Through RegisterModule
, you can directly register a standard module. Developers need to first implement a Module
that inherits from the base class ModuleBase
, then register this Module
instance before the AimRTCore
instance calls the Initialize
method. This approach still maintains a relatively clear Module
boundary.
For information about the ModuleBase
base class, please refer to the ModuleBase documentation.
Here is a simple example. The main.cc
file that developers need to write is as follows:
#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;
}
Compile the above main.cc
example, and simply run the compiled executable to start the process. Press ctrl-c
to stop the process.
For detailed example code, please refer to:
Creating a Module
After the AimRTCore
instance calls the Initialize
method, you can create a module using CreateModule
, which returns an aimrt::CoreRef
handle. Developers can directly use this handle to call some framework methods, such as RPC or Log. This approach does not have a clear Module
boundary, which is not conducive to organizing large projects. It is generally only used for quickly creating small tools.
Here is a simple example that implements a function to publish channel messages. The main.cc
file that developers need to write is as follows:
#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;
}
Compile the above main.cc
example, and simply run the compiled executable to start the process. The process will publish a message, wait for a period of time, and then exit.
For more examples, please refer to:
Pkg Mode### Creating a Pkg
Developers can reference the CMake Target: aimrt::interface::aimrt_pkg_c_interface. In its header file aimrt_pkg_c_interface/pkg_main.h, several interfaces to be implemented are defined:
#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
Here, aimrt_module_base_t
can be obtained from a derived class that inherits from the ModuleBase
base class. For information about the ModuleBase
base class, please refer to the ModuleBase documentation.
Through these interfaces, the AimRT framework can retrieve the desired modules from the Pkg dynamic library at runtime. Developers need to implement these interfaces in a C/CPP
file to create a Pkg.
These interfaces are in pure C form, so theoretically, as long as developers hide all symbols of the Pkg, good compatibility between different Pkgs can be achieved. If developers use C++, they can also use a simple macro from the aimrt_pkg_c_interface/pkg_macro.h file to encapsulate these details. Users only need to implement a static array containing all module constructors.
Below is a simple example. Developers need to write a pkg_main.cc
file as follows:
#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)
Launching a Pkg
After compiling the above example pkg_main.cc
into a dynamic library, you can use the aimrt_main executable provided by AimRT to start the process, loading the Pkg dynamic library via the path specified in the configuration. An example configuration is as follows:
aimrt:
module:
pkgs:
- path: /path/to/your/pkg/libxxx_pkg.so
With the configuration file in place, you can start the AimRT process using the following example command. Press ctrl-c
to stop the process:
./aimrt_main --cfg_file_path=/path/to/your/cfg/xxx_cfg.yaml
The aimrt_main executable provided by AimRT accepts several parameters as initialization parameters for the AimRT runtime. The functions of these parameters are as follows:
Parameter |
Type |
Default |
Purpose |
Example |
---|---|---|---|---|
cfg_file_path |
string |
“” |
Path to the configuration file. |
–cfg_file_path=/path/to/your/xxx_cfg.yaml |
dump_cfg_file |
bool |
false |
Whether to dump the configuration file. |
–dump_cfg_file=true |
dump_cfg_file_path |
string |
“./dump_cfg.yaml” |
Path to dump the configuration file. |
–dump_cfg_file_path=/path/to/your/xxx_dump_cfg.yaml |
dump_init_report |
bool |
false |
Whether to dump the initialization report. |
–dump_init_report=true |
dump_init_report_path |
string |
“./init_report.txt” |
Path to dump the initialization report. |
–dump_init_report_path=/path/to/your/xxx_init_report.txt |
register_signal |
bool |
true |
Whether to register sigint and sigterm signals to trigger Shutdown. |
–register_signal=true |
running_duration |
int32 |
0 |
Duration of this run, in seconds. If 0, it runs indefinitely. |
–running_duration=10 |
Developers can also use the ./aimrt_main --help
command to view the functions of these parameters.