feat: add timer support based on executor's ExecuteAt (#73)

* feat: add timer module integration

Integrate the new timer module into the executor package, enhancing functionality and providing more precise timing capabilities within the system.

* feat: add timer class for periodic task scheduling

Implement a Timer class that enables users to schedule recurring tasks with configurable periods. This enhances the executor functionality by allowing more precise timing control and task management within the AimRT framework.

* refactor: replace WallTimer with aimrt::executor::Timer and enhance logging

Streamline the timer module by removing the custom WallTimer implementation in favor of the built-in aimrt::executor::Timer. Improve logging to track execution intervals, providing clearer insights into task execution frequency.

* feat: enhance timer functionality and behavior

Introduce a shortened sleep duration for task execution and add cancellation capability for timers, improving responsiveness and efficiency in managing timer events.

* feat: enhance timer functionality with flexible period handling

Refine the timer by allowing a customizable reset period, improving task scheduling accuracy, and ensuring that callbacks execute only when appropriate. This update addresses potential timing issues when the timer is reset, enhancing overall reliability in task execution.

* feat: improve timer module functionality and simplify timer creation

Enhance the timer module by updating the logging to microseconds, refactoring timer creation for improved clarity, and adjusting timing intervals for better performance and maintainability.

* feat: add utility for comparing function argument types

Introduce a new utility to compare argument types of functions, enhancing type safety and flexibility in the Timer implementation. Update Timer module to integrate this utility for improved argument type checks. Additionally, adjust timer intervals for more appropriate timing behavior.

* test: add comprehensive tests for SameArguments trait functionality

Introduce a series of tests to validate the behavior of the SameArguments trait across various function types, member functions, function objects, lambda functions, and complex argument types. This ensures compatibility and correctness in argument matching, enhancing type safety and reliability in code usage.

* style: format function definitions for clarity

Improve the readability of the TimerBase class by formatting function definitions to align with standard coding style.

Expand timer functionality to support const reference callbacks, enhancing flexibility in task management.

* style: ensure newline at end of file and tidy up CMakeLists.txt

Update formatting for consistency and readability by adding a newline at the end of the file and improving the organization of the CMakeLists.txt.

* chore: update include paths for consistency

Refactor the include statement for the same_arg_trait to follow the new directory structure, promoting better organization and maintainability.

* fix: clarify comment on task execution timing

Ensure that the executor guarantees all tasks are executed after their planned time to handle timer resets more effectively.

* refactor: simplify cancellation check in timer execution

Improve code clarity by replacing direct access of a member variable with a method call for cancellation checks. This enhances maintainability and encapsulates the cancellation logic.

* refactor: enhance timer interface to support configurable period

Revise the TimerBase and Timer classes to allow starting and resetting with a specific duration. This improves flexibility, prevents duplicate callbacks on resets, and ensures more accurate callback scheduling.

* feat: improve timer functionality and logging

Enhance the timer with more precise intervals and additional logging for better tracking of execution and reset events. Adjust execution periods for improved timing accuracy and introduce dynamic start behavior for better flow control.

* refactor: adjust timer periods for improved performance

Update timer initialization and execution intervals to enhance timing accuracy and operational efficiency.

* refactor: rename callback method for clarity

Update the timer interface to use "ExecuteTask" instead of "ExecuteCallback" for improved readability and accuracy in describing the method's purpose. Ensure that task cancellation is properly handled when auto-start is disabled.

* docs: enhance timer documentation and add usage examples

Clarify the timer interface and its functionality, including creation, scheduling, and task execution. Provide practical usage examples to improve understanding and facilitate integration into projects.

* chore: update copyright information to reflect current year and new authors

Ensure compliance with licensing requirements by revising the copyright and license details in the CMake configuration.

* fix: address potential duplicate callback executions on timer reset

Remove commented FIXME notes regarding edge case of duplicate callbacks when the timer is reset to the same time point.

