From 6e6bb4d639b9a93ed2ca656057ab7ea871265328 Mon Sep 17 00:00:00 2001 From: samisalreadytaken <46823719+samisalreadytaken@users.noreply.github.com> Date: Mon, 18 Jul 2022 23:58:29 +0300 Subject: [PATCH] Implement CScriptHookManager --- sp/src/game/client/vscript_client.cpp | 11 +- sp/src/game/server/vscript_server.cpp | 12 +- .../shared/mapbase/vscript_singletons.cpp | 6 +- sp/src/game/shared/vscript_shared.cpp | 4 + sp/src/public/vscript/ivscript.h | 329 ++++++++++++++++-- sp/src/vscript/vscript_squirrel.cpp | 66 +--- sp/src/vscript/vscript_squirrel.nut | 8 +- 7 files changed, 349 insertions(+), 87 deletions(-) diff --git a/sp/src/game/client/vscript_client.cpp b/sp/src/game/client/vscript_client.cpp index 1b2891ab..1a5c59be 100644 --- a/sp/src/game/client/vscript_client.cpp +++ b/sp/src/game/client/vscript_client.cpp @@ -114,7 +114,7 @@ public: void OnEntityCreated( CBaseEntity *pEntity ) { - if ( g_pScriptVM ) + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityCreated" ) ) { // entity ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; @@ -124,7 +124,7 @@ public: void OnEntityDeleted( CBaseEntity *pEntity ) { - if ( g_pScriptVM ) + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityDeleted" ) ) { // entity ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; @@ -645,6 +645,11 @@ bool VScriptClientInit() #else Log( "VSCRIPT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); #endif + +#ifdef MAPBASE_VSCRIPT + GetScriptHookManager().OnInit(); +#endif + ScriptRegisterFunction( g_pScriptVM, GetMapName, "Get the name of the map."); ScriptRegisterFunction( g_pScriptVM, Time, "Get the current server time" ); ScriptRegisterFunction( g_pScriptVM, DoUniqueString, SCRIPT_ALIAS( "UniqueString", "Generate a string guaranteed to be unique across the life of the script VM, with an optional root string." ) ); @@ -770,6 +775,8 @@ public: { #ifdef MAPBASE_VSCRIPT g_ScriptNetMsg->LevelShutdownPreVM(); + + GetScriptHookManager().OnShutdown(); #endif VScriptClientTerm(); } diff --git a/sp/src/game/server/vscript_server.cpp b/sp/src/game/server/vscript_server.cpp index 559c57e3..25c8281a 100644 --- a/sp/src/game/server/vscript_server.cpp +++ b/sp/src/game/server/vscript_server.cpp @@ -154,7 +154,7 @@ public: void OnEntityCreated( CBaseEntity *pEntity ) { - if ( g_pScriptVM ) + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityCreated" ) ) { // entity ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; @@ -164,7 +164,7 @@ public: void OnEntitySpawned( CBaseEntity *pEntity ) { - if ( g_pScriptVM ) + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntitySpawned" ) ) { // entity ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; @@ -174,7 +174,7 @@ public: void OnEntityDeleted( CBaseEntity *pEntity ) { - if ( g_pScriptVM ) + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityDeleted" ) ) { // entity ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; @@ -600,6 +600,10 @@ bool VScriptServerInit() Log( "VSCRIPT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); #endif +#ifdef MAPBASE_VSCRIPT + GetScriptHookManager().OnInit(); +#endif + #ifdef MAPBASE_VSCRIPT // MULTIPLAYER // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByIndex, "GetPlayerByIndex", "PlayerInstanceFromIndex" ); @@ -836,6 +840,8 @@ public: { #ifdef MAPBASE_VSCRIPT g_ScriptNetMsg->LevelShutdownPreVM(); + + GetScriptHookManager().OnShutdown(); #endif VScriptServerTerm(); } diff --git a/sp/src/game/shared/mapbase/vscript_singletons.cpp b/sp/src/game/shared/mapbase/vscript_singletons.cpp index 64212d44..8f821e6c 100644 --- a/sp/src/game/shared/mapbase/vscript_singletons.cpp +++ b/sp/src/game/shared/mapbase/vscript_singletons.cpp @@ -872,7 +872,8 @@ public: // IGameSystem { if ( g_pScriptVM ) { - g_Hook_OnSave.Call( NULL, NULL, NULL ); + if ( GetScriptHookManager().IsEventHooked( "OnSave" ) ) + g_Hook_OnSave.Call( NULL, NULL, NULL ); // Legacy hook HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnSave" ); @@ -893,7 +894,8 @@ public: // IGameSystem { if ( g_pScriptVM ) { - g_Hook_OnRestore.Call( NULL, NULL, NULL ); + if ( GetScriptHookManager().IsEventHooked( "OnRestore" ) ) + g_Hook_OnRestore.Call( NULL, NULL, NULL ); // Legacy hook HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnRestore" ); diff --git a/sp/src/game/shared/vscript_shared.cpp b/sp/src/game/shared/vscript_shared.cpp index 088c19a7..4f20b7c6 100644 --- a/sp/src/game/shared/vscript_shared.cpp +++ b/sp/src/game/shared/vscript_shared.cpp @@ -451,6 +451,10 @@ public: } m_InstanceMap.Purge(); +#ifdef MAPBASE_VSCRIPT + GetScriptHookManager().OnRestore(); +#endif + #if defined(MAPBASE_VSCRIPT) && defined(CLIENT_DLL) VScriptSaveRestoreUtil_OnVMRestore(); #endif diff --git a/sp/src/public/vscript/ivscript.h b/sp/src/public/vscript/ivscript.h index fc16a143..0d105d55 100644 --- a/sp/src/public/vscript/ivscript.h +++ b/sp/src/public/vscript/ivscript.h @@ -98,6 +98,9 @@ #include #include +#include "utlmap.h" +#include "utlvector.h" + #include "platform.h" #include "datamap.h" #include "appframework/IAppSystem.h" @@ -137,6 +140,8 @@ class KeyValues; // This has been moved up a bit for IScriptManager DECLARE_POINTER_HANDLE( HSCRIPT ); #define INVALID_HSCRIPT ((HSCRIPT)-1) + +typedef unsigned int HScriptRaw; #endif enum ScriptLanguage_t @@ -885,8 +890,9 @@ public: //-------------------------------------------------------- // Hooks //-------------------------------------------------------- - virtual HSCRIPT LookupHookFunction( const char *pszEventName, HSCRIPT hScope ) = 0; - virtual ScriptStatus_t ExecuteHookFunction( HSCRIPT hFunction, const char *pszEventName, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) = 0; + // Persistent unique identifier for an HSCRIPT variable + virtual HScriptRaw HScriptToRaw( HSCRIPT val ) = 0; + virtual ScriptStatus_t ExecuteHookFunction( const char *pszEventName, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) = 0; #endif //-------------------------------------------------------- @@ -1586,6 +1592,291 @@ typedef CScriptScopeT<> CScriptScope; #define VScriptAddEnumToRoot( enumVal ) g_pScriptVM->SetValue( #enumVal, (int)enumVal ) #ifdef MAPBASE_VSCRIPT + +// +// Map pointer iteration +// +#define FOR_EACH_MAP_PTR( mapName, iteratorName ) \ + for ( int iteratorName = (mapName)->FirstInorder(); (mapName)->IsUtlMap && iteratorName != (mapName)->InvalidIndex(); iteratorName = (mapName)->NextInorder( iteratorName ) ) + +#define FOR_EACH_MAP_PTR_FAST( mapName, iteratorName ) \ + for ( int iteratorName = 0; (mapName)->IsUtlMap && iteratorName < (mapName)->MaxElement(); ++iteratorName ) if ( !(mapName)->IsValidIndex( iteratorName ) ) continue; else + +#define FOR_EACH_VEC_PTR( vecName, iteratorName ) \ + for ( int iteratorName = 0; iteratorName < (vecName)->Count(); iteratorName++ ) + +//----------------------------------------------------------------------------- +// +// Keeps track of which events and scopes are hooked without polling this from the script VM on each request. +// Local cache is updated each time there is a change to script hooks: on Add, on Remove, on game restore +// +//----------------------------------------------------------------------------- +class CScriptHookManager +{ +private: + typedef CUtlVector< char* > contextmap_t; + typedef CUtlMap< HScriptRaw, contextmap_t* > scopemap_t; + typedef CUtlMap< char*, scopemap_t* > hookmap_t; + + HSCRIPT m_hfnHookFunc; + + // { [string event], { [HSCRIPT scope], { [string context], [HSCRIPT callback] } } } + hookmap_t m_HookList; + +public: + + CScriptHookManager() : m_HookList( DefLessFunc(char*) ), m_hfnHookFunc(NULL) + { + } + + HSCRIPT GetHookFunction() + { + return m_hfnHookFunc; + } + + // For global hooks + bool IsEventHooked( const char *szEvent ) + { + return m_HookList.Find( const_cast< char* >( szEvent ) ) != m_HookList.InvalidIndex(); + } + + bool IsEventHookedInScope( const char *szEvent, HSCRIPT hScope ) + { + extern IScriptVM *g_pScriptVM; + + Assert( hScope ); + + int eventIdx = m_HookList.Find( const_cast< char* >( szEvent ) ); + if ( eventIdx == m_HookList.InvalidIndex() ) + return false; + + scopemap_t *scopeMap = m_HookList.Element( eventIdx ); + return scopeMap->Find( g_pScriptVM->HScriptToRaw( hScope ) ) != scopeMap->InvalidIndex(); + } + + static void __UpdateScriptHooks( HSCRIPT hooksList ) + { + extern CScriptHookManager &GetScriptHookManager(); + GetScriptHookManager().Update( hooksList ); + } + + // + // On VM init, registers script func and caches the hook func. + // + void OnInit() + { + extern IScriptVM *g_pScriptVM; + + ScriptRegisterFunctionNamed( g_pScriptVM, __UpdateScriptHooks, "__UpdateScriptHooks", SCRIPT_HIDE ); + + ScriptVariant_t hHooks; + g_pScriptVM->GetValue( "Hooks", &hHooks ); + + Assert( hHooks.m_type == FIELD_HSCRIPT ); + + if ( hHooks.m_type == FIELD_HSCRIPT ) + { + m_hfnHookFunc = g_pScriptVM->LookupFunction( "Call", hHooks ); + } + + Clear(); + } + + // + // On VM shutdown, clear the cache. + // Not exactly necessary, as the cache will be cleared on VM init next time. + // + void OnShutdown() + { + extern IScriptVM *g_pScriptVM; + + if ( m_hfnHookFunc ) + g_pScriptVM->ReleaseFunction( m_hfnHookFunc ); + + m_hfnHookFunc = NULL; + + Clear(); + } + + // + // On VM restore, update local cache. + // + void OnRestore() + { + extern IScriptVM *g_pScriptVM; + + ScriptVariant_t hHooks; + g_pScriptVM->GetValue( "Hooks", &hHooks ); + + if ( hHooks.m_type == FIELD_HSCRIPT ) + { + // Existing m_hfnHookFunc is invalid + m_hfnHookFunc = g_pScriptVM->LookupFunction( "Call", hHooks ); + + HSCRIPT func = g_pScriptVM->LookupFunction( "__UpdateHooks", hHooks ); + g_pScriptVM->Call( func ); + g_pScriptVM->ReleaseFunction( func ); + g_pScriptVM->ReleaseValue( hHooks ); + } + } + + // + // Clear local cache. + // + void Clear() + { + if ( m_HookList.Count() ) + { + FOR_EACH_MAP_FAST( m_HookList, i ) + { + scopemap_t *scopeMap = m_HookList.Element(i); + + FOR_EACH_MAP_PTR_FAST( scopeMap, j ) + { + contextmap_t *contextMap = scopeMap->Element(j); + contextMap->PurgeAndDeleteElements(); + } + + char *szEvent = m_HookList.Key(i); + free( szEvent ); + + scopeMap->PurgeAndDeleteElements(); + } + + m_HookList.PurgeAndDeleteElements(); + } + } + + // + // Called from script, update local cache. + // + void Update( HSCRIPT hooksList ) + { + extern IScriptVM *g_pScriptVM; + + // Rebuild from scratch + Clear(); + { + ScriptVariant_t varEvent, varScopeMap; + int it = -1; + while ( ( it = g_pScriptVM->GetKeyValue( hooksList, it, &varEvent, &varScopeMap ) ) != -1 ) + { + // types are checked in script + Assert( varEvent.m_type == FIELD_CSTRING ); + Assert( varScopeMap.m_type == FIELD_HSCRIPT ); + + scopemap_t *scopeMap; + + int eventIdx = m_HookList.Find( const_cast< char* >( varEvent.m_pszString ) ); + if ( eventIdx != m_HookList.InvalidIndex() ) + { + scopeMap = m_HookList.Element( eventIdx ); + } + else + { + scopeMap = new scopemap_t( DefLessFunc(HScriptRaw) ); + m_HookList.Insert( strdup( varEvent.m_pszString ), scopeMap ); + } + + ScriptVariant_t varScope, varContextMap; + int it2 = -1; + while ( ( it2 = g_pScriptVM->GetKeyValue( varScopeMap, it2, &varScope, &varContextMap ) ) != -1 ) + { + Assert( varScope.m_type == FIELD_HSCRIPT ); + Assert( varContextMap.m_type == FIELD_HSCRIPT); + + contextmap_t *contextMap; + + int scopeIdx = scopeMap->Find( g_pScriptVM->HScriptToRaw( varScope.m_hScript ) ); + if ( scopeIdx != scopeMap->InvalidIndex() ) + { + contextMap = scopeMap->Element( scopeIdx ); + } + else + { + contextMap = new contextmap_t(); + scopeMap->Insert( g_pScriptVM->HScriptToRaw( varScope.m_hScript ), contextMap ); + } + + ScriptVariant_t varContext, varCallback; + int it3 = -1; + while ( ( it3 = g_pScriptVM->GetKeyValue( varContextMap, it3, &varContext, &varCallback ) ) != -1 ) + { + Assert( varContext.m_type == FIELD_CSTRING ); + Assert( varCallback.m_type == FIELD_HSCRIPT ); + + bool skip = false; + + FOR_EACH_VEC_PTR( contextMap, k ) + { + char *szContext = contextMap->Element(k); + if ( V_strcmp( szContext, varContext.m_pszString ) == 0 ) + { + skip = true; + break; + } + } + + if ( !skip ) + contextMap->AddToTail( strdup( varContext.m_pszString ) ); + + g_pScriptVM->ReleaseValue( varContext ); + g_pScriptVM->ReleaseValue( varCallback ); + } + + g_pScriptVM->ReleaseValue( varScope ); + g_pScriptVM->ReleaseValue( varContextMap ); + } + + g_pScriptVM->ReleaseValue( varEvent ); + g_pScriptVM->ReleaseValue( varScopeMap ); + } + } + } +#ifdef _DEBUG + void Dump() + { + extern IScriptVM *g_pScriptVM; + + FOR_EACH_MAP( m_HookList, i ) + { + scopemap_t *scopeMap = m_HookList.Element(i); + char *szEvent = m_HookList.Key(i); + + Msg( "%s [%x]\n", szEvent, (void*)scopeMap ); + Msg( "{\n" ); + + FOR_EACH_MAP_PTR( scopeMap, j ) + { + HScriptRaw hScope = scopeMap->Key(j); + contextmap_t *contextMap = scopeMap->Element(j); + + Msg( "\t(0x%X) [%x]\n", hScope, (void*)contextMap ); + Msg( "\t{\n" ); + + FOR_EACH_VEC_PTR( contextMap, k ) + { + char *szContext = contextMap->Element(k); + + Msg( "\t\t%-.50s\n", szContext ); + } + + Msg( "\t}\n" ); + } + + Msg( "}\n" ); + } + } +#endif +}; + +inline CScriptHookManager &GetScriptHookManager() +{ + static CScriptHookManager g_ScriptHookManager; + return g_ScriptHookManager; +} + + //----------------------------------------------------------------------------- // Function bindings allow script functions to run C++ functions. // Hooks allow C++ functions to run script functions. @@ -1610,10 +1901,8 @@ struct ScriptHook_t // Only valid between CanRunInScope() and Call() HSCRIPT m_hFunc; - bool m_bLegacy; ScriptHook_t() : - m_bLegacy(false), m_hFunc(NULL) { } @@ -1633,20 +1922,17 @@ struct ScriptHook_t // Checks if there's a function of this name which would run in this scope bool CanRunInScope( HSCRIPT hScope ) { + // For now, assume null scope (which is used for global hooks) is always hooked + if ( !hScope || GetScriptHookManager().IsEventHookedInScope( m_desc.m_pszScriptName, hScope ) ) + { + m_hFunc = NULL; + return true; + } + extern IScriptVM *g_pScriptVM; - // Check the hook system first to make sure an unintended function in the script scope does not cancel out all script hooks. - m_hFunc = g_pScriptVM->LookupHookFunction( m_desc.m_pszScriptName, hScope ); - if ( !m_hFunc ) - { - // Legacy support if the new system is not being used - m_hFunc = g_pScriptVM->LookupFunction( m_desc.m_pszScriptName, hScope ); - m_bLegacy = true; - } - else - { - m_bLegacy = false; - } + // Legacy support if the new system is not being used + m_hFunc = g_pScriptVM->LookupFunction( m_desc.m_pszScriptName, hScope ); return !!m_hFunc; } @@ -1661,10 +1947,8 @@ struct ScriptHook_t Assert( CanRunInScope( hScope ) ); // Legacy - if ( m_bLegacy ) + if ( m_hFunc ) { - Assert( m_hFunc ); - for (int i = 0; i < m_desc.m_Parameters.Count(); i++) { g_pScriptVM->SetValue( m_pszParameterNames[i], pArgs[i] ); @@ -1686,12 +1970,7 @@ struct ScriptHook_t // New Hook System else { - ScriptStatus_t status = g_pScriptVM->ExecuteHookFunction( m_hFunc, m_desc.m_pszScriptName, pArgs, m_desc.m_Parameters.Count(), pReturn, hScope, true ); - - if ( bRelease ) - g_pScriptVM->ReleaseFunction( m_hFunc ); - m_hFunc = NULL; - + ScriptStatus_t status = g_pScriptVM->ExecuteHookFunction( m_desc.m_pszScriptName, pArgs, m_desc.m_Parameters.Count(), pReturn, hScope, true ); return status == SCRIPT_DONE; } } diff --git a/sp/src/vscript/vscript_squirrel.cpp b/sp/src/vscript/vscript_squirrel.cpp index 932ab26f..3b1e87ab 100644 --- a/sp/src/vscript/vscript_squirrel.cpp +++ b/sp/src/vscript/vscript_squirrel.cpp @@ -188,8 +188,8 @@ public: //-------------------------------------------------------- // Hooks //-------------------------------------------------------- - virtual HSCRIPT LookupHookFunction( const char *pszEventName, HSCRIPT hScope ) override; - virtual ScriptStatus_t ExecuteHookFunction( HSCRIPT hFunction, const char *pszEventName, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) override; + virtual HScriptRaw HScriptToRaw( HSCRIPT val ) override; + virtual ScriptStatus_t ExecuteHookFunction( const char *pszEventName, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) override; //-------------------------------------------------------- // External functions @@ -2347,64 +2347,24 @@ ScriptStatus_t SquirrelVM::ExecuteFunction(HSCRIPT hFunction, ScriptVariant_t* p return SCRIPT_DONE; } -HSCRIPT SquirrelVM::LookupHookFunction(const char *pszEventName, HSCRIPT hScope) +HScriptRaw SquirrelVM::HScriptToRaw( HSCRIPT val ) { - SquirrelSafeCheck safeCheck(vm_); + Assert( val ); + Assert( val != INVALID_HSCRIPT ); - // For now, assume null scope (which is used for global hooks) is always hooked - if ( hScope ) - { - Assert( hScope != INVALID_HSCRIPT ); - - sq_pushroottable(vm_); - sq_pushstring(vm_, "Hooks", -1); - sq_get(vm_, -2); - sq_pushstring(vm_, "IsEventHookedInScope", -1); - sq_get(vm_, -2); - sq_push(vm_, -2); - sq_pushstring(vm_, pszEventName, -1); - sq_pushobject(vm_, *((HSQOBJECT*)hScope)); - sq_call(vm_, 3, SQTrue, SQTrue); - - SQBool val; - if (SQ_FAILED(sq_getbool(vm_, -1, &val))) - { - sq_pop(vm_, 3); - return nullptr; - } - - sq_pop(vm_, 4); - - if (!val) - return nullptr; - } - - sq_pushroottable(vm_); - sq_pushstring(vm_, "Hooks", -1); - sq_get(vm_, -2); - sq_pushstring(vm_, "Call", -1); - sq_get(vm_, -2); - - HSQOBJECT obj; - sq_resetobject(&obj); - sq_getstackobj(vm_, -1, &obj); - sq_addref(vm_, &obj); - sq_pop(vm_, 3); - - HSQOBJECT* pObj = new HSQOBJECT; - *pObj = obj; - - return (HSCRIPT)pObj; + HSQOBJECT *obj = (HSQOBJECT*)val; +#if 0 + if ( sq_isweakref(*obj) ) + return obj->_unVal.pWeakRef->_obj._unVal.raw; +#endif + return obj->_unVal.raw; } -ScriptStatus_t SquirrelVM::ExecuteHookFunction(HSCRIPT hFunction, const char *pszEventName, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) +ScriptStatus_t SquirrelVM::ExecuteHookFunction(const char *pszEventName, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) { - if ( !hFunction ) - return SCRIPT_ERROR; - SquirrelSafeCheck safeCheck(vm_); - HSQOBJECT* pFunc = (HSQOBJECT*)hFunction; + HSQOBJECT* pFunc = (HSQOBJECT*)GetScriptHookManager().GetHookFunction(); sq_pushobject(vm_, *pFunc); // The call environment of the Hooks::Call function does not matter diff --git a/sp/src/vscript/vscript_squirrel.nut b/sp/src/vscript/vscript_squirrel.nut index 1c1bd4e7..5b76bbe3 100644 --- a/sp/src/vscript/vscript_squirrel.nut +++ b/sp/src/vscript/vscript_squirrel.nut @@ -180,6 +180,8 @@ Hooks <- t[scope] <- {}; t[scope][context] <- callback; + + return __UpdateHooks(); } function Remove( event, context ) @@ -244,6 +246,8 @@ Hooks <- delete s_List[ev]; } } + + return __UpdateHooks(); } function Call( event, scope, ... ) @@ -287,9 +291,9 @@ Hooks <- return firstReturn; } - function IsEventHookedInScope( event, scope ) + function __UpdateHooks() { - return ( event in s_List ) && ( scope in s_List[event] ) + return __UpdateScriptHooks( s_List ); } }