#include constexpr bool COND_CONTINUE = false; constexpr bool COND_END = true; namespace Components { std::thread Scheduler::Thread; volatile bool Scheduler::Kill = false; Scheduler::TaskPipeline Scheduler::Pipelines[static_cast>(Pipeline::COUNT)]; void Scheduler::TaskPipeline::add(Task&& task) { newCallbacks_.access([&task](taskList& tasks) { tasks.emplace_back(std::move(task)); }); } void Scheduler::TaskPipeline::execute() { callbacks_.access([&](taskList& tasks) { this->mergeCallbacks(); for (auto i = tasks.begin(); i != tasks.end();) { const auto now = std::chrono::high_resolution_clock::now(); const auto diff = now - i->lastCall; if (diff < i->interval) { ++i; continue; } i->lastCall = now; const auto res = i->handler(); if (res == COND_END) { i = tasks.erase(i); } else { ++i; } } }); } void Scheduler::TaskPipeline::mergeCallbacks() { callbacks_.access([&](taskList& tasks) { newCallbacks_.access([&](taskList& new_tasks) { tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), std::move_iterator(new_tasks.end())); new_tasks = {}; }); }); } void Scheduler::Execute(Pipeline type) { assert(type < Pipeline::COUNT); const auto index = static_cast>(type); Pipelines[index].execute(); } void Scheduler::ScrPlace_EndFrame_Hk() { Utils::Hook::Call(0x4AA720)(); Execute(Pipeline::RENDERER); } void Scheduler::ServerFrame_Hk() { Utils::Hook::Call(0x471C50)(); Execute(Pipeline::SERVER); } void Scheduler::ClientFrame_Hk(const int localClientNum) { Utils::Hook::Call(0x5A8E80)(localClientNum); Execute(Pipeline::CLIENT); } void Scheduler::MainFrame_Hk() { Utils::Hook::Call(0x47DCA0)(); Execute(Pipeline::MAIN); } void Scheduler::SysSetBlockSystemHotkeys_Hk(int block) { Execute(Pipeline::QUIT); Utils::Hook::Call(0x46B370)(block); } void Scheduler::Schedule(const std::function& callback, const Pipeline type, const std::chrono::milliseconds delay) { assert(type < Pipeline::COUNT); Task task; task.handler = callback; task.interval = delay; task.lastCall = std::chrono::high_resolution_clock::now(); const auto index = static_cast>(type); Pipelines[index].add(std::move(task)); } void Scheduler::Loop(const std::function& callback, const Pipeline type, const std::chrono::milliseconds delay) { Schedule([callback] { callback(); return COND_CONTINUE; }, type, delay); } void Scheduler::Once(const std::function& callback, const Pipeline type, const std::chrono::milliseconds delay) { Schedule([callback] { callback(); return COND_END; }, type, delay); } void Scheduler::OnGameInitialized(const std::function& callback, const Pipeline type, const std::chrono::milliseconds delay) { Schedule([=] { if (Game::Sys_IsDatabaseReady2()) { Once(callback, type, delay); return COND_END; } return COND_CONTINUE; }, Pipeline::MAIN); // Once Com_Frame_Try_Block_Function is called we know the game is 'ready' } void Scheduler::OnGameShutdown(const std::function& callback) { Schedule([callback] { callback(); return COND_END; }, Pipeline::QUIT, 0ms); } Scheduler::Scheduler() { Thread = Utils::Thread::CreateNamedThread("Async Scheduler", [] { while (!Kill) { Execute(Pipeline::ASYNC); std::this_thread::sleep_for(10ms); } }); Utils::Hook(0x5ACB9E, ScrPlace_EndFrame_Hk, HOOK_CALL).install()->quick(); // Hook G_Glass_Update so we may fix TLS issues Utils::Hook(0x416049, ServerFrame_Hk, HOOK_CALL).install()->quick(); // CL_CheckTimeout Utils::Hook(0x4B0F81, ClientFrame_Hk, HOOK_CALL).install()->quick(); // Com_Frame_Try_Block_Function Utils::Hook(0x4B724F, MainFrame_Hk, HOOK_CALL).install()->quick(); // Sys_Quit Utils::Hook(0x4D697A, SysSetBlockSystemHotkeys_Hk, HOOK_CALL).install()->quick(); } void Scheduler::preDestroy() { Kill = true; if (Thread.joinable()) { Thread.join(); } } }