refactor(runtime): add shared application runtime

This commit is contained in:
lgv 2026-06-30 16:02:28 +08:00
parent c7f31708cb
commit 922f73adb5
8 changed files with 335 additions and 122 deletions

View File

@ -105,6 +105,7 @@ add_subdirectory(cmvr-es)
add_executable(cmvr_es cmvr-es/main.cpp) add_executable(cmvr_es cmvr-es/main.cpp)
target_include_directories(cmvr_es PRIVATE ${GLOG_INCLUDE_DIRS}) target_include_directories(cmvr_es PRIVATE ${GLOG_INCLUDE_DIRS})
target_link_libraries(cmvr_es PRIVATE target_link_libraries(cmvr_es PRIVATE
cmvr_es::runtime
cmvr_es::proto cmvr_es::proto
cmvr_es::logging cmvr_es::logging
service service
@ -113,7 +114,7 @@ target_link_libraries(cmvr_es PRIVATE
cmvr_es::service cmvr_es::service
cmvr_es::hardware cmvr_es::hardware
cmvr_es::device::canbus cmvr_es::device::canbus
cmvr_es::device::ti5motor cmvr_es::device::ti5_canopen_motor_driver
cmvr_es::algorithms::controller cmvr_es::algorithms::controller
cmvr_es::ik_solver cmvr_es::ik_solver
cmvr_es::base_motion cmvr_es::base_motion

View File

@ -4,9 +4,11 @@ link_libraries(cmvr_es::logging)
add_subdirectory(common) add_subdirectory(common)
add_subdirectory(hardware) add_subdirectory(hardware)
add_subdirectory(algorithms) add_subdirectory(algorithms)
add_subdirectory(simulate)
add_subdirectory(devices) add_subdirectory(devices)
add_subdirectory(manager/device_manager) add_subdirectory(manager/device_manager)
add_subdirectory(task) add_subdirectory(task)
add_subdirectory(manager/task_manager) add_subdirectory(manager/task_manager)
add_subdirectory(service) add_subdirectory(service)
add_subdirectory(simulate) add_subdirectory(runtime)
add_subdirectory(test)

View File

@ -0,0 +1,24 @@
#ifndef CMVR_ES_PROTOBUF_UTILS_H
#define CMVR_ES_PROTOBUF_UTILS_H
#include <google/protobuf/repeated_field.h>
#include <vector>
namespace cmvr::common {
template <typename T>
std::vector<T> repeatedToVector(const google::protobuf::RepeatedPtrField<T>& values)
{
return {values.begin(), values.end()};
}
template <typename T>
std::vector<T> repeatedToVector(const google::protobuf::RepeatedField<T>& values)
{
return {values.begin(), values.end()};
}
} // namespace cmvr::common
#endif // CMVR_ES_PROTOBUF_UTILS_H

View File

@ -0,0 +1,25 @@
#ifndef CMVR_ES_STRING_UTILS_H
#define CMVR_ES_STRING_UTILS_H
#include <sstream>
#include <string>
#include <vector>
namespace cmvr::common {
inline std::string joinStrings(const std::vector<std::string>& values,
const std::string& separator = ", ")
{
std::ostringstream oss;
for (std::size_t i = 0; i < values.size(); ++i) {
if (i > 0) {
oss << separator;
}
oss << values[i];
}
return oss.str();
}
} // namespace cmvr::common
#endif // CMVR_ES_STRING_UTILS_H

View File

@ -1,149 +1,49 @@
//
// Created by xtkuang on 2025/5/6.
//
#include <unistd.h>
#include <limits.h>
#include <csignal> #include <csignal>
#include <filesystem>
#include <pthread.h> #include <pthread.h>
#include "common/base/logging/logger.h" #include "common/base/logging/logger.h"
#include "cmvr/config/cmvr_es_config/cmvr_es_config.pb.h" #include "runtime/include/cmvr_runtime.h"
#include "cmvr/config/device_manager_config/device_manager_config.pb.h"
#include "cmvr/config/task_manager_config/task_manager_config.pb.h"
#include "common/config/config_files.h"
#include "common/io/proto_file_io.h"
#include "manager/device_manager/include/device_manager.h"
#include "manager/task_manager/include/task_manager.h"
#include "task/grpc_server_task/include/grpc_server_task.h"
namespace { namespace {
void logSection(const char* title) bool blockShutdownSignals(sigset_t& shutdown_signals)
{ {
CMVR_LOG(INFO) << "---------------- " << title << " ----------------"; sigemptyset(&shutdown_signals);
} sigaddset(&shutdown_signals, SIGINT);
sigaddset(&shutdown_signals, SIGTERM);
bool executableDirectory(std::filesystem::path& directory) return pthread_sigmask(SIG_BLOCK, &shutdown_signals, nullptr) == 0;
{
char executable_path[PATH_MAX] = {0};
const ssize_t count = readlink("/proc/self/exe", executable_path, PATH_MAX - 1);
if (count < 0) {
return false;
}
executable_path[count] = '\0';
directory = std::filesystem::path(executable_path).parent_path();
return true;
} }
} // namespace } // namespace
int main(int argc, char* argv[]) int main()
{ {
std::filesystem::path executable_dir;
if (!executableDirectory(executable_dir)) {
CMVR_LOG(ERROR) << "Failed to read /proc/self/exe";
return 1;
}
std::string config_path;
if (argc == 1) {
config_path = (executable_dir / "config/cmvr_es.pb.txt").string();
CMVR_LOG(INFO) << "Using default config path: " << config_path;
} else if (argc == 2) {
config_path = argv[1];
} else {
CMVR_LOG(ERROR) << "Usage: " << argv[0] << " [cmvr_es.pb.txt]";
return 1;
}
cmvr::ConfigHelper::setConfigRootFromFile(config_path);
cmvr::config::CMVRESRootConfig root_config;
if (!ProtoMessageIo::getProtoFromAsciiFile(config_path, &root_config)) {
CMVR_LOG(ERROR) << "Failed to load config: " << config_path;
return 1;
}
const auto& app_config = root_config.cmvr_es();
if (app_config.logger_config_file().empty()) {
CMVR_LOG(ERROR) << "Logger config file is empty";
return 1;
}
cmvr::config::LoggerRootConfig logger_root;
if (!cmvr::ConfigHelper::loadConfigFile(app_config.logger_config_file(), logger_root)) {
CMVR_LOG(ERROR) << "Failed to load logger config: " << app_config.logger_config_file();
return 1;
}
if (!cmvr::logging::initLogging(logger_root.logger(), "cmvr_es", executable_dir)) {
CMVR_LOG(FATAL) << "Failed to initialize logging";
return 1;
}
logSection("Startup");
CMVR_LOG(INFO) << "Logging initialized";
CMVR_LOG(INFO) << "[Startup] Runtime config file: " << config_path;
CMVR_LOG(INFO) << "[Startup] Logger config file: "
<< cmvr::ConfigHelper::resolveConfigFile(app_config.logger_config_file());
sigset_t shutdown_signals; sigset_t shutdown_signals;
sigemptyset(&shutdown_signals); if (!blockShutdownSignals(shutdown_signals)) {
sigaddset(&shutdown_signals, SIGINT);
sigaddset(&shutdown_signals, SIGTERM);
if (pthread_sigmask(SIG_BLOCK, &shutdown_signals, nullptr) != 0) {
CMVR_LOG(ERROR) << "Failed to block shutdown signals";
cmvr::logging::shutdownLogging();
return 1; return 1;
} }
if (app_config.device_manager_config_file().empty()) { cmvr::Runtime runtime;
CMVR_LOG(ERROR) << "DeviceManager config file is empty"; if (!runtime.init()) {
cmvr::logging::shutdownLogging();
return 1; return 1;
} }
logSection("DeviceManager"); if (!runtime.startTasks()) {
CMVR_LOG(INFO) << "[Startup] Load DeviceManager config: "
<< cmvr::ConfigHelper::resolveConfigFile(app_config.device_manager_config_file());
cmvr::config::DeviceManagerRootConfig device_manager_root;
if (!cmvr::ConfigHelper::loadConfigFile(app_config.device_manager_config_file(), device_manager_root)) {
CMVR_LOG(ERROR) << "Failed to load DeviceManager config: " << app_config.device_manager_config_file();
cmvr::logging::shutdownLogging();
return 1;
}
CMVR_LOG(INFO) << "[Startup] Initialize DeviceManager";
cmvr::device::DeviceManager::getInstance(device_manager_root.device_manager());
cmvr::task::registerGrpcServerTaskFactory();
if (app_config.task_manager_config_file().empty()) {
CMVR_LOG(ERROR) << "TaskManager config file is empty";
cmvr::logging::shutdownLogging();
return 1;
}
logSection("TaskManager");
CMVR_LOG(INFO) << "[Startup] Load TaskManager config: "
<< cmvr::ConfigHelper::resolveConfigFile(app_config.task_manager_config_file());
cmvr::config::TaskManagerRootConfig task_manager_root;
if (!cmvr::ConfigHelper::loadConfigFile(app_config.task_manager_config_file(), task_manager_root)) {
CMVR_LOG(ERROR) << "Failed to load TaskManager config: " << app_config.task_manager_config_file();
cmvr::logging::shutdownLogging();
return 1; return 1;
} }
CMVR_LOG(INFO) << "[Startup] Initialize TaskManager"; if (auto viewer = runtime.mujocoViewer("mujoco_viewer")) {
auto& task_manager = cmvr::task::TaskManager::getInstance(task_manager_root.task_manager()); CMVR_LOG(INFO) << "[Startup] Run MuJoCo viewer on main thread";
logSection("Start Tasks"); viewer->runOnMainThread();
CMVR_LOG(INFO) << "[Startup] Start tasks"; runtime.shutdown();
task_manager.startRunTask(); return 0;
}
int received_signal = 0; int received_signal = 0;
if (sigwait(&shutdown_signals, &received_signal) != 0) { if (sigwait(&shutdown_signals, &received_signal) != 0) {
CMVR_LOG(ERROR) << "Failed to wait for shutdown signal"; runtime.shutdown();
task_manager.stopRunTask();
cmvr::logging::shutdownLogging();
return 1; return 1;
} }
task_manager.stopRunTask(); runtime.shutdown();
cmvr::logging::shutdownLogging();
return 0; return 0;
} }

View File

@ -0,0 +1,19 @@
add_library(cmvr_runtime STATIC
src/cmvr_runtime.cpp
)
target_include_directories(cmvr_runtime PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(cmvr_runtime PUBLIC
cmvr_es::proto
cmvr_es::common
cmvr_es::device_manager
cmvr_es::task_manager
cmvr_es::service
cmvr_es::mujoco_viewer
)
add_library(cmvr_es::runtime ALIAS cmvr_runtime)
install(TARGETS cmvr_runtime LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

View File

@ -0,0 +1,47 @@
#ifndef CMVR_ES_RUNTIME_CMVR_RUNTIME_H
#define CMVR_ES_RUNTIME_CMVR_RUNTIME_H
#include <filesystem>
#include <memory>
#include <string>
#include "manager/device_manager/include/device_manager.h"
#include "manager/task_manager/include/task_manager.h"
#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h"
namespace cmvr {
class Runtime {
public:
Runtime() = default;
~Runtime();
Runtime(const Runtime&) = delete;
Runtime& operator=(const Runtime&) = delete;
bool init();
bool init(const std::string& config_path);
bool startTasks(double control_period_s = 0.001);
void stopTasks();
void shutdown();
bool initialized() const { return initialized_; }
bool tasksStarted() const { return tasks_started_; }
device::DeviceManager& deviceManager();
task::TaskManager& taskManager();
std::shared_ptr<MujocoViewerDevice> mujocoViewer(const std::string& id = "mujoco_viewer");
private:
bool init_(const std::string& config_path,
const std::filesystem::path& executable_directory);
bool executableDirectory_(std::filesystem::path& directory) const;
bool initialized_{false};
bool tasks_started_{false};
bool logging_initialized_{false};
};
} // namespace cmvr
#endif // CMVR_ES_RUNTIME_CMVR_RUNTIME_H

View File

@ -0,0 +1,195 @@
#include "runtime/include/cmvr_runtime.h"
#include <limits.h>
#include <unistd.h>
#include "cmvr/config/cmvr_es_config/cmvr_es_config.pb.h"
#include "cmvr/config/device_manager_config/device_manager_config.pb.h"
#include "cmvr/config/logger_config/logger_config.pb.h"
#include "cmvr/config/task_manager_config/task_manager_config.pb.h"
#include "common/base/logging/logger.h"
#include "common/config/config_files.h"
#include "common/io/proto_file_io.h"
#include "task/grpc_server_task/include/grpc_server_task.h"
namespace cmvr {
namespace {
void logSection(const char* title)
{
CMVR_LOG(INFO) << "---------------- " << title << " ----------------";
}
} // namespace
Runtime::~Runtime()
{
shutdown();
}
bool Runtime::init()
{
std::filesystem::path executable_directory;
if (!executableDirectory_(executable_directory)) {
return false;
}
return init_((executable_directory / "config/cmvr_es.pb.txt").string(), executable_directory);
}
bool Runtime::init(const std::string& config_path)
{
std::filesystem::path executable_directory;
if (!executableDirectory_(executable_directory)) {
return false;
}
return init_(config_path, executable_directory);
}
bool Runtime::init_(const std::string& config_path,
const std::filesystem::path& executable_directory)
{
if (initialized_) {
return true;
}
if (config_path.empty()) {
return false;
}
ConfigHelper::setConfigRootFromFile(config_path);
config::CMVRESRootConfig root_config;
if (!ProtoMessageIo::getProtoFromAsciiFile(config_path, &root_config)) {
return false;
}
const auto& app_config = root_config.cmvr_es();
if (app_config.logger_config_file().empty()) {
return false;
}
config::LoggerRootConfig logger_root;
if (!ConfigHelper::loadConfigFile(app_config.logger_config_file(), logger_root)) {
return false;
}
if (executable_directory.empty()) {
return false;
}
if (!logging::initLogging(logger_root.logger(), "cmvr_es", executable_directory)) {
return false;
}
logging_initialized_ = true;
logSection("Startup");
CMVR_LOG(INFO) << "Logging initialized";
CMVR_LOG(INFO) << "[Startup] Runtime config file: " << config_path;
CMVR_LOG(INFO) << "[Startup] Logger config file: "
<< ConfigHelper::resolveConfigFile(app_config.logger_config_file());
if (app_config.device_manager_config_file().empty()) {
CMVR_LOG(ERROR) << "DeviceManager config file is empty";
return false;
}
logSection("DeviceManager");
CMVR_LOG(INFO) << "[Startup] Load DeviceManager config: "
<< ConfigHelper::resolveConfigFile(app_config.device_manager_config_file());
config::DeviceManagerRootConfig device_manager_root;
if (!ConfigHelper::loadConfigFile(app_config.device_manager_config_file(), device_manager_root)) {
CMVR_LOG(ERROR) << "Failed to load DeviceManager config: "
<< app_config.device_manager_config_file();
return false;
}
CMVR_LOG(INFO) << "[Startup] Initialize DeviceManager";
device::DeviceManager::getInstance(device_manager_root.device_manager());
task::registerGrpcServerTaskFactory();
if (app_config.task_manager_config_file().empty()) {
CMVR_LOG(ERROR) << "TaskManager config file is empty";
return false;
}
logSection("TaskManager");
CMVR_LOG(INFO) << "[Startup] Load TaskManager config: "
<< ConfigHelper::resolveConfigFile(app_config.task_manager_config_file());
config::TaskManagerRootConfig task_manager_root;
if (!ConfigHelper::loadConfigFile(app_config.task_manager_config_file(), task_manager_root)) {
CMVR_LOG(ERROR) << "Failed to load TaskManager config: "
<< app_config.task_manager_config_file();
return false;
}
CMVR_LOG(INFO) << "[Startup] Initialize TaskManager";
task::TaskManager::getInstance(task_manager_root.task_manager());
initialized_ = true;
return true;
}
bool Runtime::startTasks(const double control_period_s)
{
if (!initialized_) {
return false;
}
if (tasks_started_) {
return true;
}
logSection("Start Tasks");
CMVR_LOG(INFO) << "[Startup] Start tasks";
task::TaskManager::getInstance().startRunTask(control_period_s);
tasks_started_ = true;
return true;
}
void Runtime::stopTasks()
{
if (!tasks_started_) {
return;
}
task::TaskManager::getInstance().stopRunTask();
tasks_started_ = false;
}
void Runtime::shutdown()
{
if (tasks_started_) {
stopTasks();
}
if (initialized_) {
device::DeviceManager::getInstance().stop();
task::TaskManager::destroyInstance();
device::DeviceManager::destroyInstance();
initialized_ = false;
}
if (logging_initialized_) {
logging::shutdownLogging();
logging_initialized_ = false;
}
}
device::DeviceManager& Runtime::deviceManager()
{
return device::DeviceManager::getInstance();
}
task::TaskManager& Runtime::taskManager()
{
return task::TaskManager::getInstance();
}
std::shared_ptr<MujocoViewerDevice> Runtime::mujocoViewer(const std::string& id)
{
return device::DeviceManager::getInstance().getDevice<MujocoViewerDevice>(id);
}
bool Runtime::executableDirectory_(std::filesystem::path& directory) const
{
char executable_path[PATH_MAX] = {0};
const ssize_t count = readlink("/proc/self/exe", executable_path, PATH_MAX - 1);
if (count < 0) {
return false;
}
executable_path[count] = '\0';
directory = std::filesystem::path(executable_path).parent_path();
return true;
}
} // namespace cmvr