Remove unused Tasks class
This commit is contained in:
parent
2dabb9f29d
commit
9137471b75
8 changed files with 6 additions and 389 deletions
|
|
@ -26,7 +26,7 @@ include(makefiles/shared.cmake)
|
||||||
include(makefiles/protobuf.cmake)
|
include(makefiles/protobuf.cmake)
|
||||||
|
|
||||||
add_subdirectory(vendor/spdlog)
|
add_subdirectory(vendor/spdlog)
|
||||||
add_subdirectory(vendor/khook)
|
# add_subdirectory(vendor/khook)
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCE_FILES
|
file(GLOB_RECURSE SOURCE_FILES
|
||||||
src/*.cpp
|
src/*.cpp
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#include "listeners.h"
|
#include "listeners.h"
|
||||||
#include <raytrace.h>
|
#include <raytrace.h>
|
||||||
#include <shared.h>
|
#include <shared.h>
|
||||||
#include <tasks.h>
|
|
||||||
#include <dynlibutils/module.h>
|
#include <dynlibutils/module.h>
|
||||||
|
|
||||||
class GameSessionConfiguration_t
|
class GameSessionConfiguration_t
|
||||||
|
|
@ -15,37 +14,22 @@ class GameSessionConfiguration_t
|
||||||
namespace RayTracePlugin::Listeners {
|
namespace RayTracePlugin::Listeners {
|
||||||
SourceHooks sourceHooks;
|
SourceHooks sourceHooks;
|
||||||
|
|
||||||
SH_DECL_HOOK3_void(IServerGameDLL, GameFrame, SH_NOATTRIB, 0, bool, bool, bool);
|
SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t&, ISource2WorldSession*, const char*);
|
||||||
SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t&,
|
|
||||||
ISource2WorldSession*, const char*);
|
|
||||||
SH_DECL_HOOK2(IGameEventManager2, LoadEventsFromFile, SH_NOATTRIB, 0, int, const char*, bool);
|
SH_DECL_HOOK2(IGameEventManager2, LoadEventsFromFile, SH_NOATTRIB, 0, int, const char*, bool);
|
||||||
|
|
||||||
int g_iLoadEventsFromFileId = -1;
|
int g_iLoadEventsFromFileId = -1;
|
||||||
|
|
||||||
void InitListeners() {
|
void InitListeners() {
|
||||||
SH_ADD_HOOK(IServerGameDLL, GameFrame, shared::g_pServer,
|
SH_ADD_HOOK(INetworkServerService, StartupServer, shared::g_pNetworkServerService, SH_MEMBER(&sourceHooks, &SourceHooks::Hook_StartupServer), true);
|
||||||
SH_MEMBER(&sourceHooks,&SourceHooks::Hook_GameFrame), false);
|
auto pCGameEventManagerVTable = DynLibUtils::CModule(shared::g_pServer).GetVirtualTableByName("CGameEventManager").RCast<IGameEventManager2*>();
|
||||||
SH_ADD_HOOK(INetworkServerService, StartupServer, shared::g_pNetworkServerService,
|
g_iLoadEventsFromFileId = SH_ADD_DVPHOOK(IGameEventManager2, LoadEventsFromFile, pCGameEventManagerVTable, SH_MEMBER(&sourceHooks, &SourceHooks::Hook_LoadEventsFromFile), false);
|
||||||
SH_MEMBER(&sourceHooks, &SourceHooks::Hook_StartupServer), true);
|
|
||||||
auto pCGameEventManagerVTable = DynLibUtils::CModule(shared::g_pServer).
|
|
||||||
GetVirtualTableByName("CGameEventManager").RCast<IGameEventManager2*>();
|
|
||||||
g_iLoadEventsFromFileId = SH_ADD_DVPHOOK(IGameEventManager2, LoadEventsFromFile, pCGameEventManagerVTable,
|
|
||||||
SH_MEMBER(&sourceHooks, &SourceHooks::Hook_LoadEventsFromFile), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DestructListeners() {
|
void DestructListeners() {
|
||||||
SH_REMOVE_HOOK(IServerGameDLL, GameFrame, shared::g_pServer,
|
SH_REMOVE_HOOK(INetworkServerService, StartupServer, shared::g_pNetworkServerService, SH_MEMBER(&sourceHooks, &SourceHooks::Hook_StartupServer), true);
|
||||||
SH_MEMBER(&sourceHooks,&SourceHooks::Hook_GameFrame), false);
|
|
||||||
SH_REMOVE_HOOK(INetworkServerService, StartupServer, shared::g_pNetworkServerService,
|
|
||||||
SH_MEMBER(&sourceHooks, &SourceHooks::Hook_StartupServer), true);
|
|
||||||
SH_REMOVE_HOOK_ID(g_iLoadEventsFromFileId);
|
SH_REMOVE_HOOK_ID(g_iLoadEventsFromFileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SourceHooks::Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick)
|
|
||||||
{
|
|
||||||
Tasks::Tick(simulating);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SourceHooks::Hook_StartupServer(const GameSessionConfiguration_t& config,
|
void SourceHooks::Hook_StartupServer(const GameSessionConfiguration_t& config,
|
||||||
ISource2WorldSession*, const char*)
|
ISource2WorldSession*, const char*)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace RayTracePlugin::Listeners {
|
||||||
|
|
||||||
class SourceHooks {
|
class SourceHooks {
|
||||||
public:
|
public:
|
||||||
void Hook_GameFrame(bool simulating, bool bFirstTick, bool bLastTick);
|
|
||||||
void Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*);
|
void Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*);
|
||||||
int Hook_LoadEventsFromFile(const char* filename, bool bSearchAll);
|
int Hook_LoadEventsFromFile(const char* filename, bool bSearchAll);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
#include "craytraceinterface.h"
|
#include "craytraceinterface.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "raytrace.h"
|
#include "raytrace.h"
|
||||||
#include "tasks.h"
|
|
||||||
|
|
||||||
#define VERSION_STRING SEMVER " @ " GITHUB_SHA
|
#define VERSION_STRING SEMVER " @ " GITHUB_SHA
|
||||||
#define BUILD_TIMESTAMP __DATE__ " " __TIME__
|
#define BUILD_TIMESTAMP __DATE__ " " __TIME__
|
||||||
|
|
@ -67,7 +66,6 @@ namespace RayTracePlugin
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Log::Init();
|
Log::Init();
|
||||||
Tasks::Init();
|
|
||||||
|
|
||||||
auto gamedata_path = std::string(Paths::GetRootDirectory() + "/gamedata.json");
|
auto gamedata_path = std::string(Paths::GetRootDirectory() + "/gamedata.json");
|
||||||
shared::g_pGameConfig = new CGameConfig(gamedata_path);
|
shared::g_pGameConfig = new CGameConfig(gamedata_path);
|
||||||
|
|
@ -99,7 +97,6 @@ namespace RayTracePlugin
|
||||||
bool IPlugin::Unload(char* error, size_t maxlen)
|
bool IPlugin::Unload(char* error, size_t maxlen)
|
||||||
{
|
{
|
||||||
Listeners::DestructListeners();
|
Listeners::DestructListeners();
|
||||||
Tasks::Shutdown();
|
|
||||||
|
|
||||||
FP_INFO("<<< Unload() success! >>>");
|
FP_INFO("<<< Unload() success! >>>");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Michal Přikryl on 31.10.2025.
|
|
||||||
// Copyright (c) 2025 slynxcz. All rights reserved.
|
|
||||||
//
|
|
||||||
#include "CTimer.h"
|
|
||||||
|
|
||||||
#include <shared.h>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
RayTracePlugin
|
|
||||||
{
|
|
||||||
std::list<std::shared_ptr<CTimerBase>> g_timers;
|
|
||||||
|
|
||||||
void RunTimers()
|
|
||||||
{
|
|
||||||
auto iterator = g_timers.begin();
|
|
||||||
|
|
||||||
while (iterator != g_timers.end())
|
|
||||||
{
|
|
||||||
auto pTimer = *iterator;
|
|
||||||
pTimer->Initialize();
|
|
||||||
|
|
||||||
// Timer execute
|
|
||||||
if (pTimer->GetLastExecute() + pTimer->GetInterval() <= universal_time && !pTimer->Execute(true))
|
|
||||||
iterator = g_timers.erase(iterator);
|
|
||||||
else
|
|
||||||
iterator++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveAllTimers()
|
|
||||||
{
|
|
||||||
g_timers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveTimers(uint64 iTimerFlag)
|
|
||||||
{
|
|
||||||
auto iterator = g_timers.begin();
|
|
||||||
|
|
||||||
while (iterator != g_timers.end())
|
|
||||||
if ((*iterator)->IsTimerFlagSet(iTimerFlag))
|
|
||||||
iterator = g_timers.erase(iterator);
|
|
||||||
else
|
|
||||||
iterator++;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::weak_ptr<CTimer> CTimer::Create(float flInitialInterval, uint64 nTimerFlags, std::function<float()> func)
|
|
||||||
{
|
|
||||||
auto pTimer = std::make_shared<CTimer>(flInitialInterval, nTimerFlags, func, _timer_constructor_tag{});
|
|
||||||
|
|
||||||
g_timers.push_back(pTimer);
|
|
||||||
return pTimer;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CTimer::Execute(bool bAutomaticExecute)
|
|
||||||
{
|
|
||||||
SetInterval(m_func());
|
|
||||||
SetLastExecute(universal_time);
|
|
||||||
|
|
||||||
bool bContinue = GetInterval() >= 0;
|
|
||||||
|
|
||||||
// Only scan the timer list if this isn't an automatic execute (RunTimers() already has the iterator to erase)
|
|
||||||
if (!bAutomaticExecute && !bContinue)
|
|
||||||
Cancel();
|
|
||||||
|
|
||||||
return bContinue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTimer::Cancel()
|
|
||||||
{
|
|
||||||
auto iterator = g_timers.begin();
|
|
||||||
|
|
||||||
while (iterator != g_timers.end())
|
|
||||||
{
|
|
||||||
if (*iterator == shared_from_this())
|
|
||||||
{
|
|
||||||
g_timers.erase(iterator);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Michal Přikryl on 31.10.2025.
|
|
||||||
// Copyright (c) 2025 slynxcz. All rights reserved.
|
|
||||||
//
|
|
||||||
#pragma once
|
|
||||||
#include <functional>
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
#include <platform.h>
|
|
||||||
#include <shared.h>
|
|
||||||
|
|
||||||
#include "tasks.h"
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
#define TIMERFLAG_NONE (0)
|
|
||||||
#define TIMERFLAG_MAP (1 << 0) // Only valid for this map, cancels on map change
|
|
||||||
#define TIMERFLAG_ROUND (1 << 1) // Only valid for this round, cancels on new round
|
|
||||||
|
|
||||||
namespace RayTracePlugin {
|
|
||||||
class CTimerBase
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
CTimerBase(float flInitialInterval, uint64 nTimerFlags) :
|
|
||||||
m_flInterval(flInitialInterval), m_nTimerFlags(nTimerFlags)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void SetInterval(float flInterval) { m_flInterval = flInterval; }
|
|
||||||
void SetLastExecute(float flLastExecute) { m_flLastExecute = flLastExecute; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual bool Execute(bool bAutomaticExecute = false) = 0;
|
|
||||||
virtual void Cancel() = 0;
|
|
||||||
|
|
||||||
float GetInterval() { return m_flInterval; }
|
|
||||||
float GetLastExecute() { return m_flLastExecute; }
|
|
||||||
bool IsTimerFlagSet(uint64 iTimerFlag) { return !iTimerFlag || (m_nTimerFlags & iTimerFlag); }
|
|
||||||
void Initialize()
|
|
||||||
{
|
|
||||||
if (m_flLastExecute == -1)
|
|
||||||
m_flLastExecute = universal_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
float m_flInterval;
|
|
||||||
float m_flLastExecute = -1;
|
|
||||||
uint64 m_nTimerFlags;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Timer functions should return the time until next execution, or a negative value like -1.0f to stop
|
|
||||||
// Having an interval of 0 is fine, in this case it will run on every game frame
|
|
||||||
class CTimer : public CTimerBase, public std::enable_shared_from_this<CTimer>
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
// Silly workaround to achieve a "private constructor" only Create() can call
|
|
||||||
struct _timer_constructor_tag
|
|
||||||
{
|
|
||||||
explicit _timer_constructor_tag() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
CTimer(float flInitialInterval, uint64 nTimerFlags, std::function<float()> func, _timer_constructor_tag) :
|
|
||||||
CTimerBase(flInitialInterval, nTimerFlags), m_func(func)
|
|
||||||
{}
|
|
||||||
|
|
||||||
static std::weak_ptr<CTimer> Create(float flInitialInterval, uint64 nTimerFlags, std::function<float()> func);
|
|
||||||
bool Execute(bool bAutomaticExecute) override;
|
|
||||||
void Cancel() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::function<float()> m_func;
|
|
||||||
};
|
|
||||||
|
|
||||||
void RunTimers();
|
|
||||||
void RemoveAllTimers();
|
|
||||||
void RemoveTimers(uint64 iTimerFlag);
|
|
||||||
}
|
|
||||||
160
src/tasks.cpp
160
src/tasks.cpp
|
|
@ -1,160 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Michal Přikryl on 10.07.2025.
|
|
||||||
// Copyright (c) 2025 slynxcz. All rights reserved.
|
|
||||||
//
|
|
||||||
#include "tasks.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
|
||||||
#include <mutex>
|
|
||||||
#include <queue>
|
|
||||||
|
|
||||||
namespace RayTracePlugin {
|
|
||||||
double universal_time = 0.0;
|
|
||||||
double last_tick_time = 0.0;
|
|
||||||
double timer_next_think = 0.0;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::vector<Timer *> once_off_timers;
|
|
||||||
std::vector<Timer *> repeat_timers;
|
|
||||||
std::mutex nextFrameMutex;
|
|
||||||
std::queue<std::function<void()> > nextFrameQueue;
|
|
||||||
}
|
|
||||||
void Tasks::NextFrame(std::function<void()> &&task) {
|
|
||||||
std::lock_guard lock(nextFrameMutex);
|
|
||||||
nextFrameQueue.emplace(std::move(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer::Timer(float interval, double execTime, TimerCallback callback, int flags)
|
|
||||||
: Interval(interval), ExecTime(execTime), Callback(std::move(callback)), Flags(flags) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tasks::Init() {
|
|
||||||
universal_time = 0.0;
|
|
||||||
last_tick_time = 0.0;
|
|
||||||
timer_next_think = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tasks::Shutdown() {
|
|
||||||
for (auto *timer: once_off_timers)
|
|
||||||
delete timer;
|
|
||||||
for (auto *timer: repeat_timers)
|
|
||||||
delete timer;
|
|
||||||
once_off_timers.clear();
|
|
||||||
repeat_timers.clear();
|
|
||||||
std::lock_guard lock(nextFrameMutex);
|
|
||||||
std::queue<std::function<void()> > empty;
|
|
||||||
std::swap(nextFrameQueue, empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tasks::Tick(bool simulating) {
|
|
||||||
std::queue<std::function<void()>> localQueue;
|
|
||||||
{
|
|
||||||
std::lock_guard lock(nextFrameMutex);
|
|
||||||
std::swap(localQueue, nextFrameQueue);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!localQueue.empty()) {
|
|
||||||
try {
|
|
||||||
localQueue.front()();
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
localQueue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
double now = std::chrono::duration_cast<std::chrono::duration<float> >(
|
|
||||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
||||||
|
|
||||||
if (simulating)
|
|
||||||
universal_time += now - last_tick_time;
|
|
||||||
else
|
|
||||||
universal_time += 0.015;
|
|
||||||
|
|
||||||
last_tick_time = now;
|
|
||||||
|
|
||||||
if (universal_time < timer_next_think)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = static_cast<int>(once_off_timers.size()) - 1; i >= 0; --i) {
|
|
||||||
Timer *timer = once_off_timers[i];
|
|
||||||
if (universal_time >= timer->ExecTime) {
|
|
||||||
timer->InExec = true;
|
|
||||||
try {
|
|
||||||
timer->Callback();
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
delete timer;
|
|
||||||
once_off_timers.erase(once_off_timers.begin() + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = static_cast<int>(repeat_timers.size()) - 1; i >= 0; --i) {
|
|
||||||
Timer *timer = repeat_timers[i];
|
|
||||||
if (universal_time >= timer->ExecTime) {
|
|
||||||
timer->InExec = true;
|
|
||||||
try {
|
|
||||||
timer->Callback();
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer->KillMe) {
|
|
||||||
delete timer;
|
|
||||||
repeat_timers.erase(repeat_timers.begin() + i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
timer->InExec = false;
|
|
||||||
timer->ExecTime = universal_time + timer->Interval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timer_next_think = universal_time + 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer *Tasks::AddTimer(float interval, TimerCallback callback, int flags) {
|
|
||||||
Timer *timer = new Timer(interval, universal_time + interval, std::move(callback), flags);
|
|
||||||
|
|
||||||
if (flags & TIMER_FLAG_REPEAT)
|
|
||||||
repeat_timers.push_back(timer);
|
|
||||||
else
|
|
||||||
once_off_timers.push_back(timer);
|
|
||||||
|
|
||||||
return timer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tasks::KillTimer(Timer *timer) {
|
|
||||||
if (!timer) return;
|
|
||||||
|
|
||||||
auto killFrom = [](std::vector<Timer *> &list, Timer *target) {
|
|
||||||
auto it = std::remove_if(list.begin(), list.end(), [=](Timer *t) { return t == target; });
|
|
||||||
if (it != list.end()) {
|
|
||||||
delete target;
|
|
||||||
list.erase(it, list.end());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (timer->InExec) {
|
|
||||||
timer->KillMe = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer->Flags & TIMER_FLAG_REPEAT)
|
|
||||||
killFrom(repeat_timers, timer);
|
|
||||||
else
|
|
||||||
killFrom(once_off_timers, timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tasks::RemoveMapChangeTimers() {
|
|
||||||
auto removeFrom = [](std::vector<Timer*>& list) {
|
|
||||||
for (int i = static_cast<int>(list.size()) - 1; i >= 0; --i) {
|
|
||||||
Timer* t = list[i];
|
|
||||||
if (t->Flags & TIMER_FLAG_NO_MAPCHANGE) {
|
|
||||||
delete t;
|
|
||||||
list.erase(list.begin() + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
removeFrom(once_off_timers);
|
|
||||||
removeFrom(repeat_timers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
src/tasks.h
43
src/tasks.h
|
|
@ -1,43 +0,0 @@
|
||||||
//
|
|
||||||
// Created by Michal Přikryl on 10.07.2025.
|
|
||||||
// Copyright (c) 2025 slynxcz. All rights reserved.
|
|
||||||
//
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace RayTracePlugin {
|
|
||||||
using TimerCallback = std::function<void()>;
|
|
||||||
|
|
||||||
enum TimerFlags {
|
|
||||||
TIMER_FLAG_REPEAT = 1 << 0,
|
|
||||||
TIMER_FLAG_NO_MAPCHANGE = 1 << 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern double universal_time;
|
|
||||||
extern double last_tick_time;
|
|
||||||
extern double timer_next_think;
|
|
||||||
|
|
||||||
class Timer {
|
|
||||||
public:
|
|
||||||
Timer(float interval, double execTime, TimerCallback callback, int flags);
|
|
||||||
~Timer() = default;
|
|
||||||
|
|
||||||
float Interval;
|
|
||||||
double ExecTime;
|
|
||||||
TimerCallback Callback;
|
|
||||||
int Flags;
|
|
||||||
bool InExec = false;
|
|
||||||
bool KillMe = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace Tasks {
|
|
||||||
void Init();
|
|
||||||
void Shutdown();
|
|
||||||
void Tick(bool simulating = true);
|
|
||||||
void NextFrame(std::function<void()> &&task);
|
|
||||||
Timer* AddTimer(float interval, TimerCallback callback, int flags = 0);
|
|
||||||
void KillTimer(Timer* timer);
|
|
||||||
void RemoveMapChangeTimers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue