From 99858e63f7ff125edbda1fb0d14d7c4682517b86 Mon Sep 17 00:00:00 2001 From: Alexander 'z33ky' Hirsch <1zeeky@gmail.com> Date: Sat, 7 Sep 2024 22:50:28 +0200 Subject: [PATCH] Optimize squirrel function_stub() Vector/QAngle return value Instead of temporary allocating dynamic memory, which subsequently is copied into another dynamically allocated piece of memory, provide a temporary buffer on the stack. The script value inserted into the VM still needs to allocate dynamically though... A few sites are adapted to not create a ScriptVariant_t from a temporary Vector to avoid dynamic allocation there too. cf. mapbase-source/source-sdk-2013#321 --- sp/src/game/server/hl2/proto_sniper.cpp | 15 ++- .../shared/mapbase/vscript_consts_shared.cpp | 3 +- sp/src/public/vscript/ivscript.h | 107 ++++++------------ sp/src/public/vscript/vscript_templates.h | 104 +++++++++++++++-- sp/src/vscript/vscript_squirrel.cpp | 7 +- 5 files changed, 145 insertions(+), 91 deletions(-) diff --git a/sp/src/game/server/hl2/proto_sniper.cpp b/sp/src/game/server/hl2/proto_sniper.cpp index 4315e35c..cd7c309e 100644 --- a/sp/src/game/server/hl2/proto_sniper.cpp +++ b/sp/src/game/server/hl2/proto_sniper.cpp @@ -2645,16 +2645,19 @@ Vector CProtoSniper::DesiredBodyTarget( CBaseEntity *pTarget ) { // By default, aim for the center Vector vecTarget = pTarget->WorldSpaceCenter(); + const Vector vecBulletOrigin = GetBulletOrigin(); #ifdef MAPBASE_VSCRIPT - if (m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope(m_ScriptScope)) + if ( m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope( m_ScriptScope ) ) { ScriptVariant_t functionReturn; - ScriptVariant_t args[] = { GetBulletOrigin(), ToHScript( pTarget ) }; - if (g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args )) + ScriptVariant_t args[] = { vecBulletOrigin, ToHScript( pTarget ) }; + if ( g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args ) ) { - if (functionReturn.m_type == FIELD_VECTOR && functionReturn.m_pVector->LengthSqr() != 0.0f) + if ( functionReturn.m_type == FIELD_VECTOR && functionReturn.m_pVector->LengthSqr() != 0.0f ) + { return *functionReturn.m_pVector; + } } } #endif @@ -2682,12 +2685,12 @@ Vector CProtoSniper::DesiredBodyTarget( CBaseEntity *pTarget ) { if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() ) { - vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false ); + vecTarget = pTarget->BodyTarget( vecBulletOrigin, false ); } else { // Shoot zombies in the headcrab - vecTarget = pTarget->HeadTarget( GetBulletOrigin() ); + vecTarget = pTarget->HeadTarget( vecBulletOrigin ); } } else if( pTarget->Classify() == CLASS_ANTLION ) diff --git a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp index 2ced7086..8b6fbd96 100644 --- a/sp/src/game/shared/mapbase/vscript_consts_shared.cpp +++ b/sp/src/game/shared/mapbase/vscript_consts_shared.cpp @@ -346,7 +346,8 @@ void RegisterSharedScriptConstants() ScriptRegisterConstant( g_pScriptVM, ROPE_NO_GRAVITY, "Disable gravity on this rope. (for use in rope flags)" ); ScriptRegisterConstant( g_pScriptVM, ROPE_NUMFLAGS, "The number of rope flags recognized by the game." ); - ScriptRegisterConstantNamed( g_pScriptVM, Vector( ROPE_GRAVITY ), "ROPE_GRAVITY", "Default rope gravity vector." ); + static Vector vecRopeGravity( ROPE_GRAVITY ); + ScriptRegisterConstantNamed( g_pScriptVM, vecRopeGravity, "ROPE_GRAVITY", "Default rope gravity vector." ); // // Sounds diff --git a/sp/src/public/vscript/ivscript.h b/sp/src/public/vscript/ivscript.h index c14b099e..c9a24e4b 100644 --- a/sp/src/public/vscript/ivscript.h +++ b/sp/src/public/vscript/ivscript.h @@ -300,7 +300,8 @@ enum ScriptFuncBindingFlags_t SF_MEMBER_FUNC = 0x01, }; -typedef bool (*ScriptBindingFunc_t)( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ); +union ScriptVariantTemporaryStorage_t; +typedef bool (*ScriptBindingFunc_t)( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ); struct ScriptFunctionBinding_t { @@ -389,52 +390,17 @@ struct ScriptVariant_t ScriptVariant_t( bool val ) : m_flags( 0 ), m_type( FIELD_BOOLEAN ) { m_bool = val; } ScriptVariant_t( HSCRIPT val ) : m_flags( 0 ), m_type( FIELD_HSCRIPT ) { m_hScript = val; } - ScriptVariant_t( const Vector &val, bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_VECTOR ) - { - if ( !bCopy ) - { - m_pVector = &val; - } - else - { - m_pVector = (Vector*)malloc( sizeof( Vector ) ); - new ( (Vector*)m_pVector ) Vector( val ); - m_flags |= SV_FREE; - } - } - ScriptVariant_t( const Vector *val, bool bCopy = false ) : ScriptVariant_t( *val ) { } + ScriptVariant_t( const Vector &val ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { m_pVector = &val; } + ScriptVariant_t( const Vector *val ) : ScriptVariant_t( *val ) { } - ScriptVariant_t( const char *val , bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_CSTRING ) - { - if ( !bCopy ) - { - m_pszString = val; - } - else - { - m_pszString = strdup( val ); - m_flags |= SV_FREE; - } - } + ScriptVariant_t( const char *val ) : m_flags( 0 ), m_type( FIELD_CSTRING ) { m_pszString = val; } #ifdef MAPBASE_VSCRIPT - ScriptVariant_t( const QAngle &val, bool bCopy = false ) : m_flags( 0 ), m_type( FIELD_VECTOR ) - { - if ( !bCopy ) - { - m_pAngle = &val; - } - else - { - m_pAngle = (QAngle*)malloc( sizeof( QAngle ) ); - new ( (QAngle*)m_pAngle ) QAngle( val ); - m_flags |= SV_FREE; - } - } - ScriptVariant_t( const QAngle *val, bool bCopy = false ) : ScriptVariant_t( *val ) { } + ScriptVariant_t( const QAngle &val ) : m_flags( 0 ), m_type( FIELD_VECTOR ) { m_pAngle = &val; } + ScriptVariant_t( const QAngle *val ) : ScriptVariant_t( *val ) { } - ScriptVariant_t( Vector &&val ) : ScriptVariant_t( val, true ) { } - ScriptVariant_t( QAngle &&val ) : ScriptVariant_t( val, true ) { } + ScriptVariant_t( Vector &&val ) = delete; + ScriptVariant_t( QAngle &&val ) = delete; #endif bool IsNull() const { return (m_type == FIELD_VOID ); } @@ -442,42 +408,29 @@ struct ScriptVariant_t operator int() const { Assert( m_type == FIELD_INTEGER ); return m_int; } operator float() const { Assert( m_type == FIELD_FLOAT ); return m_float; } operator const char *() const { Assert( m_type == FIELD_CSTRING ); return ( m_pszString ) ? m_pszString : ""; } - operator const Vector &() const { Assert( m_type == FIELD_VECTOR ); static Vector vecNull(0, 0, 0); return (m_pVector) ? *m_pVector : vecNull; } + operator const Vector &() const { Assert( m_type == FIELD_VECTOR ); return (m_pVector) ? *m_pVector : vec3_origin; } operator char() const { Assert( m_type == FIELD_CHARACTER ); return m_char; } operator bool() const { Assert( m_type == FIELD_BOOLEAN ); return m_bool; } operator HSCRIPT() const { Assert( m_type == FIELD_HSCRIPT ); return m_hScript; } #ifdef MAPBASE_VSCRIPT - operator const QAngle &() const { Assert( m_type == FIELD_VECTOR ); static QAngle vecNull(0, 0, 0); return (m_pAngle) ? *m_pAngle : vecNull; } + operator const QAngle &() const { Assert( m_type == FIELD_VECTOR ); return (m_pAngle) ? *m_pAngle : vec3_angle; } #endif - void operator=( int i ) { m_type = FIELD_INTEGER; m_int = i; } - void operator=( float f ) { m_type = FIELD_FLOAT; m_float = f; } - void operator=( double f ) { m_type = FIELD_FLOAT; m_float = (float)f; } - void operator=( const Vector &vec ) { m_type = FIELD_VECTOR; m_pVector = &vec; } - void operator=( const Vector *vec ) { m_type = FIELD_VECTOR; m_pVector = vec; } - void operator=( const char *psz ) { m_type = FIELD_CSTRING; m_pszString = psz; } - void operator=( char c ) { m_type = FIELD_CHARACTER; m_char = c; } - void operator=( bool b ) { m_type = FIELD_BOOLEAN; m_bool = b; } - void operator=( HSCRIPT h ) { m_type = FIELD_HSCRIPT; m_hScript = h; } + void operator=( int i ) { m_type = FIELD_INTEGER; m_flags = 0; m_int = i; } + void operator=( float f ) { m_type = FIELD_FLOAT; m_flags = 0; m_float = f; } + void operator=( double f ) { m_type = FIELD_FLOAT; m_flags = 0; m_float = (float)f; } + void operator=( const Vector &vec ) { m_type = FIELD_VECTOR; m_flags = 0; m_pVector = &vec; } + void operator=( const Vector *vec ) { m_type = FIELD_VECTOR; m_flags = 0; m_pVector = vec; } + void operator=( const char *psz ) { m_type = FIELD_CSTRING; m_flags = 0; m_pszString = psz; } + void operator=( char c ) { m_type = FIELD_CHARACTER; m_flags = 0; m_char = c; } + void operator=( bool b ) { m_type = FIELD_BOOLEAN; m_flags = 0; m_bool = b; } + void operator=( HSCRIPT h ) { m_type = FIELD_HSCRIPT; m_flags = 0; m_hScript = h; } #ifdef MAPBASE_VSCRIPT - void operator=( const QAngle &vec ) { m_type = FIELD_VECTOR; m_pAngle = &vec; } - void operator=( const QAngle *vec ) { m_type = FIELD_VECTOR; m_pAngle = vec; } + void operator=( const QAngle &ang ) { m_type = FIELD_VECTOR; m_flags = 0; m_pAngle = ∠ } + void operator=( const QAngle *ang ) { m_type = FIELD_VECTOR; m_flags = 0; m_pAngle = ang; } - void operator=( Vector &&vec ) - { - m_type = FIELD_VECTOR; - m_pVector = (Vector*)malloc( sizeof( Vector ) ); - new ( (Vector*)m_pVector ) Vector( vec ); - m_flags |= SV_FREE; - } - - void operator=( QAngle &&ang ) - { - m_type = FIELD_VECTOR; - m_pAngle = (QAngle*)malloc( sizeof( QAngle ) ); - new ( (QAngle*)m_pAngle ) QAngle( ang ); - m_flags |= SV_FREE; - } + void operator=( Vector &&vec ) = delete; + void operator=( QAngle &&ang ) = delete; #endif void Free() @@ -651,6 +604,16 @@ inline void ScriptVariant_t::EmplaceAllocedVector( const Vector &vec ) #define SCRIPT_VARIANT_NULL ScriptVariant_t() +union ScriptVariantTemporaryStorage_t +{ + // members must be initialized via placement-new + ScriptVariantTemporaryStorage_t() { } + + // members must have trivial destructor, since no destructor will be invoked + Vector m_vec; + QAngle m_ang; +}; + #ifdef MAPBASE_VSCRIPT //--------------------------------------------------------- struct ScriptConstantBinding_t @@ -743,7 +706,7 @@ static inline int ToConstantVariant(int value) // This is used for registering variants (particularly vectors) not tied to existing variables. // The principal difference is that m_data is initted with bCopy set to true. #define ScriptRegisterConstantFromTemp( pVM, constant, description ) ScriptRegisterConstantFromTempNamed( pVM, constant, #constant, description ) -#define ScriptRegisterConstantFromTempNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ScriptVariant_t( constant, true ); pVM->RegisterConstant( &binding ); } while (0) +#define ScriptRegisterConstantFromTempNamed( pVM, constant, scriptName, description ) do { static const auto constantStorage = constant; static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ScriptVariant_t( constantStorage ); pVM->RegisterConstant( &binding ); } while (0) //----------------------------------------------------------------------------- // diff --git a/sp/src/public/vscript/vscript_templates.h b/sp/src/public/vscript/vscript_templates.h index af020fe6..9f2054cc 100644 --- a/sp/src/public/vscript/vscript_templates.h +++ b/sp/src/public/vscript/vscript_templates.h @@ -326,8 +326,8 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) class CNonMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ - { \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ Assert( nArguments == N ); \ Assert( pReturn ); \ Assert( !pContext ); \ @@ -337,15 +337,15 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) return false; \ } \ *pReturn = ((FUNC_TYPE)pFunction)( SCRIPT_BINDING_ARGS_##N ); \ - return true; \ - } \ + return true; \ + } \ }; \ \ template \ class CNonMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ { \ Assert( nArguments == N ); \ Assert( !pReturn ); \ @@ -360,12 +360,52 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) } \ }; \ \ + template \ + class CNonMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( !pContext ); \ + \ + if ( nArguments != N || !pReturn || pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_vec ) Vector( ((FUNC_TYPE)pFunction)( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_vec; \ + return true; \ + } \ + }; \ + \ + template \ + class CNonMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( !pContext ); \ + \ + if ( nArguments != N || !pReturn || pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_ang ) QAngle( ((FUNC_TYPE)pFunction)( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_ang; \ + return true; \ + } \ + }; \ + \ template \ class CMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ - { \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ Assert( nArguments == N ); \ Assert( pReturn ); \ Assert( pContext ); \ @@ -375,15 +415,15 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) return false; \ } \ *pReturn = (((OBJECT_TYPE_PTR)(pContext))->*ScriptConvertFuncPtrFromVoid(pFunction))( SCRIPT_BINDING_ARGS_##N ); \ - return true; \ - } \ + return true; \ + } \ }; \ \ template \ class CMemberScriptBinding##N \ { \ public: \ - static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn ) \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ { \ Assert( nArguments == N ); \ Assert( !pReturn ); \ @@ -398,6 +438,46 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) } \ }; \ \ + template \ + class CMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( pContext ); \ + \ + if ( nArguments != N || !pReturn || !pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_vec ) Vector( (((OBJECT_TYPE_PTR)(pContext))->*ScriptConvertFuncPtrFromVoid(pFunction))( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_vec; \ + return true; \ + } \ + }; \ + \ + template \ + class CMemberScriptBinding##N \ + { \ + public: \ + static bool Call( void *pFunction, void *pContext, ScriptVariant_t *pArguments, int nArguments, ScriptVariant_t *pReturn, ScriptVariantTemporaryStorage_t &temporaryReturnStorage ) \ + { \ + Assert( nArguments == N ); \ + Assert( pReturn ); \ + Assert( pContext ); \ + \ + if ( nArguments != N || !pReturn || !pContext ) \ + { \ + return false; \ + } \ + new ( &temporaryReturnStorage.m_ang ) QAngle( (((OBJECT_TYPE_PTR)(pContext))->*ScriptConvertFuncPtrFromVoid(pFunction))( SCRIPT_BINDING_ARGS_##N ) ); \ + *pReturn = temporaryReturnStorage.m_ang; \ + return true; \ + } \ + }; \ + \ template \ inline ScriptBindingFunc_t ScriptCreateBinding(FUNCTION_RETTYPE (*pfnProxied)( FUNC_BASE_TEMPLATE_FUNC_PARAMS_##N ) ) \ { \ @@ -419,7 +499,11 @@ inline FUNCPTR_TYPE ScriptConvertFuncPtrFromVoid( void *p ) return &CMemberScriptBinding##N::Call; \ } +//note: no memory is actually allocated in the functions that get defined, +// it merely uses placement-new for which we need to disable this +#include "tier0/memdbgoff.h" FUNC_GENERATE_ALL( DEFINE_SCRIPT_BINDINGS ); +#include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // diff --git a/sp/src/vscript/vscript_squirrel.cpp b/sp/src/vscript/vscript_squirrel.cpp index b9ba659c..c17f3c64 100644 --- a/sp/src/vscript/vscript_squirrel.cpp +++ b/sp/src/vscript/vscript_squirrel.cpp @@ -1399,6 +1399,7 @@ SQInteger function_stub(HSQUIRRELVM vm) } ScriptVariant_t retval; + ScriptVariantTemporaryStorage_t retval_storage; SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); assert(pSquirrelVM); @@ -1406,7 +1407,7 @@ SQInteger function_stub(HSQUIRRELVM vm) sq_resetobject(&pSquirrelVM->lastError_); (*pFunc->m_pfnBinding)(pFunc->m_pFunction, instance, params.Base(), nargs, - pFunc->m_desc.m_ReturnType == FIELD_VOID ? nullptr : &retval); + pFunc->m_desc.m_ReturnType == FIELD_VOID ? nullptr : &retval, retval_storage); if (!sq_isnull(pSquirrelVM->lastError_)) { @@ -1417,7 +1418,9 @@ SQInteger function_stub(HSQUIRRELVM vm) PushVariant(vm, retval); - retval.Free(); + // strings never get copied here, Vector and QAngle are stored in script_retval_storage + // everything else is stored inline, so there should be no memory to free + Assert(!(retval.m_flags & SV_FREE)); return pFunc->m_desc.m_ReturnType != FIELD_VOID; }