* fix: adjust timer intervals for improved timing accuracy

Reduce timer intervals from seconds to milliseconds to enhance responsiveness of the timer functionality. Implement a sleep in the shutdown process to ensure all timer executor tasks complete properly before shutdown.

* refactor: simplify timer restart logic

Consolidate timer actions by removing redundant cancellation and restart statements. Adjust logging to reflect the accurate restart timing, enhancing clarity and reducing unnecessary latency.

* fix: add cancellation check in timer execution loop

Ensure that the timer's execution loop properly checks for cancellation, optimizing performance and preventing unnecessary operations when the timer is no longer needed.

* refactor: update time point calculations for accuracy

Improve the handling of time point arithmetic in the Timer class to ensure precision during execution and scheduling. This revision clarifies the time calculations, preventing potential errors in scheduling tasks.

* feat: add synchronization support to Timer class

Implement SyncWait method to allow for synchronous waiting on timer tasks, enhancing task execution control and reliability.

* refactor: streamline timer execution logging

Improve timer execution logging by incrementing the execution count directly within the log statement. Ensure the timer synchronously waits during shutdown to enhance reliability.

* feat: add SyncWait method to TimerBase for safer resource cleanup

Improve timer behavior by introducing SyncWait to ensure resources are correctly released after cancellation before the next execution point. Update timer intervals for enhanced responsiveness and logging clarity.

* feat: add timer based on executor for scheduled tasks

Introduce a new feature to facilitate scheduled task execution with an executor-based timer, enhancing the flexibility and usability of the task management capabilities.

* refactor: streamline timer initialization and assertion handling

Remove inline assertions from the TimerBase constructor and relocate assertions to the CreateTimer function, enhancing clarity and maintaining execution flow. This simplifies timer initialization while ensuring that critical checks occur before timer creation.

* refactor: simplify timer task management and logging

Improve timer initialization and task execution by streamlining start and reset processes. Enhance logging clarity by aligning messages with timepoints and ensuring the timer cancels correctly after reaching execution limits.

* docs: simplify TimerBase interface documentation

Clarify method descriptions and refine behavior explanation to enhance understanding of TimerBase functionality, particularly around the Reset and Start methods.

* refactor: optimize task forwarding in Timer constructor

Improve task handling by using std::forward to perfect-forward the task parameter, ensuring efficient movement semantics and reducing potential overhead.

---------

Co-authored-by: zhangyi <zhangyi@agibot.com>
This commit is contained in:
zhangyi1357 2024-11-05 17:49:24 +08:00 committed by GitHub
parent 5b7d76d788
commit 9c53b663a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 660 additions and 4 deletions

View File

@ -10,6 +10,7 @@
- 新增了第三方库 asioruntime::core 不再引用 boost改为引用独立的 asio 库,以减轻依赖;
- 新增 aimrt_cli trans 命令,用于将 使用 aimrt record_playback 插件录制的 bag 文件转换为 ros2 的 bag 文件;
- 新增 Echo 插件,用于回显消息;
- 新增了基于执行器的定时器,方便执行定时任务;
**次要修改**
- 缩短了一些 examples 的文件路径长度;

View File

