From ced3b8f74e0aa86a00d29703ad66925d8030b1d5 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 17 May 2022 04:58:38 +0200 Subject: [PATCH] Port script debugger support from dhewm3 all that work in dhewm3 was done by https://github.com/HarrievG/ --- d3xp/GameEdit.cpp | 62 +++++++- d3xp/Game_local.cpp | 16 +++ d3xp/script/Script_Interpreter.cpp | 222 ++++++++++++++++++++++++----- d3xp/script/Script_Thread.cpp | 47 ++++++ framework/Common.h | 10 ++ framework/Game.h | 41 ++++++ game/GameEdit.cpp | 64 ++++++++- game/Game_local.cpp | 16 +++ game/script/Script_Interpreter.cpp | 190 +++++++++++++++++++++--- game/script/Script_Thread.cpp | 47 ++++++ 10 files changed, 654 insertions(+), 61 deletions(-) diff --git a/d3xp/GameEdit.cpp b/d3xp/GameEdit.cpp index 0c637bd..f6c99c6 100644 --- a/d3xp/GameEdit.cpp +++ b/d3xp/GameEdit.cpp @@ -670,7 +670,7 @@ void idEditEntities::DisplayEntities( void ) { =============================================================================== */ -idGameEdit gameEditLocal; +idGameEditExt gameEditLocal; idGameEdit * gameEdit = &gameEditLocal; @@ -1146,3 +1146,63 @@ void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { } } } + + +/*********************************************************************** + + Debugger + +***********************************************************************/ + +bool idGameEditExt::IsLineCode( const char *filename, int linenumber ) const { + idStr fileStr; + idProgram *program = &gameLocal.program; + for ( int i = 0; i < program->NumStatements( ); i++ ) { + fileStr = program->GetFilename( program->GetStatement( i ).file ); + fileStr.BackSlashesToSlashes( ); + + if ( strcmp( filename, fileStr.c_str( ) ) == 0 + && program->GetStatement( i ).linenumber == linenumber + ) { + return true; + } + } + return false; +} + +void idGameEditExt::GetLoadedScripts(idStrList** result) +{ + (*result)->Clear(); + idProgram* program = &gameLocal.program; + + for (int i = 0; i < program->NumFilenames(); i++) + { + (*result)->AddUnique(idStr(program->GetFilename(i))); + } +} + +void idGameEditExt::MSG_WriteScriptList(idBitMsg* msg) +{ + idProgram* program = &gameLocal.program; + + msg->WriteInt(program->NumFilenames()); + for (int i = 0; i < program->NumFilenames(); i++) + { + idStr file = program->GetFilename(i); + //fix this. it seams that scripts triggered by the runtime are stored with a wrong path + //the use // instead of '\' + file.BackSlashesToSlashes(); + msg->WriteString(file); + } +} + +const char* idGameEditExt::GetFilenameForStatement(idProgram* program, int index) const +{ + return program->GetFilenameForStatement(index); +} + +int idGameEditExt::GetLineNumberForStatement(idProgram* program, int index) const +{ + return program->GetLineNumberForStatement(index); +} + diff --git a/d3xp/Game_local.cpp b/d3xp/Game_local.cpp index 0489186..f1b6178 100644 --- a/d3xp/Game_local.cpp +++ b/d3xp/Game_local.cpp @@ -300,6 +300,17 @@ void idGameLocal::Clear( void ) { #endif } +// DG: for script debugger +static bool ( *updateDebuggerFnPtr )( idInterpreter *interpreter, idProgram *program, int instructionPointer ) = NULL; +bool updateGameDebugger( idInterpreter *interpreter, idProgram *program, int instructionPointer ) { + bool ret = false; + if ( interpreter != NULL && program != NULL ) { + ret = updateDebuggerFnPtr ? updateDebuggerFnPtr( interpreter , program, instructionPointer ) : false; + } + return ret; +} + + /* =========== idGameLocal::Init @@ -408,6 +419,11 @@ void idGameLocal::Init( void ) { gamestate = GAMESTATE_NOMAP; Printf( "...%d aas types\n", aasList.Num() ); + + // debugger support - Note: a side effect of requesting this function pointer + // is that now the engine knows that this mod supports script debugging. + common->GetAdditionalFunction( idCommon::FT_UpdateDebugger,( idCommon::FunctionPointer * ) &updateDebuggerFnPtr,NULL); + } /* diff --git a/d3xp/script/Script_Interpreter.cpp b/d3xp/script/Script_Interpreter.cpp index c8e0cc4..b14b903 100644 --- a/d3xp/script/Script_Interpreter.cpp +++ b/d3xp/script/Script_Interpreter.cpp @@ -33,6 +33,11 @@ If you have questions concerning this license or the applicable additional terms #include "script/Script_Interpreter.h" +#include "framework/FileSystem.h" + +// HvG: Debugger support +extern bool updateGameDebugger( idInterpreter *interpreter, idProgram *program, int instructionPointer ); + /* ================ idInterpreter::idInterpreter() @@ -183,7 +188,6 @@ idInterpreter::GetRegisterValue Returns a string representation of the value of the register. This is used primarily for the debugger and debugging -//FIXME: This is pretty much wrong. won't access data in most situations. ================ */ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { @@ -191,17 +195,17 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep idVarDef *d; char funcObject[ 1024 ]; char *funcName; - const idVarDef *scope; + const idVarDef *scope = NULL; + const idVarDef *scopeObj; const idTypeDef *field; - const idScriptObject *obj; const function_t *func; out.Empty(); - + if ( scopeDepth == -1 ) { scopeDepth = callStackDepth; - } - + } + if ( scopeDepth == callStackDepth ) { func = currentFunction; } else { @@ -215,35 +219,44 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep funcName = strstr( funcObject, "::" ); if ( funcName ) { *funcName = '\0'; - scope = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); - funcName += 2; + scopeObj = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + funcName += 2; + if ( scopeObj ) + { + scope = gameLocal.program.GetDef( NULL, funcName, scopeObj ); + } } else { funcName = funcObject; - scope = &def_namespace; + scope = gameLocal.program.GetDef( NULL, func->Name(), &def_namespace ); + scopeObj = NULL; } - // Get the function from the object - d = gameLocal.program.GetDef( NULL, funcName, scope ); - if ( !d ) { + if ( !scope ) + { return false; } - // Get the variable itself and check various namespaces - d = gameLocal.program.GetDef( NULL, name, d ); - if ( !d ) { - if ( scope == &def_namespace ) { - return false; - } - - d = gameLocal.program.GetDef( NULL, name, scope ); - if ( !d ) { - d = gameLocal.program.GetDef( NULL, name, &def_namespace ); - if ( !d ) { - return false; + d = gameLocal.program.GetDef( NULL, name, scope ); + + // Check the objects for it if it wasnt local to the function + if ( !d ) + { + for ( ; scopeObj && scopeObj->TypeDef()->SuperClass(); scopeObj = scopeObj->TypeDef()->SuperClass()->def ) + { + d = gameLocal.program.GetDef( NULL, name, scopeObj ); + if ( d ) + { + break; } } - } + } + if ( !d ) + { + out = "???"; + return false; + } + reg = GetVariable( d ); switch( d->Type() ) { case ev_float: @@ -256,7 +269,7 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep break; case ev_vector: - if ( reg.vectorPtr ) { + if ( reg.vectorPtr ) { out = va( "%g,%g,%g", reg.vectorPtr->x, reg.vectorPtr->y, reg.vectorPtr->z ); } else { out = "0,0,0"; @@ -274,30 +287,55 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep break; case ev_field: + { + idEntity* entity; + idScriptObject* obj; + if ( scope == &def_namespace ) { // should never happen, but handle it safely anyway return false; } - field = scope->TypeDef()->GetParmType( reg.ptrOffset )->FieldType(); - obj = *reinterpret_cast( &localstack[ callStack[ callStackDepth ].stackbase ] ); - if ( !field || !obj ) { + field = d->TypeDef()->FieldType(); + entity = GetEntity ( *((int*)&localstack[ localstackBase ]) ); + if ( !entity || !field ) + { return false; } + obj = &entity->scriptObject; + if ( !obj ) { + return false; + } + switch ( field->Type() ) { - case ev_boolean: - out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); - return true; + case ev_boolean: + out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; - case ev_float: - out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); - return true; + case ev_float: + out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_string: { + const char* str; + str = reinterpret_cast( &obj->data[ reg.ptrOffset ] ); + if ( !str ) { + out = "\"\""; + } else { + out = "\""; + out += str; + out += "\""; + } + return true; + } - default: - return false; + default: + return false; } + break; + } case ev_string: if ( reg.stringPtr ) { @@ -969,6 +1007,19 @@ bool idInterpreter::Execute( void ) { // next statement st = &gameLocal.program.GetStatement( instructionPointer ); + if ( !updateGameDebugger( this, &gameLocal.program, instructionPointer ) + && g_debugScript.GetBool( ) ) + { + static int lastLineNumber = -1; + if (lastLineNumber != gameLocal.program.GetStatement(instructionPointer).linenumber) { + gameLocal.Printf("%s (%d)\n", + gameLocal.program.GetFilename(gameLocal.program.GetStatement(instructionPointer).file), + gameLocal.program.GetStatement(instructionPointer).linenumber + ); + lastLineNumber = gameLocal.program.GetStatement(instructionPointer).linenumber; + } + } + switch( st->op ) { case OP_RETURN: LeaveFunction( st->a ); @@ -1833,3 +1884,100 @@ bool idInterpreter::Execute( void ) { return threadDying; } + + +bool idGameEditExt::CheckForBreakPointHit(const idInterpreter* interpreter, const function_t* function1, const function_t* function2, int depth) const +{ + return ((interpreter->GetCurrentFunction() == function1 || + interpreter->GetCurrentFunction() == function2) && + (interpreter->GetCallstackDepth() <= depth)); +} + +bool idGameEditExt::ReturnedFromFunction(const idProgram* program, const idInterpreter* interpreter, int index) const +{ + + return (const_cast(program)->GetStatement(index).op == OP_RETURN && interpreter->GetCallstackDepth() <= 1); +} + +bool idGameEditExt::GetRegisterValue(const idInterpreter* interpreter, const char* name, idStr& out, int scopeDepth) const +{ + return const_cast(interpreter)->GetRegisterValue(name, out, scopeDepth); +} + +const idThread* idGameEditExt::GetThread(const idInterpreter* interpreter) const +{ + return interpreter->GetThread(); +} + +void idGameEditExt::MSG_WriteCallstackFunc(idBitMsg* msg, const prstack_t* stack, const idProgram* program, int instructionPtr) +{ + const statement_t* st; + const function_t* func; + + func = stack->f; + + // If the function is unknown then just fill in with default data. + if (!func) + { + msg->WriteString(""); + msg->WriteString(""); + msg->WriteInt(0); + return; + } + else + { + msg->WriteString(va("%s( )", func->Name())); + } + + if (stack->s == -1) //this is a fake stack created by debugger, use intruction pointer for retrieval. + st = &const_cast(program)->GetStatement(instructionPtr); + else // Use the calling statement as the filename and linenumber where the call was made from + st = &const_cast(program)->GetStatement(stack->s); + + if (st) + { + idStr qpath = const_cast(program)->GetFilename(st->file); + if (idStr::FindChar(qpath, ':') != -1) + qpath = fileSystem->OSPathToRelativePath(qpath.c_str()); + qpath.BackSlashesToSlashes(); + msg->WriteString(qpath); + msg->WriteInt(st->linenumber); + } + else + { + msg->WriteString(""); + msg->WriteInt(0); + } +} + +void idGameEditExt::MSG_WriteInterpreterInfo(idBitMsg* msg, const idInterpreter* interpreter, const idProgram* program, int instructionPtr) +{ + int i; + prstack_s temp; + + msg->WriteShort((int)interpreter->GetCallstackDepth()); + + // write out the current function + temp.f = interpreter->GetCurrentFunction(); + temp.s = -1; + temp.stackbase = 0; + MSG_WriteCallstackFunc(msg, &temp, program, instructionPtr); + + // Run through all of the callstack and write each to the msg + for (i = interpreter->GetCallstackDepth() - 1; i > 0; i--) + { + MSG_WriteCallstackFunc(msg, interpreter->GetCallstack() + i, program, instructionPtr); + } +} + + +int idGameEditExt::GetInterpreterCallStackDepth(const idInterpreter* interpreter) +{ + return interpreter->GetCallstackDepth(); +} + +const function_t* idGameEditExt::GetInterpreterCallStackFunction(const idInterpreter* interpreter, int stackDepth/* = -1*/) +{ + return interpreter->GetCallstack()[stackDepth > -1 ? stackDepth : interpreter->GetCallstackDepth()].f; +} + diff --git a/d3xp/script/Script_Thread.cpp b/d3xp/script/Script_Thread.cpp index cb82633..e3fa1eb 100644 --- a/d3xp/script/Script_Thread.cpp +++ b/d3xp/script/Script_Thread.cpp @@ -1921,3 +1921,50 @@ void idThread::Event_InfluenceActive( void ) { idThread::ReturnInt( false ); } } + +int idGameEditExt::ThreadGetNum(const idThread* thread) const +{ + return const_cast(thread)->GetThreadNum(); +} + +const char* idGameEditExt::ThreadGetName(const idThread* thread) const +{ + return const_cast(thread)->GetThreadName(); +} + +int idGameEditExt::GetTotalScriptThreads() const +{ + return idThread::GetThreads().Num(); +} + +const idThread* idGameEditExt::GetThreadByIndex(int index) const +{ + return idThread::GetThreads()[index]; +} + +bool idGameEditExt::ThreadIsDoneProcessing(const idThread* thread) const +{ + return const_cast(thread)->IsDoneProcessing(); +} + +bool idGameEditExt::ThreadIsWaiting(const idThread* thread) const +{ + return const_cast(thread)->IsWaiting(); +} + +bool idGameEditExt::ThreadIsDying(const idThread* thread) const +{ + return const_cast(thread)->IsDying(); +} + +void idGameEditExt::MSG_WriteThreadInfo(idBitMsg* msg, const idThread* thread, const idInterpreter* interpreter) +{ + msg->WriteString(const_cast(thread)->GetThreadName()); + msg->WriteInt(const_cast(thread)->GetThreadNum()); + + msg->WriteBits((int)(thread == interpreter->GetThread()), 1); + msg->WriteBits((int)const_cast(thread)->IsDoneProcessing(), 1); + msg->WriteBits((int)const_cast(thread)->IsWaiting(), 1); + msg->WriteBits((int)const_cast(thread)->IsDying(), 1); +} + diff --git a/framework/Common.h b/framework/Common.h index f56146d..af3bd18 100644 --- a/framework/Common.h +++ b/framework/Common.h @@ -73,6 +73,9 @@ extern idCVar com_showAsyncStats; extern idCVar com_showSoundDecoders; extern idCVar com_makingBuild; extern idCVar com_updateLoadSize; +extern idCVar com_enableDebuggerServer; +extern idCVar com_dbgClientAdr; +extern idCVar com_dbgServerAdr; extern int time_gameFrame; // game logic time extern int time_gameDraw; // game present time @@ -84,6 +87,8 @@ extern volatile int com_ticNumber; // 60 hz tics, incremented by async functio extern int com_editors; // current active editor(s) extern bool com_editorActive; // true if an editor has focus +extern bool com_debuggerSupported; // only set to true when the updateDebugger function is set. see GetAdditionalFunction() + #ifdef _WIN32 const char DMAP_MSGID[] = "DMAPOutput"; const char DMAP_DONE[] = "DMAPDone"; @@ -266,6 +271,11 @@ public: // it returns true if we're currently running the doom3 demo // not relevant for mods, only for game/ aka base.dll/base.so/... FT_IsDemo = 1, + // the function's signature is bool fn(idInterpreter,idProgram,int) with arguments: + // idInterpreter *interpreter, idProgram *program, int instructionPointer + // it returns true if the game debugger is active. + // relevant for mods. + FT_UpdateDebugger, }; // returns true if that function is available in this version of dhewm3 diff --git a/framework/Game.h b/framework/Game.h index 8a4c4e1..5377659 100644 --- a/framework/Game.h +++ b/framework/Game.h @@ -31,6 +31,7 @@ If you have questions concerning this license or the applicable additional terms #include "idlib/BitMsg.h" #include "idlib/Dict.h" +#include "idlib/containers/StrList.h" #include "framework/UsercmdGen.h" #include "renderer/RenderWorld.h" #include "sound/sound.h" @@ -232,6 +233,12 @@ enum { class idEntity; class idMD5Anim; +// DG: for idGameEditExt (for script debugger) +class idThread; +class function_t; +class idProgram; +class idInterpreter; +typedef struct prstack_s prstack_t; // FIXME: this interface needs to be reworked but it properly separates code for the time being class idGameEdit { @@ -315,6 +322,40 @@ public: extern idGameEdit * gameEdit; +// In game script Debugging Support +class idGameEditExt : public idGameEdit { +public: + virtual ~idGameEditExt( void ) { } + // IdProgram + virtual void GetLoadedScripts( idStrList ** result ); + virtual bool IsLineCode( const char* filename, int linenumber) const; + virtual const char * GetFilenameForStatement( idProgram* program, int index ) const; + virtual int GetLineNumberForStatement( idProgram* program, int index ) const; + + // idInterpreter + virtual bool CheckForBreakPointHit( const idInterpreter* interpreter, const function_t* function1, const function_t* function2, int depth ) const; + virtual bool ReturnedFromFunction( const idProgram* program, const idInterpreter* interpreter, int index ) const; + virtual bool GetRegisterValue( const idInterpreter* interpreter, const char* name, idStr& out, int scopeDepth ) const; + virtual const idThread* GetThread( const idInterpreter* interpreter ) const; + virtual int GetInterpreterCallStackDepth( const idInterpreter* interpreter ); + virtual const function_t* GetInterpreterCallStackFunction( const idInterpreter* interpreter, int stackDepth = -1 ); + + // IdThread + virtual const char * ThreadGetName( const idThread* thread ) const; + virtual int ThreadGetNum( const idThread* thread ) const; + virtual bool ThreadIsDoneProcessing( const idThread* thread ) const; + virtual bool ThreadIsWaiting( const idThread* thread ) const; + virtual bool ThreadIsDying( const idThread* thread ) const; + virtual int GetTotalScriptThreads( ) const; + virtual const idThread* GetThreadByIndex( int index ) const; + + // MSG helpers + virtual void MSG_WriteThreadInfo( idBitMsg* msg, const idThread* thread, const idInterpreter* interpreter ); + virtual void MSG_WriteCallstackFunc( idBitMsg* msg, const prstack_t* stack, const idProgram* program, int instructionPtr ); + virtual void MSG_WriteInterpreterInfo( idBitMsg* msg, const idInterpreter* interpreter, const idProgram* program, int instructionPtr ); + virtual void MSG_WriteScriptList( idBitMsg* msg ); +}; + /* =============================================================================== diff --git a/game/GameEdit.cpp b/game/GameEdit.cpp index 0c637bd..4aa2f71 100644 --- a/game/GameEdit.cpp +++ b/game/GameEdit.cpp @@ -670,7 +670,7 @@ void idEditEntities::DisplayEntities( void ) { =============================================================================== */ -idGameEdit gameEditLocal; +idGameEditExt gameEditLocal; idGameEdit * gameEdit = &gameEditLocal; @@ -1146,3 +1146,65 @@ void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { } } } + +/*********************************************************************** + + Debugger + +***********************************************************************/ + +bool idGameEditExt::IsLineCode(const char* filename, int linenumber) const +{ + idStr fileStr; + idProgram* program = &gameLocal.program; + for (int i = 0; i < program->NumStatements(); i++) + { + fileStr = program->GetFilename(program->GetStatement(i).file); + fileStr.BackSlashesToSlashes(); + + if (strcmp(filename, fileStr.c_str()) == 0 + && program->GetStatement(i).linenumber == linenumber + ) + { + return true; + } + } + return false; +} + +void idGameEditExt::GetLoadedScripts( idStrList** result ) +{ + (*result)->Clear(); + idProgram* program = &gameLocal.program; + + for (int i = 0; i < program->NumFilenames(); i++) + { + (*result)->AddUnique( idStr(program->GetFilename( i )) ); + } +} + +void idGameEditExt::MSG_WriteScriptList( idBitMsg* msg) +{ + idProgram* program = &gameLocal.program; + + msg->WriteInt( program->NumFilenames() ); + for (int i = 0; i < program->NumFilenames(); i++) + { + idStr file = program->GetFilename(i); + //fix this. it seams that scripts triggered by the runtime are stored with a wrong path + //the use // instead of '\' + file.BackSlashesToSlashes(); + msg->WriteString(file); + } +} + +const char*idGameEditExt::GetFilenameForStatement(idProgram* program, int index) const +{ + return program->GetFilenameForStatement(index); +} + +int idGameEditExt::GetLineNumberForStatement(idProgram* program, int index) const +{ + return program->GetLineNumberForStatement(index); +} + diff --git a/game/Game_local.cpp b/game/Game_local.cpp index ae1eb86..76df5d8 100644 --- a/game/Game_local.cpp +++ b/game/Game_local.cpp @@ -256,6 +256,18 @@ void idGameLocal::Clear( void ) { memset( lagometer, 0, sizeof( lagometer ) ); } + +// DG: for script debugger support +static bool ( *updateDebuggerFnPtr )( idInterpreter *interpreter, idProgram *program, int instructionPointer ) = NULL; +bool updateGameDebugger( idInterpreter *interpreter, idProgram *program, int instructionPointer ) { + bool ret = false; + if ( interpreter != NULL && program != NULL ) { + ret = updateDebuggerFnPtr ? updateDebuggerFnPtr( interpreter, program, instructionPointer ) : false; + } + return ret; +} + + /* =========== idGameLocal::Init @@ -332,6 +344,10 @@ void idGameLocal::Init( void ) { gamestate = GAMESTATE_NOMAP; Printf( "...%d aas types\n", aasList.Num() ); + + // debugger support - Note: a side effect of requesting this function pointer + // is that now the engine knows that this mod supports script debugging. + common->GetAdditionalFunction(idCommon::FT_UpdateDebugger,(idCommon::FunctionPointer*) &updateDebuggerFnPtr,NULL); } /* diff --git a/game/script/Script_Interpreter.cpp b/game/script/Script_Interpreter.cpp index b6d044c..aa5e5e9 100644 --- a/game/script/Script_Interpreter.cpp +++ b/game/script/Script_Interpreter.cpp @@ -33,6 +33,11 @@ If you have questions concerning this license or the applicable additional terms #include "script/Script_Interpreter.h" +#include "framework/FileSystem.h" + +// HvG: Debugger support +extern bool updateGameDebugger( idInterpreter *interpreter, idProgram *program, int instructionPointer ); + /* ================ idInterpreter::idInterpreter() @@ -183,7 +188,6 @@ idInterpreter::GetRegisterValue Returns a string representation of the value of the register. This is used primarily for the debugger and debugging -//FIXME: This is pretty much wrong. won't access data in most situations. ================ */ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { @@ -191,9 +195,9 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep idVarDef *d; char funcObject[ 1024 ]; char *funcName; - const idVarDef *scope; + const idVarDef *scope = NULL; + const idVarDef *scopeObj; const idTypeDef *field; - const idScriptObject *obj; const function_t *func; out.Empty(); @@ -215,33 +219,42 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep funcName = strstr( funcObject, "::" ); if ( funcName ) { *funcName = '\0'; - scope = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + scopeObj = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); funcName += 2; + if ( scopeObj ) + { + scope = gameLocal.program.GetDef( NULL, funcName, scopeObj ); + } } else { funcName = funcObject; - scope = &def_namespace; + scope = gameLocal.program.GetDef( NULL, func->Name(), &def_namespace ); + scopeObj = NULL; } - // Get the function from the object - d = gameLocal.program.GetDef( NULL, funcName, scope ); - if ( !d ) { + if ( !scope ) + { return false; } - // Get the variable itself and check various namespaces - d = gameLocal.program.GetDef( NULL, name, d ); - if ( !d ) { - if ( scope == &def_namespace ) { - return false; - } + d = gameLocal.program.GetDef( NULL, name, scope ); - d = gameLocal.program.GetDef( NULL, name, scope ); - if ( !d ) { - d = gameLocal.program.GetDef( NULL, name, &def_namespace ); - if ( !d ) { - return false; + // Check the objects for it if it wasnt local to the function + if ( !d ) + { + for ( ; scopeObj && scopeObj->TypeDef()->SuperClass(); scopeObj = scopeObj->TypeDef()->SuperClass()->def ) + { + d = gameLocal.program.GetDef( NULL, name, scopeObj ); + if ( d ) + { + break; } } + } + + if ( !d ) + { + out = "???"; + return false; } reg = GetVariable( d ); @@ -274,14 +287,24 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep break; case ev_field: + { + idEntity* entity; + idScriptObject* obj; + if ( scope == &def_namespace ) { // should never happen, but handle it safely anyway return false; } - field = scope->TypeDef()->GetParmType( reg.ptrOffset )->FieldType(); - obj = *reinterpret_cast( &localstack[ callStack[ callStackDepth ].stackbase ] ); - if ( !field || !obj ) { + field = d->TypeDef()->FieldType(); + entity = GetEntity ( *((int*)&localstack[ localstackBase ]) ); + if ( !entity || !field ) + { + return false; + } + + obj = &entity->scriptObject; + if ( !obj ) { return false; } @@ -293,11 +316,26 @@ bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDep case ev_float: out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); return true; + + case ev_string: { + const char* str; + str = reinterpret_cast( &obj->data[ reg.ptrOffset ] ); + if ( !str ) { + out = "\"\""; + } else { + out = "\""; + out += str; + out += "\""; + } + return true; + } default: return false; } + break; + } case ev_string: if ( reg.stringPtr ) { @@ -969,6 +1007,19 @@ bool idInterpreter::Execute( void ) { // next statement st = &gameLocal.program.GetStatement( instructionPointer ); + if ( !updateGameDebugger( this, &gameLocal.program, instructionPointer ) + && g_debugScript.GetBool( ) ) + { + static int lastLineNumber = -1; + if ( lastLineNumber != gameLocal.program.GetStatement ( instructionPointer ).linenumber ) { + gameLocal.Printf ( "%s (%d)\n", + gameLocal.program.GetFilename ( gameLocal.program.GetStatement ( instructionPointer ).file ), + gameLocal.program.GetStatement ( instructionPointer ).linenumber + ); + lastLineNumber = gameLocal.program.GetStatement ( instructionPointer ).linenumber; + } + } + switch( st->op ) { case OP_RETURN: LeaveFunction( st->a ); @@ -1833,3 +1884,98 @@ bool idInterpreter::Execute( void ) { return threadDying; } + +bool idGameEditExt::CheckForBreakPointHit(const idInterpreter* interpreter, const function_t* function1, const function_t* function2, int depth) const +{ + return ( ( interpreter->GetCurrentFunction ( ) == function1 || + interpreter->GetCurrentFunction ( ) == function2)&& + ( interpreter->GetCallstackDepth ( ) <= depth) ); +} + +bool idGameEditExt::ReturnedFromFunction(const idProgram* program, const idInterpreter* interpreter, int index) const +{ + + return ( const_cast(program)->GetStatement(index).op == OP_RETURN && interpreter->GetCallstackDepth ( ) <= 1 ); +} + +bool idGameEditExt::GetRegisterValue(const idInterpreter* interpreter, const char* name, idStr& out, int scopeDepth) const +{ + return const_cast(interpreter)->GetRegisterValue(name, out, scopeDepth); +} + +const idThread*idGameEditExt::GetThread(const idInterpreter* interpreter) const +{ + return interpreter->GetThread(); +} + +void idGameEditExt::MSG_WriteCallstackFunc(idBitMsg* msg, const prstack_t* stack, const idProgram * program, int instructionPtr) +{ + const statement_t* st; + const function_t* func; + + func = stack->f; + + // If the function is unknown then just fill in with default data. + if ( !func ) + { + msg->WriteString ( "" ); + msg->WriteString ( "" ); + msg->WriteInt ( 0 ); + return; + } + else + { + msg->WriteString ( va("%s( )", func->Name() ) ); + } + + if (stack->s == -1) //this is a fake stack created by debugger, use intruction pointer for retrieval. + st = &const_cast( program )->GetStatement( instructionPtr ); + else // Use the calling statement as the filename and linenumber where the call was made from + st = &const_cast( program )->GetStatement ( stack->s ); + + if ( st ) + { + idStr qpath = const_cast( program )->GetFilename( st->file ); + if (idStr::FindChar( qpath, ':' ) != -1) + qpath = fileSystem->OSPathToRelativePath( qpath.c_str() ); + qpath.BackSlashesToSlashes ( ); + msg->WriteString( qpath ); + msg->WriteInt( st->linenumber ); + } + else + { + msg->WriteString ( "" ); + msg->WriteInt ( 0 ); + } +} + +void idGameEditExt::MSG_WriteInterpreterInfo(idBitMsg* msg, const idInterpreter* interpreter, const idProgram* program, int instructionPtr) +{ + int i; + prstack_s temp; + + msg->WriteShort((int)interpreter->GetCallstackDepth( ) ); + + // write out the current function + temp.f = interpreter->GetCurrentFunction( ); + temp.s = -1; + temp.stackbase = 0; + MSG_WriteCallstackFunc( msg, &temp, program, instructionPtr); + + // Run through all of the callstack and write each to the msg + for (i = interpreter->GetCallstackDepth() - 1; i > 0; i--) + { + MSG_WriteCallstackFunc( msg, interpreter->GetCallstack( ) + i, program, instructionPtr); + } +} + + +int idGameEditExt::GetInterpreterCallStackDepth(const idInterpreter* interpreter) +{ + return interpreter->GetCallstackDepth(); +} + +const function_t*idGameEditExt::GetInterpreterCallStackFunction( const idInterpreter* interpreter, int stackDepth/* = -1*/) +{ + return interpreter->GetCallstack( )[ stackDepth > -1 ? stackDepth :interpreter->GetCallstackDepth( ) ].f; +} diff --git a/game/script/Script_Thread.cpp b/game/script/Script_Thread.cpp index 58daa5d..0d471b1 100644 --- a/game/script/Script_Thread.cpp +++ b/game/script/Script_Thread.cpp @@ -1841,3 +1841,50 @@ void idThread::Event_InfluenceActive( void ) { idThread::ReturnInt( false ); } } + +int idGameEditExt::ThreadGetNum(const idThread* thread) const +{ + return const_cast(thread)->GetThreadNum(); +} + +const char*idGameEditExt::ThreadGetName(const idThread* thread) const +{ + return const_cast(thread)->GetThreadName(); +} + +int idGameEditExt::GetTotalScriptThreads() const +{ + return idThread::GetThreads().Num(); +} + +const idThread*idGameEditExt::GetThreadByIndex(int index) const +{ + return idThread::GetThreads()[index]; +} + +bool idGameEditExt::ThreadIsDoneProcessing(const idThread* thread) const +{ + return const_cast(thread)->IsDoneProcessing(); +} + +bool idGameEditExt::ThreadIsWaiting(const idThread* thread) const +{ + return const_cast(thread)->IsWaiting(); +} + +bool idGameEditExt::ThreadIsDying(const idThread* thread) const +{ + return const_cast(thread)->IsDying(); +} + +void idGameEditExt::MSG_WriteThreadInfo(idBitMsg* msg, const idThread* thread, const idInterpreter* interpreter) +{ + msg->WriteString(const_cast(thread)->GetThreadName()); + msg->WriteInt(const_cast(thread)->GetThreadNum()); + + msg->WriteBits((int)(thread == interpreter->GetThread()), 1); + msg->WriteBits((int)const_cast(thread)->IsDoneProcessing(), 1); + msg->WriteBits((int)const_cast(thread)->IsWaiting(), 1); + msg->WriteBits((int)const_cast(thread)->IsDying(), 1); +} +