diff --git a/document/sphinx-cn/release_notes/v0_9_0.md b/document/sphinx-cn/release_notes/v0_9_0.md index 5e95c6bf7..8b2dd1abe 100644 --- a/document/sphinx-cn/release_notes/v0_9_0.md +++ b/document/sphinx-cn/release_notes/v0_9_0.md @@ -10,6 +10,7 @@ - 新增了第三方库 asio,runtime::core 不再引用 boost,改为引用独立的 asio 库,以减轻依赖; - 新增 aimrt_cli trans 命令,用于将 使用 aimrt record_playback 插件录制的 bag 文件转换为 ros2 的 bag 文件; - 新增 Echo 插件,用于回显消息; +- 新增了基于执行器的定时器,方便执行定时任务; **次要修改**: - 缩短了一些 examples 的文件路径长度; diff --git a/document/sphinx-cn/tutorials/interface_cpp/executor.md b/document/sphinx-cn/tutorials/interface_cpp/executor.md index b3e0585c9..4141f6ec2 100644 --- a/document/sphinx-cn/tutorials/interface_cpp/executor.md +++ b/document/sphinx-cn/tutorials/interface_cpp/executor.md @@ -384,7 +384,7 @@ class HelloWorldModule : public aimrt::ModuleBase { aimrt::co::Task MainLoop() { auto time_scheduler = co::AimRTScheduler(time_schedule_executor_); - + // Schedule to time_schedule_executor co_await co::Schedule(time_scheduler); @@ -404,7 +404,7 @@ class HelloWorldModule : public aimrt::ModuleBase { void ExecutorCoModule::Shutdown() { run_flag_ = false; - + // Blocked waiting for all coroutines in the scope to complete execution co::SyncWait(scope_.complete()); @@ -418,3 +418,145 @@ class HelloWorldModule : public aimrt::ModuleBase { aimrt::executor::ExecutorRef time_schedule_executor_; }; ``` + +## 基于执行器的定时器 + +### 定时器接口 + +代码文件: +- {{ '[aimrt_module_cpp_interface/executor/timer.h]({}/src/interface/aimrt_module_cpp_interface/executor/timer.h)'.format(code_site_root_path_url) }} + +参考示例: +- {{ '[timer_module.cc]({}/src/examples/cpp/executor/module/timer_module/timer_module.cc)'.format(code_site_root_path_url) }} + +### 定时器的概念 + +定时器是基于执行器提供的一个定时执行任务的工具,可以基于执行器创建一个定时器,并指定定时器执行的周期。 + +### 定时器接口 + +使用`aimrt::executor::CreateTimer`接口创建一个定时器,并指定定时器执行的周期和任务,其函数声明如下: + +```cpp +namespace aimrt::executor { + +template +std::shared_ptr CreateTimer(ExecutorRef executor, std::chrono::nanoseconds period, + TaskType&& task, bool auto_start = true); + +} // namespace aimrt::executor +``` + +其中`ExecutorRef`是执行器句柄,`TaskType`是任务类型,`period`是定时器执行的周期,`auto_start`是是否自动启动定时器,默认为`true`。 + +定时器所使用的 `ExecutorRef` 必须支持定时调度功能,即`SupportTimerSchedule()` 返回 `true`,可以参考[执行器配置](../cfg/executor.md)章节查询执行器是否支持定时调度功能。 + +`TaskType`是任务类型,接受一个可调用对象,可以使用`std::function`、`std::bind`、lambda 表达式等,只要其函数签名满足如下要求之一即可: + +```cpp +void() +void(TimerBase&) +void(const TimerBase&) +``` + +函数签名中,`TimerBase&`是定时器对象本身,`const TimerBase&`是定时器对象的常量引用。 + +`TimerBase`是定时器对象的基类,`Timer`是定时器对象的派生类,主要封装了用户指定的定时器任务的执行,我们一般使用 `TimerBase` 的智能指针类型:`std::shared_ptr`。 + +`TimerBase` 的核心接口如下: + +```cpp +class TimerBase { + public: + virtual void Reset() = 0; + virtual void Cancel() = 0; + virtual void ExecuteTask() = 0; + virtual void SyncWait() = 0; + + [[nodiscard]] bool IsCancelled() const; + [[nodiscard]] std::chrono::nanoseconds Period() const; + [[nodiscard]] std::chrono::system_clock::time_point NextCallTime() const; + [[nodiscard]] std::chrono::nanoseconds TimeUntilNextCall() const; + [[nodiscard]] ExecutorRef Executor() const; +}; +``` + +关于`TimerBase`类中接口的详细使用说明如下: +- `void Cancel()`:取消定时器,设置 cancel 状态。 +- `void Reset()`:重置定时器,取消 cancel 状态,并重置下次执行时间,下一次执行时间会基于当前时间加上周期计算得出。 +- `void ExecuteTask()`:执行定时器任务。 +- `void SyncWait()`:等待已经取消的定时器清理资源完毕,阻塞等待定时器任务取消后的下一个执行时间点到来。 +- `bool IsCancelled()`:返回定时器是否被取消。 +- `std::chrono::nanoseconds Period()`:返回定时器执行的周期。 +- `std::chrono::system_clock::time_point NextCallTime()`:返回定时器下次执行的时间。 +- `std::chrono::nanoseconds TimeUntilNextCall()`:返回定时器下次执行的时间与当前时间的时间差。 +- `ExecutorRef Executor()`:返回定时器所属的执行器。 + +### 定时器行为概述 + +定时器的行为如下: +- 定时器创建后,默认是自动启动的,相当于自动调用一次`Reset()`接口,如果不想自动启动,可以设置`auto_start`为`false`,此时定时器会处于`cancel`状态。 +- 定时器无论是否启动,调用`Cancel()`接口,会取消定时器,并设置 cancel 状态。 +- 定时器无论是否启动,调用`Reset()`接口,会重置定时器,取消 cancel 状态,并重置下次执行时间,下一次执行时间会基于当前时间加上周期计算得出。 +- `Reset()` 接口可以覆盖原先的定时器任务,即调用`Reset()`接口后,紧接着调用`Reset()`接口,会重新按照新的周期执行任务,原先的定时器任务会被新的任务覆盖。 +- 如果任务执行时间太长或者定时器所使用的执行器中存在阻塞操作,导致错过部分定时周期,定时器不会将错过的次数补上,而是等到下次执行时间到达时执行任务,举例如下: + - 假设定时器周期为 1000 ms,原本预计在 0, 1000, 2000, 3000, 4000, ... ms 各执行一次任务 + - 假设任务执行时间为 1500 ms,那么在 0 ms 时启动的任务在 1500 ms 时执行完毕,并错过了 1000 ms 时的执行 + - 定时器会将下一次执行时间重置为 2000 ms,并在 2000 ms 时执行任务,而不会补上 1000 ms 时的执行 + - 最终任务的执行起始时间点是:0, 2000, 4000, 6000, ... ms +- 由于一些实现上的原因,定时器 `Cancel` 后模块立马退出会有一定的风险,需要等到下一个执行时间点到来后才能确保资源得到正确释放,例如定时器周期为 1000 ms, 在 500 ms 时 `Cancel`,需要等到 1000 ms 时才能确保资源得到正确释放(但在 1000 ms 时用户任务不会实际执行,只会进行一些清理工作),所以推荐在 Shutdown 时先 `Cancel` 再 `SyncWait`。 +- `SyncWait()` 接口仅用于等待清理执行器以及定时器资源完毕,在用户传入的 task 中调用会导致死锁。 + +### 定时器使用示例 + +以下是一个简单的使用示例,演示了如何创建一个定时器,并使用定时器执行一个任务: +```cpp +bool TimerModule::Initialize(aimrt::CoreRef core) { + core_ = core; + + timer_executor_ = core_.GetExecutorManager().GetExecutor("timer_executor"); + AIMRT_CHECK_ERROR_THROW(timer_executor_, "Can not get timer_executor"); + AIMRT_CHECK_ERROR_THROW(timer_executor_.SupportTimerSchedule(), + "timer_executor does not support timer schedule"); + + return true; +} + +bool TimerModule::Start() { + using namespace std::chrono_literals; + + auto start_time = timer_executor_.Now(); + auto task = [logger = core_.GetLogger(), start_time](aimrt::executor::TimerBase& timer) { + static int count = 0; + + auto now = timer.Executor().Now(); + auto timepoint = std::chrono::duration_cast(now - start_time).count(); + AIMRT_HL_INFO(logger, "Executed {} times, execute timepoint: {} ms", ++count, timepoint); + + if (count >= 10) { + timer.Cancel(); + AIMRT_HL_INFO(logger, "Timer cancelled at timepoint: {} ms", timepoint); + } + }; + + timer_ = aimrt::executor::CreateTimer(timer_executor_, 100ms, std::move(task)); + AIMRT_INFO("Timer created at timepoint: 0 ms"); + + timer_executor_.ExecuteAfter(350ms, [this, logger = core_.GetLogger()]() { + timer_->Reset(); + AIMRT_HL_INFO(logger, "Timer reset at timepoint: 350 ms"); + }); + + timer_executor_.ExecuteAfter(600ms, [this, logger = core_.GetLogger()]() { + timer_->Reset(); + AIMRT_HL_INFO(logger, "Timer reset at timepoint: 600 ms"); + }); + + return true; +} + +void TimerModule::Shutdown() { + timer_->Cancel(); + timer_->SyncWait(); +} +``` diff --git a/src/common/util/same_arg_trait.h b/src/common/util/same_arg_trait.h new file mode 100644 index 000000000..6b3993819 --- /dev/null +++ b/src/common/util/same_arg_trait.h @@ -0,0 +1,39 @@ +// Copyright (c) 2024 The AimRT Authors. +// AimRT is licensed under Mulan PSL v2. + +#pragma once + +#include +#include + +namespace aimrt::common::util { +template +struct function_args; + +template +struct function_args { + using type = std::tuple; +}; + +template +struct function_args { + using type = std::tuple; +}; + +template +struct function_args { + using type = std::tuple; +}; + +template +struct function_args { + using type = typename function_args::type; +}; + +template +using function_args_t = typename function_args::type; + +template +concept SameArguments = std::same_as, function_args_t>; + +} // namespace aimrt::common::util diff --git a/src/common/util/same_arg_trait_test.cc b/src/common/util/same_arg_trait_test.cc new file mode 100644 index 000000000..cac939829 --- /dev/null +++ b/src/common/util/same_arg_trait_test.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2024 The AimRT Authors. +// AimRT is licensed under Mulan PSL v2. + +#include "util/same_arg_trait.h" + +#include +#include +#include + +namespace aimrt::common::util { +namespace { +// NOLINTBEGIN(readability-named-parameter, performance-unnecessary-value-param) + +void func1(int, double) {} +void func2(int, double) {} +void func3(double, int) {} +void func4() {} +void func5(std::string, int, double) {} +int func6(int, double) { return 0; } + +int return_int(int, double) { return 0; } +double return_double(int, double) { return 0.0; } +std::string return_string(int, double) { return ""; } + +TEST(SameArgTraitTest, NormalFunctions) { + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); +} + +class TestClass { + public: + void method1(int, double) {} + void method2(int, double) const {} + int method3(int, double) { return 0; } // NOLINT(readability-convert-member-functions-to-static) + virtual void method4(int, double) {} + + void overloaded(int, double) {} + void overloaded(double, int) {} + void overloaded(int, double) const {} +}; + +TEST(SameArgTraitTest, MemberFunctions) { + using Method1 = decltype(&TestClass::method1); + using Method2 = decltype(&TestClass::method2); + using Method3 = decltype(&TestClass::method3); + using Method4 = decltype(&TestClass::method4); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + + using Overload1 = void (TestClass::*)(int, double); + EXPECT_TRUE((SameArguments)); +} + +struct FunctionObject1 { + void operator()(int, double) {} +}; + +struct FunctionObject2 { + void operator()(int, double) const {} +}; + +struct FunctionObject3 { + void operator()(double, int) {} +}; + +TEST(SameArgTraitTest, FunctionObjects) { + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_TRUE((SameArguments)); +} + +auto lambda1 = [](int, double) {}; +auto lambda2 = [](int, double) {}; +auto lambda3 = [](double, int) {}; +auto lambda4 = [](int, double) -> int { return 0; }; +auto lambda_capture = [x = 0](int, double) {}; + +TEST(SameArgTraitTest, LambdaFunctions) { + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); +} + +std::function std_func1; +std::function std_func2; +std::function std_func3; + +TEST(SameArgTraitTest, StdFunction) { + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); +} + +void complex_func1(std::vector, std::map) {} +void complex_func2(std::vector, std::map) {} +void complex_func3(std::map, std::vector) {} + +TEST(SameArgTraitTest, ComplexArgumentTypes) { + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); +} + +void ref_func1(int&, const double&) {} +void ref_func2(int&, const double&) {} +void ptr_func(int*, double*) {} + +TEST(SameArgTraitTest, ReferenceAndPointerArguments) { + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); +} + +TEST(SameArgTraitTest, CrossTypeComparisons) { + using Method1 = decltype(&TestClass::method1); + using ConstMethod = decltype(&TestClass::method2); + using VirtualMethod = decltype(&TestClass::method4); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + + EXPECT_TRUE((SameArguments)); + EXPECT_TRUE((SameArguments)); + + EXPECT_TRUE((SameArguments)); + EXPECT_FALSE((SameArguments)); + + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); + EXPECT_FALSE((SameArguments)); +} + +// NOLINTEND(readability-named-parameter, performance-unnecessary-value-param) +} // namespace +} // namespace aimrt::common::util diff --git a/src/examples/cpp/executor/CMakeLists.txt b/src/examples/cpp/executor/CMakeLists.txt index 2544ecca5..240217f68 100644 --- a/src/examples/cpp/executor/CMakeLists.txt +++ b/src/examples/cpp/executor/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(module/executor_module) add_subdirectory(module/executor_co_module) add_subdirectory(module/executor_co_loop_module) add_subdirectory(module/real_time_module) +add_subdirectory(module/timer_module) # pkg add_subdirectory(pkg/executor_pkg) diff --git a/src/examples/cpp/executor/install/linux/bin/cfg/examples_cpp_timer_cfg.yaml b/src/examples/cpp/executor/install/linux/bin/cfg/examples_cpp_timer_cfg.yaml new file mode 100644 index 000000000..6ff7b5372 --- /dev/null +++ b/src/examples/cpp/executor/install/linux/bin/cfg/examples_cpp_timer_cfg.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2024 The AimRT Authors. +# AimRT is licensed under Mulan PSL v2. + +aimrt: + log: + core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off + backends: + - type: console + executor: + executors: + - name: timer_executor + type: asio_thread + options: + thread_num: 1 + module: + pkgs: + - path: ./libexecutor_pkg.so + enable_modules: [TimerModule] + modules: + - name: TimerModule + log_lvl: INFO diff --git a/src/examples/cpp/executor/install/linux/bin/start_examples_cpp_timer.sh b/src/examples/cpp/executor/install/linux/bin/start_examples_cpp_timer.sh new file mode 100755 index 000000000..875bce8c8 --- /dev/null +++ b/src/examples/cpp/executor/install/linux/bin/start_examples_cpp_timer.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./aimrt_main --cfg_file_path=./cfg/examples_cpp_timer_cfg.yaml diff --git a/src/examples/cpp/executor/module/timer_module/CMakeLists.txt b/src/examples/cpp/executor/module/timer_module/CMakeLists.txt new file mode 100644 index 000000000..b01260763 --- /dev/null +++ b/src/examples/cpp/executor/module/timer_module/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (c) 2024, The AimRT Authors. +# AimRT is licensed under Mulan PSL v2. + +# Get the current folder name +string(REGEX REPLACE ".*/\(.*\)" "\\1" CUR_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +# Get namespace +get_namespace(CUR_SUPERIOR_NAMESPACE) +string(REPLACE "::" "_" CUR_SUPERIOR_NAMESPACE_UNDERLINE ${CUR_SUPERIOR_NAMESPACE}) + +# Set target name +set(CUR_TARGET_NAME ${CUR_SUPERIOR_NAMESPACE_UNDERLINE}_${CUR_DIR}) +set(CUR_TARGET_ALIAS_NAME ${CUR_SUPERIOR_NAMESPACE}::${CUR_DIR}) + +# Set file collection +file(GLOB_RECURSE head_files ${CMAKE_CURRENT_SOURCE_DIR}/*.h) +file(GLOB_RECURSE src ${CMAKE_CURRENT_SOURCE_DIR}/*.cc) +file(GLOB_RECURSE test_files ${CMAKE_CURRENT_SOURCE_DIR}/*_test.cc) +list(REMOVE_ITEM src ${test_files}) + +# Add target +add_library(${CUR_TARGET_NAME} STATIC) +add_library(${CUR_TARGET_ALIAS_NAME} ALIAS ${CUR_TARGET_NAME}) + +# Set source file of target +target_sources(${CUR_TARGET_NAME} PRIVATE ${src}) + +# Set include path of target +target_include_directories( + ${CUR_TARGET_NAME} + PUBLIC $) + +# Set link libraries of target +target_link_libraries( + ${CUR_TARGET_NAME} + PRIVATE yaml-cpp::yaml-cpp + PUBLIC aimrt::interface::aimrt_module_cpp_interface) + +# Set test of target +if(AIMRT_BUILD_TESTS AND test_files) + add_gtest_target(TEST_TARGET ${CUR_TARGET_NAME} TEST_SRC ${test_files}) +endif() diff --git a/src/examples/cpp/executor/module/timer_module/timer_module.cc b/src/examples/cpp/executor/module/timer_module/timer_module.cc new file mode 100644 index 000000000..5fed30b0e --- /dev/null +++ b/src/examples/cpp/executor/module/timer_module/timer_module.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2024 The AimRT Authors. +// AimRT is licensed under Mulan PSL v2. + +#include "timer_module/timer_module.h" + +#include +#include + +namespace aimrt::examples::cpp::executor::timer_module { + +bool TimerModule::Initialize(aimrt::CoreRef core) { + core_ = core; + + timer_executor_ = core_.GetExecutorManager().GetExecutor("timer_executor"); + AIMRT_CHECK_ERROR_THROW(timer_executor_, "Can not get timer_executor"); + AIMRT_CHECK_ERROR_THROW(timer_executor_.SupportTimerSchedule(), + "timer_executor does not support timer schedule"); + + return true; +} + +bool TimerModule::Start() { + using namespace std::chrono_literals; + + auto start_time = timer_executor_.Now(); + auto task = [logger = core_.GetLogger(), start_time](aimrt::executor::TimerBase& timer) { + static int count = 0; + + auto now = timer.Executor().Now(); + auto timepoint = std::chrono::duration_cast(now - start_time).count(); + AIMRT_HL_INFO(logger, "Executed {} times, execute timepoint: {} ms", ++count, timepoint); + + if (count >= 10) { + timer.Cancel(); + AIMRT_HL_INFO(logger, "Timer cancelled at timepoint: {} ms", timepoint); + } + }; + + timer_ = aimrt::executor::CreateTimer(timer_executor_, 100ms, std::move(task)); + AIMRT_INFO("Timer created at timepoint: 0 ms"); + + timer_executor_.ExecuteAfter(350ms, [this, logger = core_.GetLogger()]() { + timer_->Reset(); + AIMRT_HL_INFO(logger, "Timer reset at timepoint: 350 ms"); + }); + + timer_executor_.ExecuteAfter(600ms, [this, logger = core_.GetLogger()]() { + timer_->Reset(); + AIMRT_HL_INFO(logger, "Timer reset at timepoint: 600 ms"); + }); + + return true; +} + +void TimerModule::Shutdown() { + timer_->Cancel(); + timer_->SyncWait(); +} + +} // namespace aimrt::examples::cpp::executor::timer_module diff --git a/src/examples/cpp/executor/module/timer_module/timer_module.h b/src/examples/cpp/executor/module/timer_module/timer_module.h new file mode 100644 index 000000000..3b70cd4ae --- /dev/null +++ b/src/examples/cpp/executor/module/timer_module/timer_module.h @@ -0,0 +1,36 @@ +// Copyright (c) 2024 The AimRT Authors. +// AimRT is licensed under Mulan PSL v2. + +#pragma once + +#include "aimrt_module_cpp_interface/executor/executor.h" +#include "aimrt_module_cpp_interface/executor/timer.h" +#include "aimrt_module_cpp_interface/module_base.h" + +namespace aimrt::examples::cpp::executor::timer_module { + +class TimerModule : public aimrt::ModuleBase { + public: + TimerModule() = default; + ~TimerModule() override = default; + + [[nodiscard]] ModuleInfo Info() const override { + return ModuleInfo{.name = "TimerModule"}; + } + + bool Initialize(aimrt::CoreRef core) override; + + bool Start() override; + + void Shutdown() override; + + private: + auto GetLogger() { return core_.GetLogger(); } + + private: + aimrt::CoreRef core_; + + aimrt::executor::ExecutorRef timer_executor_; + std::shared_ptr timer_; +}; +} // namespace aimrt::examples::cpp::executor::timer_module diff --git a/src/examples/cpp/executor/pkg/executor_pkg/CMakeLists.txt b/src/examples/cpp/executor/pkg/executor_pkg/CMakeLists.txt index d2ff0596b..c2ede955f 100644 --- a/src/examples/cpp/executor/pkg/executor_pkg/CMakeLists.txt +++ b/src/examples/cpp/executor/pkg/executor_pkg/CMakeLists.txt @@ -29,7 +29,8 @@ target_link_libraries( ${CUR_SUPERIOR_NAMESPACE}::executor_module ${CUR_SUPERIOR_NAMESPACE}::executor_co_module ${CUR_SUPERIOR_NAMESPACE}::executor_co_loop_module - ${CUR_SUPERIOR_NAMESPACE}::real_time_module) + ${CUR_SUPERIOR_NAMESPACE}::real_time_module + ${CUR_SUPERIOR_NAMESPACE}::timer_module) # Set misc of target set_target_properties(${CUR_TARGET_NAME} PROPERTIES OUTPUT_NAME ${CUR_DIR}) diff --git a/src/examples/cpp/executor/pkg/executor_pkg/pkg_main.cc b/src/examples/cpp/executor/pkg/executor_pkg/pkg_main.cc index fb5002fbe..5012e6ad8 100644 --- a/src/examples/cpp/executor/pkg/executor_pkg/pkg_main.cc +++ b/src/examples/cpp/executor/pkg/executor_pkg/pkg_main.cc @@ -6,6 +6,7 @@ #include "executor_co_module/executor_co_module.h" #include "executor_module/executor_module.h" #include "real_time_module/real_time_module.h" +#include "timer_module/timer_module.h" using namespace aimrt::examples::cpp::executor; @@ -13,6 +14,7 @@ static std::tuple> aimrt_m {"ExecutorModule", []() -> aimrt::ModuleBase* { return new executor_module::ExecutorModule(); }}, {"ExecutorCoModule", []() -> aimrt::ModuleBase* { return new executor_co_module::ExecutorCoModule(); }}, {"ExecutorCoLoopModule", []() -> aimrt::ModuleBase* { return new executor_co_loop_module::ExecutorCoLoopModule(); }}, - {"RealTimeModule", []() -> aimrt::ModuleBase* { return new real_time_module::RealTimeModule(); }}}; + {"RealTimeModule", []() -> aimrt::ModuleBase* { return new real_time_module::RealTimeModule(); }}, + {"TimerModule", []() -> aimrt::ModuleBase* { return new timer_module::TimerModule(); }}}; AIMRT_PKG_MAIN(aimrt_module_register_array) diff --git a/src/interface/aimrt_module_cpp_interface/executor/timer.h b/src/interface/aimrt_module_cpp_interface/executor/timer.h new file mode 100644 index 000000000..9dce6237e --- /dev/null +++ b/src/interface/aimrt_module_cpp_interface/executor/timer.h @@ -0,0 +1,148 @@ +// Copyright (c) 2024 The AimRT Authors. +// AimRT is licensed under Mulan PSL v2. + +#pragma once + +#include +#include + +#include "aimrt_module_cpp_interface/executor/executor.h" +#include "util/light_signal.h" +#include "util/same_arg_trait.h" + +namespace aimrt::executor { + +class TimerBase { + public: + TimerBase(ExecutorRef executor, std::chrono::nanoseconds period) + : executor_(executor), period_(period) {} + + virtual ~TimerBase() = default; + + TimerBase(const TimerBase&) = delete; + TimerBase& operator=(const TimerBase&) = delete; + + virtual void Reset() = 0; + + virtual void ExecuteTask() = 0; + + virtual void SyncWait() = 0; + + void Cancel() { cancelled_ = true; } + + [[nodiscard]] bool IsCancelled() const { return cancelled_; } + + [[nodiscard]] std::chrono::nanoseconds Period() const { return period_; } + + [[nodiscard]] std::chrono::system_clock::time_point NextCallTime() const { + return next_call_time_; + } + + [[nodiscard]] std::chrono::nanoseconds TimeUntilNextCall() const { + return next_call_time_ - executor_.Now(); + } + + [[nodiscard]] ExecutorRef Executor() const { return executor_; } + + protected: + ExecutorRef executor_; + std::chrono::nanoseconds period_; + std::chrono::system_clock::time_point next_call_time_; + bool cancelled_ = false; +}; + +template + requires(common::util::SameArguments> || + common::util::SameArguments> || + common::util::SameArguments>) +class Timer : public TimerBase { + public: + Timer(ExecutorRef executor, std::chrono::nanoseconds period, TaskType&& task, bool auto_start = true) + : TimerBase(executor, period), task_(std::forward(task)) { + if (auto_start) { + Reset(); + } else { + Cancel(); + } + } + + ~Timer() { Cancel(); } + + Timer(const Timer&) = delete; + Timer& operator=(const Timer&) = delete; + + void Reset() override { + cancelled_ = false; + next_call_time_ = std::chrono::time_point_cast( + executor_.Now() + period_); + ExecuteLoop(); + } + + void ExecuteTask() override { + if constexpr (std::is_invocable_v) { + task_(); + } else { + task_(*this); + } + } + + void SyncWait() override { + signal_.Wait(); + signal_.Reset(); + } + + [[nodiscard]] const TaskType& Task() const { return task_; } + + private: + void ExecuteLoop() { + executor_.ExecuteAt(next_call_time_, [this, planned_time = next_call_time_]() { + if (IsCancelled()) { + if (planned_time == next_call_time_) { + signal_.Notify(); + } + return; + } + + // Skip current execution if timer was reset or restarted + if (planned_time != next_call_time_) { + return; + } + + ExecuteTask(); + + auto now = executor_.Now(); + next_call_time_ = std::chrono::time_point_cast( + next_call_time_ + period_); + + // If now is ahead of the next call time, skip some times. + if (next_call_time_ < now) { + if (period_ == std::chrono::nanoseconds::zero()) { + next_call_time_ = now; + } else { + auto now_ahead = now - next_call_time_; + auto skip_count = 1 + ((now_ahead - std::chrono::nanoseconds(1)) / period_); + next_call_time_ = std::chrono::time_point_cast( + next_call_time_ + (skip_count * period_)); + } + } + + ExecuteLoop(); + }); + } + + private: + TaskType task_; + + common::util::LightSignal signal_; +}; + +template +std::shared_ptr CreateTimer(ExecutorRef executor, std::chrono::nanoseconds period, + TaskType&& task, bool auto_start = true) { + AIMRT_ASSERT(executor, "Executor is null."); + AIMRT_ASSERT(executor.SupportTimerSchedule(), "Executor does not support timer scheduling."); + AIMRT_ASSERT(period >= std::chrono::nanoseconds::zero(), "Timer period must not be negative."); + return std::make_shared>(executor, period, std::forward(task), auto_start); +} + +} // namespace aimrt::executor