VScript: properly implement script command callback

Previously we did sq_pushroottable() and a subsequent sq_call() after compiling the text buffer, but this didn't work for code that was threaded, or using Get/SetNetVar* functions.

The second issue was that the callback for the "script" command was ran in the main thread. Server script should always run in the server frame thread, the Set/GetNetVar* functions check thread id to retrieve the correct VM context, so running server script from the main thread ended up with Set/GetNetVar* functions retrieving the client VM context rather than server's, causing undefined behavior.

Script commands are now queued to the server frame thread, ultimately fixing this bug.

Also fixed a small bug with function 'sq_compilebuffer()'; it takes an extra argument but this wasn't taken into account in the SDK.
This commit is contained in:
Kawe Mazidjatari 2024-04-19 12:55:31 +02:00
parent 183a6e9c35
commit 1455017419
8 changed files with 58 additions and 14 deletions

View File

@ -10,6 +10,7 @@
/////////////////////////////////////////////////////////////////////////////////
#include "core/stdafx.h"
#include "common/protocol.h"
#include "tier0/frametask.h"
#include "tier1/cvar.h"
#include "tier1/strtools.h"
#include "engine/server/sv_main.h"
@ -217,6 +218,17 @@ void CServer::BroadcastMessage(CNetMessage* const msg, const bool onlyActive, co
//---------------------------------------------------------------------------------
void CServer::FrameJob(double flFrameTime, bool bRunOverlays, bool bUpdateFrame)
{
for (IFrameTask* const& task : g_ServerTaskQueueList)
{
task->RunFrame();
}
g_ServerTaskQueueList.erase(std::remove_if(g_ServerTaskQueueList.begin(),
g_ServerTaskQueueList.end(), [](const IFrameTask* task)
{
return task->IsFinished();
}), g_ServerTaskQueueList.end());
CServer__FrameJob(flFrameTime, bRunOverlays, bUpdateFrame);
LiveAPISystem()->RunFrame();
}
@ -242,3 +254,6 @@ void VServer::Detour(const bool bAttach) const
///////////////////////////////////////////////////////////////////////////////
CServer* g_pServer = nullptr;
CClientExtended CServer::sm_ClientsExtended[MAX_PLAYERS];
std::list<IFrameTask*> g_ServerTaskQueueList;
CFrameTask g_ServerTaskQueue;

View File

@ -1,4 +1,5 @@
#pragma once
#include "tier0/frametask.h"
#include "tier1/NetAdr.h"
#include "networksystem/pylon.h"
#include "engine/client/client.h"
@ -111,6 +112,9 @@ static_assert(sizeof(CServer) == 0x25264C0);
extern CServer* g_pServer;
extern std::list<IFrameTask*> g_ServerTaskQueueList;
extern CFrameTask g_ServerTaskQueue;
extern ConVar sv_showconnecting;
extern ConVar sv_pylonVisibility;

View File

@ -20,6 +20,7 @@
#include "engine/host_cmd.h"
#include "engine/enginetrace.h"
#ifndef CLIENT_DLL
#include "engine/server/server.h"
#include "engine/server/sv_main.h"
#include "server/vengineserver_impl.h"
#include "game/server/gameinterface.h"
@ -116,8 +117,11 @@ bool CModAppSystemGroup::StaticCreate(CModAppSystemGroup* pModAppSystemGroup)
}
g_TaskQueueList.push_back(&g_TaskQueue);
g_bAppSystemInit = true;
#ifndef CLIENT_DLL
g_ServerTaskQueueList.push_back(&g_ServerTaskQueue);
#endif // !CLIENT_DLL
g_bAppSystemInit = true;
return CModAppSystemGroup__Create(pModAppSystemGroup);
}

View File

@ -31,6 +31,18 @@ static void SQVM_ServerScript_f(const CCommand& args)
{
if (args.ArgC() >= 2)
{
const char* code = args.ArgS();
if (!ThreadInServerFrameThread())
{
const string scode(code);
g_ServerTaskQueue.Dispatch([scode]()
{
Script_Execute(scode.c_str(), SQCONTEXT::SERVER);
}, 0);
return; // Only run in server frame thread.
}
Script_Execute(args.ArgS(), SQCONTEXT::SERVER);
}
}

View File

@ -4,8 +4,8 @@
#include "public/iframetask.h"
//=============================================================================//
// This class is set up to run before each frame (main thread).
// Committed tasks are scheduled to execute after 'i' frames.
// This class is set up to run before each frame, committed tasks are scheduled
// to execute after 'i' frames.
// ----------------------------------------------------------------------------
// A use case for scheduling tasks in the main thread would be (for example)
// performing a web request in a separate thread, and apply the results (such as

