From 9ddc3635d6a04ff4b78611df00c905cc86e166ae Mon Sep 17 00:00:00 2001
From: Feyo Korenhof <35343640+feyokorenhof@users.noreply.github.com>
Date: Wed, 26 May 2021 18:07:32 +0200
Subject: [PATCH] Implemented cServer::ScheduleTask() and
cServer::TickQueuedTasks() (#5224)
---
CONTRIBUTORS | 1 +
Server/Plugins/APIDump/APIDesc.lua | 15 ++++++++
src/Bindings/ManualBindings.cpp | 43 +++++++++++++++++++++
src/Bindings/ManualBindings.h | 4 --
src/Server.cpp | 62 +++++++++++++++++++++++++++++-
src/Server.h | 20 ++++++++++
6 files changed, 140 insertions(+), 5 deletions(-)
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index e8c1bbb47..9a8f3b002 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -22,6 +22,7 @@ Diusrex
Duralex
Earboxer (Zach DeCook)
FakeTruth (founder)
+feyokorenhof
Gareth Nelson
GefaketHD
HaoTNN
diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua
index ef3bc5ea0..af6049fba 100644
--- a/Server/Plugins/APIDump/APIDesc.lua
+++ b/Server/Plugins/APIDump/APIDesc.lua
@@ -12307,6 +12307,21 @@ end
},
Notes = "Add a Forge mod name/version to the server ping list.",
},
+ ScheduleTask =
+ {
+ Params =
+ {
+ {
+ Name = "DelayTicks",
+ Type = "number",
+ },
+ {
+ Name = "TaskFunction",
+ Type = "function",
+ },
+ },
+ Notes = "Queues the specified function to be executed in the server's tick thread after a the specified number of ticks. This enables operations to be queued for execution in the future. The function signature is
function({{cServer|Server}})
All return values from the function are ignored. Note that it is unsafe to store references to Cuberite objects, such as entities, across from the caller to the task handler function; store the EntityID instead.",
+ },
SetMaxPlayers =
{
Params =
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index f06f4da37..fe5a1e6cb 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -3585,6 +3585,48 @@ static int tolua_cServer_RegisterForgeMod(lua_State * a_LuaState)
+static int tolua_cServer_ScheduleTask(lua_State * a_LuaState)
+{
+ // Function signature:
+ // Server:ScheduleTask(NumTicks, Callback)
+
+ // Retrieve the args:
+ cLuaState L(a_LuaState);
+ if (
+ !L.CheckParamUserType(1, "cServer") ||
+ !L.CheckParamNumber(2) ||
+ !L.CheckParamFunction(3)
+ )
+ {
+ return 0;
+ }
+ cServer * Server;
+ int NumTicks;
+ auto Task = std::make_shared();
+ if (!L.GetStackValues(1, Server, NumTicks, Task))
+ {
+ return cManualBindings::lua_do_error(a_LuaState, "Error in function call '#funcname#': Cannot read parameters");
+ }
+ if (Server == nullptr)
+ {
+ return cManualBindings::lua_do_error(a_LuaState, "Error in function call '#funcname#': Not called on an object instance");
+ }
+ if (!Task->IsValid())
+ {
+ return cManualBindings::lua_do_error(a_LuaState, "Error in function call '#funcname#': Could not store the callback parameter");
+ }
+
+ Server->ScheduleTask(cTickTime(NumTicks), [Task](cServer & a_Server)
+ {
+ Task->Call(&a_Server);
+ });
+ return 0;
+}
+
+
+
+
+
static int tolua_cScoreboard_GetTeamNames(lua_State * L)
{
cLuaState S(L);
@@ -4625,6 +4667,7 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_beginmodule(tolua_S, "cServer");
tolua_function(tolua_S, "RegisterForgeMod", tolua_cServer_RegisterForgeMod);
+ tolua_function(tolua_S, "ScheduleTask", tolua_cServer_ScheduleTask);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cStringCompression");
diff --git a/src/Bindings/ManualBindings.h b/src/Bindings/ManualBindings.h
index 780c6ce41..f0b7cb607 100644
--- a/src/Bindings/ManualBindings.h
+++ b/src/Bindings/ManualBindings.h
@@ -298,7 +298,3 @@ public:
return 1;
}
};
-
-
-
-
diff --git a/src/Server.cpp b/src/Server.cpp
index 3402a80fa..ae0df7544 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -117,7 +117,8 @@ cServer::cServer(void) :
m_MaxPlayers(0),
m_bIsHardcore(false),
m_TickThread(*this),
- m_ShouldAuthenticate(false)
+ m_ShouldAuthenticate(false),
+ m_UpTime(0)
{
// Initialize the LuaStateTracker singleton before the app goes multithreaded:
cLuaStateTracker::GetStats();
@@ -326,6 +327,9 @@ cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIP
void cServer::Tick(float a_Dt)
{
+ // Update server uptime
+ m_UpTime++;
+
// Send the tick to the plugins, as well as let the plugin manager reload, if asked to (issue #102):
cPluginManager::Get()->Tick(a_Dt);
@@ -334,6 +338,9 @@ void cServer::Tick(float a_Dt)
// Tick all clients not yet assigned to a world:
TickClients(a_Dt);
+
+ // Process all queued tasks
+ TickQueuedTasks();
}
@@ -442,6 +449,20 @@ void cServer::QueueExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCa
+void cServer::ScheduleTask(cTickTime a_DelayTicks, std::function a_Task)
+{
+ const auto TargetTick = a_DelayTicks + m_UpTime;
+ // Insert the task into the list of scheduled tasks
+ {
+ cCSLock Lock(m_CSTasks);
+ m_Tasks.emplace_back(TargetTick, std::move(a_Task));
+ }
+}
+
+
+
+
+
void cServer::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output)
{
AStringVector split = StringSplit(a_Cmd, " ");
@@ -725,3 +746,42 @@ void cServer::TickCommands(void)
ExecuteConsoleCommand(Command.first, *Command.second);
}
}
+
+
+
+
+
+void cServer::TickQueuedTasks(void)
+{
+ // Move the tasks to be executed to a seperate vector to avoid deadlocks on
+ // accessing m_Tasks
+ decltype(m_Tasks) Tasks;
+ {
+ cCSLock Lock(m_CSTasks);
+ if (m_Tasks.empty())
+ {
+ return;
+ }
+
+ // Partition everything to be executed by returning false to move to end
+ // of list if time reached
+ auto MoveBeginIterator = std::partition(
+ m_Tasks.begin(), m_Tasks.end(),
+ [this](const decltype(m_Tasks)::value_type & a_Task)
+ {
+ return a_Task.first >= m_UpTime;
+ });
+
+ // Cut all the due tasks from m_Tasks into Tasks:
+ Tasks.insert(
+ Tasks.end(), std::make_move_iterator(MoveBeginIterator),
+ std::make_move_iterator(m_Tasks.end()));
+ m_Tasks.erase(MoveBeginIterator, m_Tasks.end());
+ }
+
+ // Execute each task:
+ for (const auto & Task : Tasks)
+ {
+ Task.second(*this);
+ } // for itr - m_Tasks[]
+}
diff --git a/src/Server.h b/src/Server.h
index 3d272d7c0..7701faa69 100644
--- a/src/Server.h
+++ b/src/Server.h
@@ -105,6 +105,9 @@ public:
The command's output will be written to the a_Output callback. */
void QueueExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output);
+ /** Queues a lambda task onto the server tick thread, with the specified delay in ticks. */
+ void ScheduleTask(cTickTime a_DelayTicks, std::function a_Task);
+
/** Lists all available console commands and their helpstrings */
void PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & a_Output);
@@ -245,6 +248,18 @@ private:
AStringVector m_Ports;
+ /** Time, in ticks, since the server started
+ Not persistent across server restarts */
+ cTickTimeLong m_UpTime;
+
+ /** Guards the m_Tasks */
+ cCriticalSection m_CSTasks;
+
+ /** Tasks that have been queued onto the tick thread, possibly to be
+ executed at target tick in the future; guarded by m_CSTasks */
+ std::vector>> m_Tasks;
+
+
cServer(void);
/** Executes the console command, sends output through the specified callback. */
@@ -268,6 +283,11 @@ private:
/** Executes commands queued in the command queue. */
void TickCommands(void);
+
+ /** Executes all tasks queued onto the tick thread */
+ void TickQueuedTasks(void);
+
+
}; // tolua_export