@ -384,7 +384,7 @@ class HelloWorldModule : public aimrt::ModuleBase {
aimrt::co::Task<void> 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 <typename TaskType>
std::shared_ptr<TimerBase> 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>`
`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<std::chrono::milliseconds>(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();
}
```

View File

@ -0,0 +1,39 @@
// Copyright (c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#pragma once
#include <concepts>
#include <tuple>
namespace aimrt::common::util {
template <typename T>
struct function_args;
template <typename R, typename... Args>
struct function_args<R(Args...)> {
using type = std::tuple<Args...>;
};
template <typename R, typename C, typename... Args>
struct function_args<R (C::*)(Args...)> {
using type = std::tuple<Args...>;
};
template <typename R, typename C, typename... Args>
struct function_args<R (C::*)(Args...) const> {
using type = std::tuple<Args...>;
};
template <typename F>
struct function_args {
using type = typename function_args<decltype(&F::operator())>::type;
};
template <typename F>
using function_args_t = typename function_args<F>::type;
template <typename F1, typename F2>
concept SameArguments = std::same_as<function_args_t<F1>, function_args_t<F2>>;
} // namespace aimrt::common::util

View File

@ -0,0 +1,160 @@
// Copyright (c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#include "util/same_arg_trait.h"
#include <gtest/gtest.h>
#include <functional>
#include <string>
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<decltype(func1), decltype(func2)>));
EXPECT_FALSE((SameArguments<decltype(func1), decltype(func3)>));
EXPECT_FALSE((SameArguments<decltype(func1), decltype(func4)>));
EXPECT_FALSE((SameArguments<decltype(func1), decltype(func5)>));
EXPECT_TRUE((SameArguments<decltype(func1), decltype(func6)>));
EXPECT_TRUE((SameArguments<decltype(return_int), decltype(return_double)>));
EXPECT_TRUE((SameArguments<decltype(return_int), decltype(return_string)>));
}
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<Method1, Method2>));
EXPECT_TRUE((SameArguments<Method1, Method3>));
EXPECT_TRUE((SameArguments<Method1, Method4>));
EXPECT_TRUE((SameArguments<Method1, decltype(func1)>));
using Overload1 = void (TestClass::*)(int, double);
EXPECT_TRUE((SameArguments<Overload1, Method1>));
}
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<FunctionObject1, FunctionObject2>));
EXPECT_FALSE((SameArguments<FunctionObject1, FunctionObject3>));
EXPECT_TRUE((SameArguments<FunctionObject1, decltype(func1)>));
}
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<decltype(lambda1), decltype(lambda2)>));
EXPECT_FALSE((SameArguments<decltype(lambda1), decltype(lambda3)>));
EXPECT_TRUE((SameArguments<decltype(lambda1), decltype(lambda4)>));
EXPECT_TRUE((SameArguments<decltype(lambda1), decltype(lambda_capture)>));
EXPECT_TRUE((SameArguments<decltype(lambda1), decltype(func1)>));
}
std::function<void(int, double)> std_func1;
std::function<int(int, double)> std_func2;
std::function<void(double, int)> std_func3;
TEST(SameArgTraitTest, StdFunction) {
EXPECT_TRUE((SameArguments<decltype(std_func1), decltype(std_func2)>));
EXPECT_FALSE((SameArguments<decltype(std_func1), decltype(std_func3)>));
EXPECT_TRUE((SameArguments<decltype(std_func1), decltype(func1)>));
EXPECT_TRUE((SameArguments<decltype(std_func1), decltype(lambda1)>));
}
void complex_func1(std::vector<int>, std::map<std::string, double>) {}
void complex_func2(std::vector<int>, std::map<std::string, double>) {}
void complex_func3(std::map<std::string, double>, std::vector<int>) {}
TEST(SameArgTraitTest, ComplexArgumentTypes) {
EXPECT_TRUE((SameArguments<decltype(complex_func1), decltype(complex_func2)>));
EXPECT_FALSE((SameArguments<decltype(complex_func1), decltype(complex_func3)>));
}
void ref_func1(int&, const double&) {}
void ref_func2(int&, const double&) {}
void ptr_func(int*, double*) {}
TEST(SameArgTraitTest, ReferenceAndPointerArguments) {
EXPECT_TRUE((SameArguments<decltype(ref_func1), decltype(ref_func2)>));
EXPECT_FALSE((SameArguments<decltype(ref_func1), decltype(ptr_func)>));
EXPECT_FALSE((SameArguments<decltype(ref_func1), decltype(func1)>));
}
TEST(SameArgTraitTest, CrossTypeComparisons) {
using Method1 = decltype(&TestClass::method1);
using ConstMethod = decltype(&TestClass::method2);
using VirtualMethod = decltype(&TestClass::method4);
EXPECT_TRUE((SameArguments<decltype(func1), Method1>));
EXPECT_TRUE((SameArguments<Method1, decltype(std_func1)>));
EXPECT_TRUE((SameArguments<Method1, FunctionObject1>));
EXPECT_TRUE((SameArguments<Method1, decltype(lambda1)>));
EXPECT_TRUE((SameArguments<FunctionObject1, decltype(lambda1)>));
EXPECT_TRUE((SameArguments<decltype(lambda1), FunctionObject1>));
EXPECT_TRUE((SameArguments<ConstMethod, Method1>));
EXPECT_TRUE((SameArguments<ConstMethod, decltype(std_func1)>));
EXPECT_TRUE((SameArguments<ConstMethod, FunctionObject2>));
EXPECT_TRUE((SameArguments<VirtualMethod, Method1>));
EXPECT_TRUE((SameArguments<VirtualMethod, decltype(lambda1)>));
EXPECT_TRUE((SameArguments<decltype(return_int), decltype(lambda4)>));
EXPECT_TRUE((SameArguments<decltype(return_int), decltype(std_func2)>));
EXPECT_TRUE((SameArguments<decltype(complex_func1), decltype(complex_func2)>));
EXPECT_FALSE((SameArguments<decltype(complex_func1), decltype(complex_func3)>));
EXPECT_FALSE((SameArguments<decltype(ref_func1), decltype(lambda1)>));
EXPECT_FALSE((SameArguments<decltype(ref_func1), FunctionObject1>));
EXPECT_FALSE((SameArguments<Method1, decltype(ref_func1)>));
EXPECT_FALSE((SameArguments<decltype(ref_func1), decltype(ptr_func)>));
EXPECT_FALSE((SameArguments<decltype(ptr_func), decltype(lambda1)>));
}
// NOLINTEND(readability-named-parameter, performance-unnecessary-value-param)
} // namespace
} // namespace aimrt::common::util

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,3 @@
#!/bin/bash
./aimrt_main --cfg_file_path=./cfg/examples_cpp_timer_cfg.yaml

View File

@ -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 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
# 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()

View File

@ -0,0 +1,60 @@
// Copyright (c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#include "timer_module/timer_module.h"
#include <chrono>
#include <memory>
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<std::chrono::milliseconds>(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

View File

@ -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<aimrt::executor::TimerBase> timer_;
};
} // namespace aimrt::examples::cpp::executor::timer_module

View File

@ -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})

View File

@ -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<std::string_view, std::function<aimrt::ModuleBase*()>> 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)

View File

@ -0,0 +1,148 @@
// Copyright (c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#pragma once
#include <chrono>
#include <memory>
#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 <typename TaskType>
requires(common::util::SameArguments<TaskType, std::function<void()>> ||
common::util::SameArguments<TaskType, std::function<void(TimerBase&)>> ||
common::util::SameArguments<TaskType, std::function<void(const TimerBase&)>>)
class Timer : public TimerBase {
public:
Timer(ExecutorRef executor, std::chrono::nanoseconds period, TaskType&& task, bool auto_start = true)
: TimerBase(executor, period), task_(std::forward<TaskType>(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<std::chrono::system_clock::duration>(
executor_.Now() + period_);
ExecuteLoop();
}
void ExecuteTask() override {
if constexpr (std::is_invocable_v<TaskType>) {
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<std::chrono::system_clock::duration>(
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<std::chrono::system_clock::duration>(
next_call_time_ + (skip_count * period_));
}
}
ExecuteLoop();
});
}
private:
TaskType task_;
common::util::LightSignal signal_;
};
template <typename TaskType>
std::shared_ptr<TimerBase> 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<Timer<TaskType>>(executor, period, std::forward<TaskType>(task), auto_start);
}
} // namespace aimrt::executor