View File

@ -187,7 +187,7 @@ void sq_newtable(HSQUIRRELVM v);
SQRESULT sq_newslot(HSQUIRRELVM v, SQInteger idx);
SQRESULT sq_arrayappend(HSQUIRRELVM v, SQInteger idx);
SQRESULT sq_pushstructure(HSQUIRRELVM v, const SQChar* name, const SQChar* member, const SQChar* codeclass1, const SQChar* codeclass2);
SQRESULT sq_compilebuffer(HSQUIRRELVM v, SQBufState* bufferState, const SQChar* buffer, SQInteger context);
SQRESULT sq_compilebuffer(HSQUIRRELVM v, SQBufState* bufferState, const SQChar* buffer, SQInteger context, SQBool raiseerror);
SQRESULT sq_call(HSQUIRRELVM v, SQInteger params, SQBool retval, SQBool raiseerror);
SQRESULT sq_startconsttable(HSQUIRRELVM v);
@ -228,7 +228,7 @@ inline void(*v_sq_newtable)(HSQUIRRELVM v);
inline SQRESULT(*v_sq_newslot)(HSQUIRRELVM v, SQInteger idx);
inline SQRESULT(*v_sq_arrayappend)(HSQUIRRELVM v, SQInteger idx);
inline SQRESULT(*v_sq_pushstructure)(HSQUIRRELVM v, const SQChar* name, const SQChar* member, const SQChar* codeclass1, const SQChar* codeclass2);
inline SQRESULT(*v_sq_compilebuffer)(HSQUIRRELVM v, SQBufState* bufferstate, const SQChar* buffer, SQInteger level);
inline SQRESULT(*v_sq_compilebuffer)(HSQUIRRELVM v, SQBufState* bufferstate, const SQChar* buffer, SQInteger level, SQBool raiseerror);
inline SQRESULT(*v_sq_call)(HSQUIRRELVM v, SQInteger params, SQBool retval, SQBool raiseerror);
inline SQRESULT(*v_sq_get)(HSQUIRRELVM v, SQInteger idx);

View File

@ -177,9 +177,9 @@ SQRESULT sq_pushstructure(HSQUIRRELVM v, const SQChar* name, const SQChar* membe
}
//---------------------------------------------------------------------------------
SQRESULT sq_compilebuffer(HSQUIRRELVM v, SQBufState* bufferState, const SQChar* buffer, SQInteger level)
SQRESULT sq_compilebuffer(HSQUIRRELVM v, SQBufState* bufferState, const SQChar* buffer, SQInteger level, SQBool raiseerror)
{
return v_sq_compilebuffer(v, bufferState, buffer, level);
return v_sq_compilebuffer(v, bufferState, buffer, level, raiseerror);
}
//---------------------------------------------------------------------------------

View File

@ -133,7 +133,11 @@ SQBool Script_PrecompileClientScripts(CSquirrelVM* vm)
void Script_Execute(const SQChar* code, const SQCONTEXT context)
{
Assert(context != SQCONTEXT::NONE);
Assert(ThreadInMainOrServerFrameThread());
if (context == SQCONTEXT::CLIENT || context == SQCONTEXT::UI)
Assert(ThreadInMainThread());
else if (context == SQCONTEXT::SERVER)
Assert(ThreadInServerFrameThread());
CSquirrelVM* s = Script_GetScriptHandle(context);
const char* const contextName = s_scriptContextNames[(int)context];
@ -145,24 +149,29 @@ void Script_Execute(const SQChar* code, const SQCONTEXT context)
}
HSQUIRRELVM v = s->GetVM();
if (!v)
{
Error(eDLL_T::ENGINE, NO_ERROR, "Attempted to run %s script while VM isn't initialized\n", contextName);
return;
}
SQBufState bufState = SQBufState(code);
SQRESULT compileResult = sq_compilebuffer(v, &bufState, "console", -1);
SQBufState bufState(code);
if (SQ_SUCCEEDED(compileResult))
if (SQ_SUCCEEDED(sq_compilebuffer(v, &bufState, "unnamed", -1, SQTrue)))
{
sq_pushroottable(v);
SQRESULT callResult = sq_call(v, 1, false, false);
SQObject hScript;
sq_getstackobj(v, -1, &hScript);
if (!SQ_SUCCEEDED(callResult))
sq_addref(v, &hScript);
sq_pop(v, 1);
if (s->ExecuteFunction((HSCRIPT)&hScript, NULL, 0, NULL, NULL) == SCRIPT_ERROR)
{
Error(eDLL_T::ENGINE, NO_ERROR, "Failed to execute %s script \"%s\"\n", contextName, code);
}
sq_release(v, &hScript);
}
}