From afba76ae65f62c9efce396cac8266df50d4926b8 Mon Sep 17 00:00:00 2001 From: Skull <86374920+skkuull@users.noreply.github.com> Date: Sat, 22 Mar 2025 01:46:59 +0300 Subject: [PATCH] feat(scheduler): add initial component --- src/client/component/scheduler.cpp | 185 +++++++++++++++++++++++++++++ src/client/component/scheduler.hpp | 36 ++++++ 2 files changed, 221 insertions(+) create mode 100644 src/client/component/scheduler.cpp create mode 100644 src/client/component/scheduler.hpp diff --git a/src/client/component/scheduler.cpp b/src/client/component/scheduler.cpp new file mode 100644 index 00000000..bb084335 --- /dev/null +++ b/src/client/component/scheduler.cpp @@ -0,0 +1,185 @@ +#include +#include "loader/component_loader.hpp" + +#include "scheduler.hpp" + +#include +#include +#include +#include +#include + +namespace scheduler +{ + namespace + { + struct task + { + std::function handler{}; + std::chrono::milliseconds interval{}; + std::chrono::high_resolution_clock::time_point last_call{}; + }; + + using task_list = std::vector; + + class task_pipeline + { + public: + void add(task&& task) + { + new_callbacks_.access([&task](task_list& tasks) + { + tasks.emplace_back(std::move(task)); + }); + } + + void execute() + { + callbacks_.access([&](task_list& tasks) + { + this->merge_callbacks(); + + for (auto i = tasks.begin(); i != tasks.end();) + { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - i->last_call; + + if (diff < i->interval) + { + ++i; + continue; + } + + i->last_call = now; + + const auto res = i->handler(); + if (res == cond_end) + { + i = tasks.erase(i); + } + else + { + ++i; + } + } + }); + } + + private: + utils::concurrency::container new_callbacks_; + utils::concurrency::container callbacks_; + + void merge_callbacks() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), + std::move_iterator(new_tasks.end())); + new_tasks = {}; + }); + }); + } + }; + + volatile bool kill = false; + std::thread thread; + task_pipeline pipelines[pipeline::count]; + + utils::hook::detour r_end_frame_hook; + utils::hook::detour g_run_frame_hook; + utils::hook::detour main_frame_hook; + + void execute(const pipeline type) + { + assert(type >= 0 && type < pipeline::count); + pipelines[type].execute(); + } + + void r_end_frame_stub() + { + execute(pipeline::renderer); + r_end_frame_hook.invoke(); + } + + void server_frame_stub() + { + g_run_frame_hook.invoke(); + execute(pipeline::server); + } + + void main_frame_stub() + { + main_frame_hook.invoke(); + execute(pipeline::main); + } + } + + void schedule(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + assert(type >= 0 && type < pipeline::count); + + task task; + task.handler = callback; + task.interval = delay; + task.last_call = std::chrono::high_resolution_clock::now(); + + pipelines[type].add(std::move(task)); + } + + void loop(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + schedule([callback]() + { + callback(); + return cond_continue; + }, type, delay); + } + + void once(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + schedule([callback]() + { + callback(); + return cond_end; + }, type, delay); + } + + class component final : public component_interface + { + public: + void post_start() override + { + thread = utils::thread::create_named_thread("Async Scheduler", []() + { + while (!kill) + { + execute(pipeline::async); + std::this_thread::sleep_for(10ms); + } + }); + } + + void post_unpack() override + { + r_end_frame_hook.create(0x14081BBF0, r_end_frame_stub); // s2mp hoss + //g_run_frame_hook.create(0x0, server_frame_stub); + //main_frame_hook.create(0x0, main_frame_stub); + } + + void pre_destroy() override + { + kill = true; + if (thread.joinable()) + { + thread.join(); + } + } + }; +} + +REGISTER_COMPONENT(scheduler::component) \ No newline at end of file diff --git a/src/client/component/scheduler.hpp b/src/client/component/scheduler.hpp new file mode 100644 index 00000000..68f78d54 --- /dev/null +++ b/src/client/component/scheduler.hpp @@ -0,0 +1,36 @@ +#pragma once + +namespace scheduler +{ + enum pipeline + { + // Asynchronuous pipeline, disconnected from the game + async = 0, + + // The game's rendering pipeline + renderer, + + // The game's server thread + server, + + // The game's main thread + main, + + // LUI context + lui, + + count, + }; + + static const bool cond_continue = false; + static const bool cond_end = true; + + void schedule(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void loop(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void once(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void on_game_initialized(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); +} \ No newline at end of file