From d3b88edf4e7b7b86bbb3e189c19a553b378e533e Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Tue, 10 Mar 2026 21:29:07 +0500 Subject: [PATCH 01/20] add tasks --- aether/tasks/details/generic_task.h | 54 +++++++ aether/tasks/details/manual_task_scheduler.h | 144 +++++++++++++++++ aether/tasks/details/task.h | 44 ++++++ aether/tasks/details/task_manager.h | 106 +++++++++++++ aether/tasks/details/task_queues.h | 157 +++++++++++++++++++ aether/tasks/manual_task_scheduler.h | 25 +++ 6 files changed, 530 insertions(+) create mode 100644 aether/tasks/details/generic_task.h create mode 100644 aether/tasks/details/manual_task_scheduler.h create mode 100644 aether/tasks/details/task.h create mode 100644 aether/tasks/details/task_manager.h create mode 100644 aether/tasks/details/task_queues.h create mode 100644 aether/tasks/manual_task_scheduler.h diff --git a/aether/tasks/details/generic_task.h b/aether/tasks/details/generic_task.h new file mode 100644 index 00000000..43f9e040 --- /dev/null +++ b/aether/tasks/details/generic_task.h @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_TASKS_DETAILS_GENERIC_TASK_H_ +#define AETHER_TASKS_DETAILS_GENERIC_TASK_H_ + +#include +#include +#include + +#include "aether/tasks/details/task.h" + +namespace ae { +template + requires(std::invocable) +class GenericTask : public ITask { + public: + explicit constexpr GenericTask(F&& f) : f_{std::move(f)} {} + + void Invoke() override { std::invoke(f_); } + + private: + F f_; +}; + +template + requires(std::invocable) +class GenericDelayedTask : public IDelayedTask { + public: + explicit constexpr GenericDelayedTask(F&& f, TimePoint tp) + : IDelayedTask{tp}, f_{std::move(f)} {} + + void Invoke() override { std::invoke(f_); } + + private: + F f_; +}; + +} // namespace ae + +#endif // AETHER_TASKS_DETAILS_GENERIC_TASK_H_ diff --git a/aether/tasks/details/manual_task_scheduler.h b/aether/tasks/details/manual_task_scheduler.h new file mode 100644 index 00000000..680a528c --- /dev/null +++ b/aether/tasks/details/manual_task_scheduler.h @@ -0,0 +1,144 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_TASKS_DETAILS_MANUAL_TASK_SCHEDULER_H_ +#define AETHER_TASKS_DETAILS_MANUAL_TASK_SCHEDULER_H_ + +#include +#include +#include +#include +#include + +#include "aether/clock.h" + +#include "aether/tasks/details/task_manager.h" + +namespace ae { +template +class ManualTaskScheduler { + using task_manager = TaskManager; + using regular_list_t = typename std::decay_t< + decltype(std::declval().regular())>::list; + using delayed_list_t = typename std::decay_t< + decltype(std::declval().delayed())>::list; + + public: + ManualTaskScheduler() = default; + + template + void Task(F&& f) { + AddSafe([&]() { return task_manager_.Task(std::forward(f)); }); + } + + template + void DelayedTask(F&& f, Duration dur) { + AddSafe( + [&]() { return task_manager_.DelayedTask(std::forward(f), dur); }); + } + template + void DelayedTask(F&& f, TimePoint tp) { + AddSafe( + [&]() { return task_manager_.DelayedTask(std::forward(f), tp); }); + } + + /** + * \brief Call Update on a loop with current time. + * \param current_time - current time point is used to check if delayed tasks + * expired + * \return the recommended time point for next update call. + */ + TimePoint Update(TimePoint current_time = Now()) { + auto lock = std::unique_lock{lock_}; + trigger_ = false; + CheckOverflows(); + + // run regular tasks + task_manager_.regular().StealTasks(reg_list_); + UpdateTasks(lock, reg_list_, task_manager_.regular()); + + // run delaed tasks + task_manager_.delayed().StealTasks(current_time, delay_list_); + UpdateTasks(lock, delay_list_, task_manager_.delayed()); + + // return amount of time for next update + if (task_manager_.delayed().size() != 0) { + return task_manager_.delayed().back()->expire_at; + } + return TimePoint::max(); + } + + /** + * \brief Wait until wake up time or while the new task is added. + * The Update method should be called after WaitUntil returns. + * \param wake_up_time - maximum time to wait. + */ + void WaitUntil(TimePoint wake_up_time) { + auto lock = std::unique_lock{lock_}; + if (trigger_.exchange(false)) { + return; + } + cv_.wait_until(lock, wake_up_time, [this]() { return trigger_.load(); }); + trigger_ = false; + } + + std::size_t overflow_counter() const { return overflow_counter_; } + + private: + template + void AddSafe(F&& f) { + auto lock = std::scoped_lock{lock_}; + if (!std::invoke(std::forward(f))) { + overflow_counter_++; + } + trigger_ = true; + cv_.notify_one(); + } + + template + void UpdateTasks(std::unique_lock& lock, StealList& tasks, + List& list) { + lock.unlock(); + for (auto* t : tasks) { + t->Invoke(); + } + lock.lock(); + list.Free(tasks); + tasks.clear(); + } + + void CheckOverflows() { +#ifndef NDEBUG + if (overflow_counter_ != 0) { + std::cerr << "ManualTaskScheduler: got overflow: " << overflow_counter_ + << '\n'; + overflow_counter_ = 0; + } +#endif + } + + task_manager task_manager_; + regular_list_t reg_list_; + delayed_list_t delay_list_; + + std::mutex lock_; + std::atomic_bool trigger_{false}; + std::condition_variable cv_; + std::size_t overflow_counter_{}; +}; +} // namespace ae + +#endif // AETHER_TASKS_DETAILS_MANUAL_TASK_SCHEDULER_H_ diff --git a/aether/tasks/details/task.h b/aether/tasks/details/task.h new file mode 100644 index 00000000..dd3d49dc --- /dev/null +++ b/aether/tasks/details/task.h @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_TASKS_DETAILS_TASK_H_ +#define AETHER_TASKS_DETAILS_TASK_H_ + +#include "aether/clock.h" + +namespace ae { +/** + * \brief A general task what can only invoke. + */ +class ITask { + public: + virtual ~ITask() = default; + + virtual void Invoke() = 0; +}; + +/** + * \brief A delayed task invocable only once. + */ +class IDelayedTask : public ITask { + public: + explicit IDelayedTask(TimePoint exp_at) : expire_at{exp_at} {} + + TimePoint expire_at; +}; + +} // namespace ae +#endif // AETHER_TASKS_DETAILS_TASK_H_ diff --git a/aether/tasks/details/task_manager.h b/aether/tasks/details/task_manager.h new file mode 100644 index 00000000..ceba63b2 --- /dev/null +++ b/aether/tasks/details/task_manager.h @@ -0,0 +1,106 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_TASKS_DETAILS_TASK_MANAGER_H_ +#define AETHER_TASKS_DETAILS_TASK_MANAGER_H_ + +#include +#include + +#include "aether/clock.h" +#include "aether/tasks/details/task_queues.h" +#include "aether/tasks/details/generic_task.h" + +#include "third_party/etl/include/etl/generic_pool.h" + +namespace ae { +namespace task_manager_internal { +static constexpr std::size_t kDefaultTaskSize = + (6 * sizeof(void*)) + std::max(sizeof(ITask), sizeof(IDelayedTask)); +static constexpr std::size_t kDefaultTaskAlign = + std::max(alignof(ITask), alignof(IDelayedTask)); +} // namespace task_manager_internal + +template +struct TaskManagerConf { + static constexpr std::size_t capacity = Capacity; + static constexpr std::size_t element_size = ElementSize; + static constexpr std::size_t element_align = ElementAlign; +}; + +template +class TaskManager { + static constexpr std::size_t capacity = TaskManagerConf::capacity; + static constexpr std::size_t element_size = TaskManagerConf::element_size; + static constexpr std::size_t element_align = TaskManagerConf::element_align; + + using task_pool = etl::generic_pool; + using regular_task_list = TaskQueue; + using delayd_task_list = DelayedTaskQueue; + + public: + TaskManager() + : regular_task_list_{task_pool_}, delayd_task_list_{task_pool_} {} + + /** + * \brief Add regular task + */ + template + requires(std::invocable) + bool Task(F&& f) { + return Emplace>>(regular_task_list_, + std::forward(f)); + } + + /** + * \brief Add delayed task + */ + template + requires(std::invocable) + bool DelayedTask(F&& f, Duration dur) { + return Emplace>>( + delayd_task_list_, std::forward(f), ae::Now() + dur); + } + template + requires(std::invocable) + bool DelayedTask(F&& f, TimePoint tp) { + return Emplace>>(delayd_task_list_, + std::forward(f), tp); + } + + regular_task_list& regular() { return regular_task_list_; } + delayd_task_list& delayed() { return delayd_task_list_; } + + private: + template + bool Emplace(List& list, Args&&... args) { + if (task_pool_.available() == 0) { + return false; + } + auto* p = task_pool_.template create(std::forward(args)...); + assert(p != nullptr); + return list.Add(p); + } + + task_pool task_pool_; + regular_task_list regular_task_list_; + delayd_task_list delayd_task_list_; +}; +} // namespace ae + +#endif // AETHER_TASKS_DETAILS_TASK_MANAGER_H_ diff --git a/aether/tasks/details/task_queues.h b/aether/tasks/details/task_queues.h new file mode 100644 index 00000000..07f1c313 --- /dev/null +++ b/aether/tasks/details/task_queues.h @@ -0,0 +1,157 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_TASKS_DETAILS_TASK_QUEUE_H_ +#define AETHER_TASKS_DETAILS_TASK_QUEUE_H_ + +#include +#include +#include + +#include "aether/tasks/details/task.h" + +#include "aether/warning_disable.h" +DISABLE_WARNING_PUSH() +IGNORE_IMPLICIT_CONVERSION() +#include "third_party/etl/include/etl/vector.h" +DISABLE_WARNING_POP() + +namespace ae { +template +class TaskQueueBase { + public: + static constexpr std::size_t kCapacity = Capacity; + template + using list_container = etl::vector; + using list = list_container; + + explicit TaskQueueBase(Pool& pool) : pool_{&pool} {} + ~TaskQueueBase() { Free(list_); } + + /** + * \brief Free elements. + */ + template + void Free(list_container& elements) { + for (auto* e : elements) { + pool_->template destroy(e); + } + } + + std::size_t size() const { return list_.size(); } + Interface* back() const { return list_.back(); } + Interface* front() const { return list_.front(); } + + protected: + Pool* pool_; + list list_; +}; + +template +class TaskQueue : TaskQueueBase { + public: + using base = TaskQueueBase; + static constexpr std::size_t kCapacity = base::kCapacity; + template + using list_container = typename base::template list_container; + using list = typename base::list; + + using base::base; + + using base::back; + using base::front; + using base::size; + + using base::Free; + + bool Add(ITask* p) { + if (base::list_.size() == base::list_.max_size()) { + return false; + } + base::list_.emplace_back(p); + return true; + } + + /** + * \brief Steal elements. + * Stealled elements should be freed with Free function \see Free + */ + template + void StealTasks(list_container& to) { + auto start = std::begin(base::list_); + auto end = (base::list_.size() > max_count) ? start + max_count + : std::end(base::list_); + to.insert(to.end(), start, end); + base::list_.erase(start, end); + } +}; + +template +class DelayedTaskQueue : TaskQueueBase { + public: + using base = TaskQueueBase; + static constexpr std::size_t kCapacity = base::kCapacity; + template + using list_container = typename base::template list_container; + using list = typename base::list; + + using base::base; + + using base::back; + using base::front; + using base::size; + + using base::Free; + + bool Add(IDelayedTask* p) { + if (base::list_.size() == base::list_.max_size()) { + return false; + } + // keep list sorted by expire_at + auto it = std::find_if( + std::rbegin(base::list_), std::rend(base::list_), + [&](IDelayedTask const* e) { return p->expire_at < e->expire_at; }); + + base::list_.emplace(it.base(), p); + return true; + } + + /** + * \brief Steal elements expired before expiratrion_time. + * Stealled elements should be freed with Free function \see Free + */ + template + void StealTasks(TimePoint expiration_time, list_container& to) { + auto it = std::rbegin(base::list_); + std::size_t count = 0; + for (; it != std::rend(base::list_); ++it) { + if (((*it)->expire_at <= expiration_time) && (++count <= max_count)) { + continue; + } + break; + } + if (count == 0) { + return; + } + + auto start = it.base(); + to.insert(to.end(), start, std::end(base::list_)); + base::list_.erase(start, std::end(base::list_)); + } +}; +} // namespace ae + +#endif // AETHER_TASKS_DETAILS_TASK_QUEUE_H_ diff --git a/aether/tasks/manual_task_scheduler.h b/aether/tasks/manual_task_scheduler.h new file mode 100644 index 00000000..6253efc4 --- /dev/null +++ b/aether/tasks/manual_task_scheduler.h @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_TASKS_MANUAL_TASK_SCHEDULER_H_ +#define AETHER_TASKS_MANUAL_TASK_SCHEDULER_H_ +// IWYU pragma: begin_exports +#include "aether/tasks/details/task.h" +#include "aether/tasks/details/task_manager.h" +#include "aether/tasks/details/manual_task_scheduler.h" +// IWYU pragma: end_exports + +#endif // AETHER_TASKS_MANUAL_TASK_SCHEDULER_H_ From a5c14d97279cbe1c22b5e6b1e4ce49559d07d183 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Tue, 10 Mar 2026 21:29:11 +0500 Subject: [PATCH 02/20] add test tasks --- tests/CMakeLists.txt | 1 + tests/test-tasks/CMakeLists.txt | 39 ++++ tests/test-tasks/main.cpp | 32 +++ .../test-tasks/test-manual-task-scheduler.cpp | 145 ++++++++++++++ tests/test-tasks/test-task-manager.cpp | 96 +++++++++ tests/test-tasks/test-task-queues.cpp | 184 ++++++++++++++++++ 6 files changed, 497 insertions(+) create mode 100644 tests/test-tasks/CMakeLists.txt create mode 100644 tests/test-tasks/main.cpp create mode 100644 tests/test-tasks/test-manual-task-scheduler.cpp create mode 100644 tests/test-tasks/test-task-manager.cpp create mode 100644 tests/test-tasks/test-task-queues.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 837450ab..c12fd233 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -63,5 +63,6 @@ add_subdirectory(test-format) add_subdirectory(test-reflect) add_subdirectory(test-domain-storage) add_subdirectory(test-serial-port) +add_subdirectory(test-tasks) add_subdirectory(third_party_tests) diff --git a/tests/test-tasks/CMakeLists.txt b/tests/test-tasks/CMakeLists.txt new file mode 100644 index 00000000..ce46b03f --- /dev/null +++ b/tests/test-tasks/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright 2026 Aethernet Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required( VERSION 3.16 ) + +list(APPEND test_srcs + main.cpp + test-task-queues.cpp + test-task-manager.cpp + test-manual-task-scheduler.cpp +) + +if(NOT CM_PLATFORM) + project(test-tasks LANGUAGES CXX) + + add_executable(${PROJECT_NAME}) + target_sources(${PROJECT_NAME} PRIVATE ${test_srcs}) + # for aether + target_include_directories(${PROJECT_NAME} PRIVATE ${ROOT_DIR}) + target_link_libraries(${PROJECT_NAME} PRIVATE unity) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(${PROJECT_NAME} PUBLIC /Zc:preprocessor) + endif() + + add_test(NAME ${PROJECT_NAME} COMMAND $) +else() + message(WARNING "Not implemented for ${CM_PLATFORM}") +endif() diff --git a/tests/test-tasks/main.cpp b/tests/test-tasks/main.cpp new file mode 100644 index 00000000..821aab71 --- /dev/null +++ b/tests/test-tasks/main.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +void setUp() {} +void tearDown() {} + +extern int test_task_queues(); +extern int test_task_manager(); +extern int test_manual_task_scheduler(); + +int main() { + int res{}; + res += test_task_queues(); + res += test_task_manager(); + res += test_manual_task_scheduler(); + return res; +} diff --git a/tests/test-tasks/test-manual-task-scheduler.cpp b/tests/test-tasks/test-manual-task-scheduler.cpp new file mode 100644 index 00000000..a64df97f --- /dev/null +++ b/tests/test-tasks/test-manual-task-scheduler.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "aether/tasks/details/manual_task_scheduler.h" + +namespace ae::test_manual_task_scheduler { +void test_ManualScheduler() { + static constexpr auto kCount = 10; + auto task_sched = ManualTaskScheduler>{}; + std::array invoked{}; + + for (auto i = 0; i < kCount; ++i) { + task_sched.Task([&, i]() { invoked[i] = true; }); + } + auto tp = task_sched.Update(); + // returned tp must be a max time + TEST_ASSERT_EQUAL(TimePoint::max().time_since_epoch().count(), + tp.time_since_epoch().count()); + + // check if all invoked + for (auto i : invoked) { + TEST_ASSERT_TRUE(i); + } + + // add delayed tasks + for (auto i = 0; i < kCount; ++i) { + task_sched.DelayedTask([&, i]() { invoked[i] = false; }, 1s); + } + + auto epoch = Now(); + auto epoch_end = epoch + 10s; + std::size_t count = 0; + while (epoch < epoch_end) { + count++; + auto tp0 = task_sched.Update(epoch += 1s); + if (tp != TimePoint::max()) { + // check if tp is later than current epoch + TEST_ASSERT_GREATER_OR_EQUAL(tp.time_since_epoch().count(), + epoch.time_since_epoch().count()); + } + } + TEST_ASSERT_EQUAL(kCount, count); + // test all invoked + for (auto i : invoked) { + TEST_ASSERT_FALSE(i); + } +} + +void test_Multithread() { + static constexpr auto kCount = 1000; + static constexpr auto kThreadCount = 4; + static constexpr auto kTarget = kCount; + auto task_sched = ManualTaskScheduler>{}; + + std::array workers; + std::array work_counters{}; + for (int i = 0; i < kThreadCount; ++i) { + workers[i] = std::thread{[&, i]() { + while (work_counters[i] < kTarget) { + task_sched.Task([&, i]() { work_counters[i]++; }); + std::this_thread::sleep_for(100us); + } + }}; + } + + auto updater = std::thread{[&]() { + bool do_work = true; + while (do_work) { + // printf("update\n"); + task_sched.Update(); + + // check if all finished + do_work = false; + for (auto wc : work_counters) { + if (wc < kTarget) { + do_work = true; + break; + } + } + std::this_thread::sleep_for(100us); + } + }}; + + updater.join(); + for (auto& w : workers) { + if (w.joinable()) { + w.join(); + } + } +} + +void test_DelayedTiming() { + static constexpr auto kCount = 10; + auto task_sched = ManualTaskScheduler>{}; + + auto before = Now(); + bool invoked = false; + task_sched.DelayedTask( + [&]() { + // check if invoked after 100ms + TEST_ASSERT_GREATER_OR_EQUAL( + (100ms).count(), + std::chrono::duration_cast(Now() - + before) + .count()); + invoked = true; + }, + 100ms); + auto tp = task_sched.Update(); + // check if wait time is 100ms + TEST_ASSERT_GREATER_OR_EQUAL( + (100ms).count(), + std::chrono::duration_cast(tp - before) + .count()); + task_sched.WaitUntil(tp); + task_sched.Update(); + TEST_ASSERT_TRUE(invoked); +} + +} // namespace ae::test_manual_task_scheduler + +int test_manual_task_scheduler() { + UNITY_BEGIN(); + RUN_TEST(ae::test_manual_task_scheduler::test_ManualScheduler); + RUN_TEST(ae::test_manual_task_scheduler::test_Multithread); + RUN_TEST(ae::test_manual_task_scheduler::test_DelayedTiming); + return UNITY_END(); +} diff --git a/tests/test-tasks/test-task-manager.cpp b/tests/test-tasks/test-task-manager.cpp new file mode 100644 index 00000000..135e37d4 --- /dev/null +++ b/tests/test-tasks/test-task-manager.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "aether/tasks/details/task_manager.h" + +namespace ae::test_task_manager { + +void test_TaskManagerAddRegular() { + static constexpr auto kCount = 10; + + auto task_manager = TaskManager>{}; + + std::array invoked{}; + for (auto i = 0; i < kCount; i++) { + auto res = task_manager.Task([&, i]() { invoked[i] = true; }); + TEST_ASSERT_TRUE(res); + } + auto res = task_manager.Task([&, i{0}]() { invoked[i] = true; }); + TEST_ASSERT_FALSE(res); + + auto tasks = std::decay_t::list{}; + task_manager.regular().StealTasks(tasks); + TEST_ASSERT_EQUAL(kCount, tasks.size()); + for (auto* t : tasks) { + t->Invoke(); + } + for (auto i : invoked) { + TEST_ASSERT_TRUE(i); + } + task_manager.regular().Free(tasks); +} + +void test_TaskManagerAddDelayed() { + static constexpr auto kCount = 10; + + auto task_manager = TaskManager>{}; + + std::array invoked{}; + auto epoch = Now(); + for (auto i = 0; i < kCount; i++) { + auto res = task_manager.DelayedTask([&, i]() { invoked[i] = true; }, + epoch + i * 1s); + TEST_ASSERT_TRUE(res); + } + + // steal only half of tasks + auto tasks = std::decay_t::list{}; + task_manager.delayed().StealTasks(epoch + 4s, tasks); + TEST_ASSERT_EQUAL(5, tasks.size()); + for (auto* t : tasks) { + t->Invoke(); + } + for (std::size_t i = 0; i < tasks.size(); i++) { + TEST_ASSERT_TRUE(invoked[i]); + } + task_manager.delayed().Free(tasks); + + // add one task by duration + task_manager.DelayedTask([&]() { invoked[0] = false; }, 1s); + + // get all expired tasks + auto tasks_rest = std::decay_t::list{}; + task_manager.delayed().StealTasks(epoch + 10s, tasks_rest); + TEST_ASSERT_EQUAL(6, tasks_rest.size()); + for (auto* t : tasks_rest) { + t->Invoke(); + } + TEST_ASSERT_FALSE(invoked[0]); + + task_manager.delayed().Free(tasks_rest); +} + +} // namespace ae::test_task_manager + +int test_task_manager() { + UNITY_BEGIN(); + RUN_TEST(ae::test_task_manager::test_TaskManagerAddRegular); + RUN_TEST(ae::test_task_manager::test_TaskManagerAddDelayed); + return UNITY_END(); +} diff --git a/tests/test-tasks/test-task-queues.cpp b/tests/test-tasks/test-task-queues.cpp new file mode 100644 index 00000000..3eb88986 --- /dev/null +++ b/tests/test-tasks/test-task-queues.cpp @@ -0,0 +1,184 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "aether/tasks/details/task_queues.h" + +#include "third_party/etl/include/etl/generic_pool.h" + +namespace ae::test_task_queues { +struct RegTask : public ITask { + static inline std::size_t delete_count = 0; + explicit RegTask(int id) : task_id{id} {} + ~RegTask() override { delete_count++; } + void Invoke() override {} + int task_id; +}; + +void test_AddRegularTasks() { + static constexpr auto kCount = 10; + + auto pool = etl::generic_pool{}; + auto task_queue = TaskQueue{pool}; + + auto* r1 = pool.create(1); + assert(r1 != nullptr); + auto res1 = task_queue.Add(r1); + TEST_ASSERT_TRUE(res1); + for (int i = 1; i < kCount; i++) { + auto* ri = pool.create(i); + auto resi = task_queue.Add(ri); + TEST_ASSERT_TRUE(resi); + } + TEST_ASSERT_EQUAL(10, task_queue.size()); + auto rlast = RegTask{kCount + 1}; + auto reslast = task_queue.Add(&rlast); + TEST_ASSERT_FALSE(reslast); +} + +void test_StealRegularTasks() { + static constexpr auto kCount = 10; + RegTask::delete_count = 0; + + auto pool = etl::generic_pool{}; + auto task_queue = TaskQueue{pool}; + + for (int i = 0; i < kCount; i++) { + auto* ri = pool.create(i); + auto resi = task_queue.Add(ri); + TEST_ASSERT_TRUE(resi); + } + + TEST_ASSERT_EQUAL(10, task_queue.size()); + auto one_task = decltype(task_queue)::list_container<1>{}; + task_queue.StealTasks(one_task); + TEST_ASSERT_EQUAL(1, one_task.size()); + TEST_ASSERT_EQUAL(0, static_cast(one_task[0])->task_id); + TEST_ASSERT_EQUAL(9, task_queue.size()); + // free one_task + task_queue.Free(one_task); + TEST_ASSERT_EQUAL(1, RegTask::delete_count); + + auto rest_tasks = decltype(task_queue)::list{}; + task_queue.StealTasks(rest_tasks); + TEST_ASSERT_EQUAL(9, rest_tasks.size()); + for (int i = 1; i < kCount; ++i) { + TEST_ASSERT_EQUAL(i, static_cast(rest_tasks[i - 1])->task_id); + } + // free rest_tasks + task_queue.Free(rest_tasks); + TEST_ASSERT_EQUAL(kCount, RegTask::delete_count); +} + +struct DelayedTask : public IDelayedTask { + static inline auto delete_count = 0; + explicit DelayedTask(int id, TimePoint et) : IDelayedTask{et}, task_id{id} {} + ~DelayedTask() override { delete_count++; } + void Invoke() override {} + int task_id; +}; + +void test_AddDelayedTask() { + static constexpr auto kCount = 10; + + auto pool = + etl::generic_pool{}; + auto delayed_task_queue = DelayedTaskQueue{pool}; + + auto* r0 = pool.create(0, TimePoint{}); + auto res = delayed_task_queue.Add(r0); + TEST_ASSERT_TRUE(res); + auto epoch = ae::Now(); + // add tasks but in reverse order + for (int i = 1; i < kCount; ++i) { + auto* ri = pool.create(i, epoch -= std::chrono::seconds{1}); + auto resi = delayed_task_queue.Add(ri); + TEST_ASSERT_TRUE(resi); + } + TEST_ASSERT_EQUAL(kCount, delayed_task_queue.size()); + auto rlast = DelayedTask{kCount + 1, TimePoint{}}; + auto reslast = delayed_task_queue.Add(&rlast); + TEST_ASSERT_FALSE(reslast); +} + +void test_StealDelayedTask() { + static constexpr auto kCount = 10; + DelayedTask::delete_count = 0; + + auto pool = + etl::generic_pool{}; + auto delayed_task_queue = DelayedTaskQueue{pool}; + + auto epoch = ae::Now(); + // add tasks but in reverse order + for (int i = 0; i < kCount; ++i) { + auto* ri = pool.create(i, epoch -= std::chrono::seconds{1}); + auto resi = delayed_task_queue.Add(ri); + TEST_ASSERT_TRUE(resi); + } + TEST_ASSERT_EQUAL(kCount, delayed_task_queue.size()); + + auto one_task_not_expired = decltype(delayed_task_queue)::list_container<1>{}; + delayed_task_queue.StealTasks(TimePoint::min(), one_task_not_expired); + TEST_ASSERT_EQUAL(0, one_task_not_expired.size()); + TEST_ASSERT_EQUAL(kCount, delayed_task_queue.size()); + + auto one_task_expired = decltype(delayed_task_queue)::list_container<1>{}; + delayed_task_queue.StealTasks(epoch + std::chrono::seconds{1}, + one_task_expired); + TEST_ASSERT_EQUAL(1, one_task_expired.size()); + TEST_ASSERT_EQUAL(kCount - 1, delayed_task_queue.size()); + // tasks added in reverse order, the first stealed must be the last added + TEST_ASSERT_EQUAL(9, static_cast(one_task_expired[0])->task_id); + delayed_task_queue.Free(one_task_expired); + TEST_ASSERT_EQUAL(1, DelayedTask::delete_count); + + auto expired_half = decltype(delayed_task_queue)::list{}; + delayed_task_queue.StealTasks(epoch + std::chrono::seconds{4}, expired_half); + TEST_ASSERT_EQUAL(4, expired_half.size()); + // check the order + for (std::size_t i = 0; i < expired_half.size(); ++i) { + TEST_ASSERT_EQUAL(static_cast(i + 5), + static_cast(expired_half[i])->task_id); + } + delayed_task_queue.Free(expired_half); + TEST_ASSERT_EQUAL(5, DelayedTask::delete_count); + + auto expired_all = decltype(delayed_task_queue)::list{}; + delayed_task_queue.StealTasks(epoch + std::chrono::seconds{kCount}, + expired_all); + TEST_ASSERT_EQUAL(5, expired_all.size()); + // check the order + for (std::size_t i = 0; i < expired_all.size(); ++i) { + TEST_ASSERT_EQUAL(static_cast(i), + static_cast(expired_all[i])->task_id); + } + delayed_task_queue.Free(expired_all); + TEST_ASSERT_EQUAL(10, DelayedTask::delete_count); +} + +} // namespace ae::test_task_queues + +int test_task_queues() { + UNITY_BEGIN(); + RUN_TEST(ae::test_task_queues::test_AddRegularTasks); + RUN_TEST(ae::test_task_queues::test_StealRegularTasks); + RUN_TEST(ae::test_task_queues::test_AddDelayedTask); + RUN_TEST(ae::test_task_queues::test_StealDelayedTask); + return UNITY_END(); +} From 60193a3de17859de0e15527ed5b7dc839bc8e226 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Wed, 11 Mar 2026 11:49:05 +0500 Subject: [PATCH 03/20] remove task_queue --- aether/CMakeLists.txt | 1 - aether/actions/task_queue.cpp | 44 ------------------------------- aether/actions/task_queue.h | 42 ----------------------------- aether/all.h | 1 - tests/test-actions/CMakeLists.txt | 1 - 5 files changed, 89 deletions(-) delete mode 100644 aether/actions/task_queue.cpp delete mode 100644 aether/actions/task_queue.h diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index baa251df..4b0549cb 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -109,7 +109,6 @@ list(APPEND actions_srcs "actions/action_registry.cpp" "actions/action_processor.cpp" "actions/timer_action.cpp" - "actions/task_queue.cpp" "actions/repeatable_task.cpp" ) diff --git a/aether/actions/task_queue.cpp b/aether/actions/task_queue.cpp deleted file mode 100644 index 8b1bfd97..00000000 --- a/aether/actions/task_queue.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/actions/task_queue.h" - -#include - -namespace ae { - -UpdateStatus TaskQueue::Update() { - if (tasks_.empty()) { - return {}; - } - std::vector tasks_invoke; - { - // steal tasks under the mutex - auto lock = std::lock_guard{sync_queue_}; - tasks_invoke = std::move(tasks_); - } - for (auto& t : tasks_invoke) { - t(); - } - return {}; -} - -void TaskQueue::Enqueue(Task&& task) { - auto lock = std::lock_guard{sync_queue_}; - tasks_.emplace_back(std::move(task)); -} - -} // namespace ae diff --git a/aether/actions/task_queue.h b/aether/actions/task_queue.h deleted file mode 100644 index a69b8b21..00000000 --- a/aether/actions/task_queue.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_ACTIONS_TASK_QUEUE_H_ -#define AETHER_ACTIONS_TASK_QUEUE_H_ - -#include -#include - -#include "aether/actions/action.h" -#include "aether/types/small_function.h" - -namespace ae { -class TaskQueue : public Action { - public: - using Task = SmallFunction; - - using Action::Action; - - UpdateStatus Update(); - void Enqueue(Task&& task); - - private: - std::mutex sync_queue_; - std::vector tasks_; -}; -}; // namespace ae - -#endif // AETHER_ACTIONS_TASK_QUEUE_H_ diff --git a/aether/all.h b/aether/all.h index 9a55b70d..33136609 100644 --- a/aether/all.h +++ b/aether/all.h @@ -27,7 +27,6 @@ #include "aether/actions/action_ptr.h" #include "aether/actions/action_context.h" #include "aether/actions/action_processor.h" -#include "aether/actions/task_queue.h" #include "aether/actions/timer_action.h" #include "aether/actions/notify_action.h" #include "aether/actions/repeatable_task.h" diff --git a/tests/test-actions/CMakeLists.txt b/tests/test-actions/CMakeLists.txt index c8e91a46..1bb439da 100644 --- a/tests/test-actions/CMakeLists.txt +++ b/tests/test-actions/CMakeLists.txt @@ -19,7 +19,6 @@ list(APPEND action_srcs ${ROOT_DIR}/aether/actions/action_registry.cpp ${ROOT_DIR}/aether/actions/action_trigger.cpp ${ROOT_DIR}/aether/actions/repeatable_task.cpp - ${ROOT_DIR}/aether/actions/task_queue.cpp ${ROOT_DIR}/aether/actions/timer_action.cpp ${ROOT_DIR}/aether/events/event_list.cpp ${ROOT_DIR}/aether/events/event_deleter.cpp From 1c1363c4dfed7c30086667d39285a60391db14d3 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Wed, 11 Mar 2026 11:49:21 +0500 Subject: [PATCH 04/20] add ae_context and task scheduler to aether --- aether/ae_context.h | 62 +++++++++++++++++++++++++++ aether/aether.cpp | 18 +++++++- aether/aether.h | 5 +++ aether/aether_app.h | 2 + aether/all.h | 3 +- aether/config.h | 14 ++++++ aether/tele/env/compilation_options.h | 3 ++ 7 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 aether/ae_context.h diff --git a/aether/ae_context.h b/aether/ae_context.h new file mode 100644 index 00000000..d42f91f4 --- /dev/null +++ b/aether/ae_context.h @@ -0,0 +1,62 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_AE_CONTEXT_H_ +#define AETHER_AE_CONTEXT_H_ + +#include + +#include "aether/config.h" + +#include "aether/tasks/manual_task_scheduler.h" + +namespace ae { +class Aether; +using TaskScheduler = ManualTaskScheduler< + TaskManagerConf>; + +struct AeCtxTable { + Aether& (*aether_getter)(void* obj); + TaskScheduler& (*scheduler_getter)(void* obj); +}; + +struct AeCtx { + void* obj; + AeCtxTable const* vtable; +}; + +template +concept AeContextual = requires(T& t) { + { t.ToAeContext() } -> std::same_as; +}; + +class AeContext { + public: + template + constexpr AeContext(T& obj) // NOLINT(*explicit-constructor) + : ctx_{obj.ToAeContext()} {} + + Aether& aether() const { return ctx_.vtable->aether_getter(ctx_.obj); } + TaskScheduler& scheduler() const { + return ctx_.vtable->scheduler_getter(ctx_.obj); + } + + private: + AeCtx ctx_; +}; +} // namespace ae + +#endif // AETHER_AE_CONTEXT_H_ diff --git a/aether/aether.cpp b/aether/aether.cpp index fa164781..14e112c8 100644 --- a/aether/aether.cpp +++ b/aether/aether.cpp @@ -33,10 +33,14 @@ namespace ae { -Aether::Aether() : action_processor{make_unique()} {} +Aether::Aether() + : action_processor{make_unique()}, + task_scheduler{make_unique()} {} Aether::Aether(ObjProp prop) - : Obj{prop}, action_processor{make_unique()} { + : Obj{prop}, + action_processor{make_unique()}, + task_scheduler{make_unique()} { AE_TELE_DEBUG(AetherCreated); } @@ -50,6 +54,16 @@ Aether::operator ActionContext() const { return ActionContext{*action_processor}; } +AeCtx Aether::ToAeContext() { + static constexpr AeCtxTable ae_table{ + [](void* obj) -> Aether& { return *static_cast(obj); }, + [](void* obj) -> TaskScheduler& { + return *static_cast(obj)->task_scheduler; + }, + }; + return AeCtx{this, &ae_table}; +} + Client::ptr Aether::CreateClient(ClientConfig const& config, std::string const& client_id) { auto client = FindClient(client_id); diff --git a/aether/aether.h b/aether/aether.h index 8f8fd94d..4226ffbb 100644 --- a/aether/aether.h +++ b/aether/aether.h @@ -26,9 +26,12 @@ #include "aether/actions/action_ptr.h" #include "aether/types/client_config.h" +#include "aether/ae_context.h" #include "aether/actions/action_context.h" #include "aether/ae_actions/select_client.h" +#include "aether/tasks/manual_task_scheduler.h" + #include "aether/uap/uap.h" namespace ae { @@ -82,6 +85,7 @@ class Aether : public Obj { // User-facing API. operator ActionContext() const; + AeCtx ToAeContext(); ObjPtr CreateClient(ClientConfig const& config, std::string const& client_id); @@ -104,6 +108,7 @@ class Aether : public Obj { Obj::ptr tele_statistics; std::unique_ptr action_processor; + std::unique_ptr task_scheduler; private: ObjPtr FindClient(std::string const& client_id); diff --git a/aether/aether_app.h b/aether/aether_app.h index 29d2e078..e8ae69af 100644 --- a/aether/aether_app.h +++ b/aether/aether_app.h @@ -40,6 +40,7 @@ #include "aether/crypto.h" #include "aether/client.h" #include "aether/uap/uap.h" +#include "aether/ae_context.h" #include "aether/poller/poller.h" #include "aether/dns/dns_resolve.h" #include "aether/adapter_registry.h" @@ -233,6 +234,7 @@ class AetherApp { // Action context protocol operator ActionContext() const { return ActionContext{*aether_}; } + AeCtx ToAeContext() { return aether_->ToAeContext(); } private: AetherApp() = default; diff --git a/aether/all.h b/aether/all.h index 33136609..c29f2b6f 100644 --- a/aether/all.h +++ b/aether/all.h @@ -19,9 +19,10 @@ // IWYU pragma: begin_exports #include "aether/config.h" -#include "aether/aether_app.h" #include "aether/common.h" #include "aether/memory.h" +#include "aether/ae_context.h" +#include "aether/aether_app.h" #include "aether/actions/action.h" #include "aether/actions/action_ptr.h" diff --git a/aether/config.h b/aether/config.h index 208754bf..5bd81d91 100644 --- a/aether/config.h +++ b/aether/config.h @@ -27,6 +27,20 @@ #endif // IWYU pragma: end_exports +// task queue buffer size \see aether/tasks +#ifndef AE_TASK_MAX_COUNT +# define AE_TASK_MAX_COUNT 128 +#endif + +// task max size, this should be more than 16 bytes +#ifndef AE_TASK_MAX_SIZE +# define AE_TASK_MAX_SIZE 8 * sizeof(void*) +#endif +// task alignment +#ifndef AE_TASK_ALIGN +# define AE_TASK_ALIGN alignof(std::max_align_t) +#endif + #ifndef AE_SUPPORT_IPV4 # define AE_SUPPORT_IPV4 1 #endif // AE_SUPPORT_IPV4 diff --git a/aether/tele/env/compilation_options.h b/aether/tele/env/compilation_options.h index c461c1f4..f4e1dd89 100644 --- a/aether/tele/env/compilation_options.h +++ b/aether/tele/env/compilation_options.h @@ -78,6 +78,9 @@ static_assert(str_to_ui64("042") == 34); static_assert(str_to_ui64("b110") == 6); constexpr inline auto _compile_options_list = std::array{ + _OPTION(AE_TASK_MAX_COUNT), + _OPTION(AE_TASK_MAX_SIZE), + _OPTION(AE_TASK_ALIGN), _OPTION(AE_SUPPORT_IPV4), _OPTION(AE_SUPPORT_IPV6), _OPTION(AE_SUPPORT_UDP), From 6cd64d2bb7fe0ee536d008a3be94d2ea72f45ca6 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 16 Mar 2026 15:31:39 +0500 Subject: [PATCH 05/20] make ToAeContext const --- aether/ae_context.h | 8 ++++++-- aether/aether.cpp | 4 ++-- aether/aether.h | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/aether/ae_context.h b/aether/ae_context.h index d42f91f4..b4b55cee 100644 --- a/aether/ae_context.h +++ b/aether/ae_context.h @@ -34,19 +34,21 @@ struct AeCtxTable { }; struct AeCtx { + bool operator<=>(AeCtx const&) const = default; + void* obj; AeCtxTable const* vtable; }; template -concept AeContextual = requires(T& t) { +concept AeContextual = requires(T const& t) { { t.ToAeContext() } -> std::same_as; }; class AeContext { public: template - constexpr AeContext(T& obj) // NOLINT(*explicit-constructor) + constexpr AeContext(T const& obj) // NOLINT(*explicit-constructor) : ctx_{obj.ToAeContext()} {} Aether& aether() const { return ctx_.vtable->aether_getter(ctx_.obj); } @@ -54,6 +56,8 @@ class AeContext { return ctx_.vtable->scheduler_getter(ctx_.obj); } + bool operator<=>(AeContext const&) const = default; + private: AeCtx ctx_; }; diff --git a/aether/aether.cpp b/aether/aether.cpp index 14e112c8..d1a549e4 100644 --- a/aether/aether.cpp +++ b/aether/aether.cpp @@ -54,14 +54,14 @@ Aether::operator ActionContext() const { return ActionContext{*action_processor}; } -AeCtx Aether::ToAeContext() { +AeCtx Aether::ToAeContext() const { static constexpr AeCtxTable ae_table{ [](void* obj) -> Aether& { return *static_cast(obj); }, [](void* obj) -> TaskScheduler& { return *static_cast(obj)->task_scheduler; }, }; - return AeCtx{this, &ae_table}; + return AeCtx{const_cast(this), &ae_table}; // NOLINT(*const-cast) } Client::ptr Aether::CreateClient(ClientConfig const& config, diff --git a/aether/aether.h b/aether/aether.h index 4226ffbb..abe93f1e 100644 --- a/aether/aether.h +++ b/aether/aether.h @@ -85,7 +85,7 @@ class Aether : public Obj { // User-facing API. operator ActionContext() const; - AeCtx ToAeContext(); + AeCtx ToAeContext() const; ObjPtr CreateClient(ClientConfig const& config, std::string const& client_id); From 57eb89ab35e44b612322e9b5004f0e1d36e7f73c Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 16 Mar 2026 15:32:02 +0500 Subject: [PATCH 06/20] TEMPORARY add call to task scheduler and action processor together --- aether/aether.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aether/aether.cpp b/aether/aether.cpp index d1a549e4..dd9dba9e 100644 --- a/aether/aether.cpp +++ b/aether/aether.cpp @@ -47,7 +47,9 @@ Aether::Aether(ObjProp prop) Aether::~Aether() { AE_TELE_DEBUG(AetherDestroyed); } void Aether::Update(TimePoint current_time) { - update_time = action_processor->Update(current_time); + task_scheduler->Update(current_time); + action_processor->Update(current_time); + update_time = current_time + 100ms; } Aether::operator ActionContext() const { From 9d3019e2f808b59b5ce3b0adb721a47ee337e13a Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 16 Mar 2026 15:32:39 +0500 Subject: [PATCH 07/20] add TypeLisToTemplate_t --- aether/meta/type_list.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aether/meta/type_list.h b/aether/meta/type_list.h index 82d4dd11..c4e212ef 100644 --- a/aether/meta/type_list.h +++ b/aether/meta/type_list.h @@ -117,6 +117,9 @@ struct TypeListToTemplate> { using type = T; }; +template