#include "script/gta_thread.h" // Rage headers #include "profile/timebars.h" #include "script/wrapper.h" #include "system/param.h" // Framework headers #include "grcore/debugdraw.h" #include "fwscript/scriptInterface.h" #include "fwutil/idgen.h" // Game headers #include "camera/viewports/ViewportManager.h" #include "control/gamelogic.h" #include "core/game.h" #include "debug/Debug.h" #include "ModelInfo/ModelInfo.h" #include "ModelInfo/ModelInfo_Factories.h" #include "network/arrays/NetworkArrayMgr.h" #include "network/debug/NetworkDebug.h" #include "network/network.h" #include "network/Objects/NetworkObjectPopulationMgr.h" #include "peds/ped.h" #include "renderer/RenderTargetMgr.h" #include "SaveLoad/SavegameScriptData/script_save_data.h" #include "SaveLoad/savegame_channel.h" #include "SaveLoad/savegame_data.h" #include "scene/ExtraContent.h" #include "scene/worldpoints.h" #include "script/commands_graphics.h" #include "script/script.h" #include "script/script_brains.h" #include "script/script_debug.h" #include "script/script_hud.h" #include "script/streamedscripts.h" #include "script/script_helper.h" #include "script/script_text_construction.h" #include "system/FileMgr.h" #include "Task/General/TaskBasic.h" #define SCRIPT_HEAP_SIZE (6700<<10) // ~6.5MiB for the script heap #define SCRIPT_DEBUG_HEAP_SIZE (2500<<10) // ~2.4MiB extra for in non final NETWORK_OPTIMISATIONS() #if !__FINAL PARAM(override_default_stack_size, "[gta_thread] when running with a testbed level, call this with 1024 to override the increased Default stack size specified in the gameconfig.xml in the Title Update"); #endif // !__FINAL #define USE_SCRIPT_ALLOCATOR ENABLE_POOL_ALLOCATOR #if USE_SCRIPT_ALLOCATOR sysMemAllocator* GtaThread::sm_Allocator = 0; #endif // USE_SCRIPT_ALLOCATOR #if !__FINAL s32 GtaThread::sm_SoakTestStackSize = -1; s32 GtaThread::sm_NetworkBotStackSize = -1; s32 GtaThread::sm_DebugMenuStackSize = -1; #endif // !__FINAL s32 GtaThread::sm_DefaultStackSize = -1; s32 GtaThread::sm_MissionStackSize = -1; #if GTA_REPLAY bool GtaThread::sm_bTriggerAutoCleanup = false; #endif // GTA_REPLAY s32 GtaThread::ScriptThreadIteratorIndex = 0; scrThreadId GtaThread::ms_LastPathNodeScriptThreadID = THREAD_INVALID; // PURPOSE: Constructor GtaThread::GtaThread() : scrThread() { m_Handler = NULL; Initialise(); // DeatharrestCheckEnabled = true; // DoneDeatharrest = false; #if __BANK pDebugger = NULL; #endif } // PURPOSE: Destructor GtaThread::~GtaThread() { #if __BANK if (pDebugger) { delete pDebugger; pDebugger = NULL; } m_WidgetTree.RemoveAllWidgetNodes(); #endif } void GtaThread::StaticInit(unsigned UNUSED_PARAM(initMode)) { #if USE_SCRIPT_ALLOCATOR if(!sm_Allocator) { const unsigned sizeData = SCRIPT_HEAP_SIZE NOTFINAL_ONLY(+SCRIPT_DEBUG_HEAP_SIZE); const unsigned sizeTotal = sizeof(sysMemSimpleAllocator) + sizeData; sysMemSimpleAllocator* source = sysMemManager::GetInstance().GetPoolAllocator(); RAGE_TRACKING_ONLY(source->SetTallied(false)); char* memory = (char*)source->Allocate(sizeTotal,16,0); RAGE_TRACKING_ONLY(source->SetTallied(true)); char* addrData = memory; // In memory the data section comes first char* addrAlc = memory + sizeData; // Place the allocator object at the end of the data block sm_Allocator = new (addrAlc) sysMemSimpleAllocator(addrData,sizeData,sysMemSimpleAllocator::HEAP_MAIN,false); // In place allocate the allocator #if RAGE_TRACKING diagTracker* t = diagTracker::GetCurrent(); if (t) { t->InitHeap("Script Heap", addrData, sizeData); } #endif // RAGE_TRACKING } #endif // USE_SCRIPT_ALLOCATOR } // PURPOSE: Reset the thread to a runnable state at the top of the current program void GtaThread::Reset(scrProgramId program,const void *argStruct,int argStructSize) { scrThread::Reset(program,argStruct,argStructSize); m_HashOfScriptName = (s32) atHashString::ComputeHash(GetScriptName()); } // PURPOSE: Execute script instructions. // PARAMS: insnCount - Reference to a variable containing the maximum number of instructions // to execute before returning. Modified after this call to contain the number // of instructions remaining. Can be zero, in which case the script does nothing. // RETURNS: New machine state /* scrThread::State GtaThread::Run(int &insnCount) { } */ #if __DEV // PURPOSE: Dumps thread state to console (program counter, stack contents, etc) /* void GtaThread::DumpState(void (*printer)(const char *fmt,...)) { } */ #endif // PURPOSE: Reserve space for fixed number of threads // PARAMS: count - Maximum number of threads we will support simultaneously // NOTES: Reimplement this in a subclass and call that version if you subclass // the script thread. void GtaThread::AllocateThreads() { const CConfigScriptStackSizes& configData = CGameConfig::Get().GetConfigScriptStackSizes(); const s32 num_of_array_entries = configData.m_StackSizeData.GetCount(); s32 TotalNumberOfStacks = 0; for (s32 i=0; i 0, "GtaThread::AllocateThreads - stack size with name DEFAULT not found in gameconfig.xml"); scriptAssertf(sm_MissionStackSize > 0, "GtaThread::AllocateThreads - stack size with name MISSION not found in gameconfig.xml"); #if !__FINAL scriptAssertf(sm_SoakTestStackSize > 0, "GtaThread::AllocateThreads - stack size with name SOAK_TEST not found in gameconfig.xml"); scriptAssertf(sm_NetworkBotStackSize > 0, "GtaThread::AllocateThreads - stack size with name NETWORK_BOT not found in gameconfig.xml"); scriptAssertf(sm_DebugMenuStackSize > 0, "GtaThread::AllocateThreads - stack size with name DEBUG_MENU not found in gameconfig.xml"); #endif // !__FINAL } extern scrCommandHash s_CommandHash; void GtaThread::DeallocateThreads() { #if !__FINAL sysStack::PrePrintStackTrace = scrThread::OriginalPrePrintStackTrace; #endif // !__FINAL s_CommandHash.Kill(); { #if USE_SCRIPT_ALLOCATOR sysMemAutoUseAllocator alc(*sm_Allocator); // Auto push/pop the allocator #endif // USE_SCRIPT_ALLOCATOR for (int i=0; iUpdate(); } #endif #if SCRIPT_PROFILING if (m_bDisplayProfileData) { CScriptDebug::DisplayProfileDataForThisThread(this); } #endif // SCRIPT_PROFILING // reset script hud CScriptHud::BeforeThreadUpdate(); CTheScripts::SetScenarioPedsCanBeGrabbedByScript(false); ASSERT_ONLY(bool bExitFlagMarked = m_bExitFlagSet;) scrThread::State ReturnState = scrThread::Update(insnCount); #if __ASSERT VerifyNetworkSafety(); #endif #if __ASSERT scriptAssertf(CTheScripts::GetXmlTree() == NULL, "GtaThread::Update - %s %d - xml node != NULL. DELETE_XML_FILE should be called" , GetScriptName(), GetThreadPC()); #endif scriptAssertf(ReturnState == ABORTED || bExitFlagMarked == m_bExitFlagResponse, "BackgroundScript \"%s\" did not call BG_SET_EXITFLAG_RESPONSE before aborting!", GetScriptName()); #if __ASSERT scriptAssertf(CTheScripts::GetMissionReplayStats().IsReplayStatsStructureBeingConstructed() == false, "GtaThread::Update - BEGIN_REPLAY_STATS has been called without a matching END_REPLAY_STATS"); #endif // __ASSERT #if __BANK CScriptWidgetTree::CheckWidgetGroupsHaveBeenClosed(GetScriptName()); #endif if (CScriptSaveData::GetCreationOfScriptSaveStructureInProgress()) { scriptAssertf(0, "GtaThread::Update - check that you've called STOP_SAVE_DATA for %s", GetScriptName()); int loop = 0; // Just to be sure that we don't end up stuck in the while loop below forever, // quit after MAX_DEPTH_OF_STACK_OF_STRUCTS attempts. GetCreationOfScriptSaveStructureInProgress() should // return false before this ever happens though. while (CScriptSaveData::GetCreationOfScriptSaveStructureInProgress() && (loop < MAX_DEPTH_OF_STACK_OF_STRUCTS) ) { CScriptSaveData::CloseScriptStruct(this); loop++; } } // reset script hud CScriptHud::AfterThreadUpdate(); // check for no lingering particle asset overrides graphics_commands::VerifyUsePtFxAssetHashName(); return ReturnState; } // PURPOSE: Virtual entry point called by KillAllThreads on each thread. void GtaThread::Kill() { scriptDisplayf("%d GtaThread::Kill - %s \n", fwTimer::GetSystemFrameCount(), GetScriptName()); #if __BANK CScriptDebug::SetComboBoxOfThreadNamesNeedsUpdated(true); #endif // __BANK if (m_Handler) { CTheScripts::GetScriptHandlerMgr().UnregisterScript(*this); } switch(ScriptBrainType) { case CScriptsForBrains::PED_STREAMED: case CScriptsForBrains::SCENARIO_PED: { CPed* pPed = CTheScripts::GetEntityToModifyFromGUID(EntityIndexForScriptBrain, 0); if (pPed) { pPed->SetPedConfigFlag( CPED_CONFIG_FLAG_HasAScriptBrain, false ); } } break; case CScriptsForBrains::OBJECT_STREAMED : { CObject* pObject = CTheScripts::GetEntityToModifyFromGUID(EntityIndexForScriptBrain, 0); if (pObject) { pObject->m_nObjectFlags.ScriptBrainStatus = CObject::OBJECT_WAITING_TILL_OUT_OF_BRAIN_RANGE; // OBJECT_WAITING_TILL_IN_BRAIN_RANGE; } } break; case CScriptsForBrains::WORLD_POINT : { C2dEffect *pEffect = &CModelInfo::GetWorldPointStore().GetEntry(fwFactory::GLOBAL, EntityIndexForScriptBrain);//CWorldPoints::GetWorldPointFromIndex(EntityIndexForScriptBrain); if (pEffect) { pEffect->GetWorldPoint()->BrainStatus = CWorldPoints::WAITING_TILL_OUT_OF_BRAIN_RANGE; // OUT_OF_BRAIN_RANGE; } } break; default: break; } #if __BANK if (pDebugger) { delete pDebugger; // KS WR 163 HACK pDebugger = NULL; } m_WidgetTree.RemoveAllWidgetNodes(); #endif #if GTA_REPLAY if (m_bCleanupReplay) { sm_bTriggerAutoCleanup = true; } #endif // GTA_REPLAY Initialise(); // clears ScriptBrainType and EntityIndexForScriptBrain scrThread::Kill(); } #if GTA_REPLAY bool GtaThread::GetShouldAutoCleanupReplay() { if (sm_bTriggerAutoCleanup) { sm_bTriggerAutoCleanup = false; return true; } else { return false; } } #endif // GTA_REPLAY void GtaThread::KillAllAbortedScriptThreads() { for (int i=0; i(sm_Threads[i]); if ( pThread->GetState()==ABORTED && pThread->IsValid() && !pThread->IsExitFlagResponseSet() ) //< background scripts get killed in BackgroundScripts.cpp { pThread->Kill(); } } } void GtaThread::Initialise() { Assert(!m_Handler); m_Handler = NULL; m_NetComponent = NULL; ForceCleanupPC = 0; ForceCleanupFP = 0; ForceCleanupSP = 0; m_ForceCleanupFlags = 0; m_ForceCleanupTriggered = 0; m_CauseOfMostRecentForceCleanup = 0; InstanceId = -1; // bDoneDeathArrest = false; IsThisAMissionScript = false; m_bIsARandomEventScript = false; m_bIsATriggerScript = false; bSafeForNetworkGame = false; bSetPlayerControlOnInMissionCleanup = true; bClearHelpInMissionCleanup = true; bIsThisAMiniGameScript = false; bThisScriptAllowsNonMiniGameHelpMessages = false; bThreadHasBeenPaused = false; bThisScriptCanBePaused = true; ScriptBrainType = -1; EntityIndexForScriptBrain = 0; bThisScriptCanRemoveBlipsCreatedByAnyScript = false; m_bHasUsedPedPathCommand = false; m_bHasOverriddenVehicleEntering = false; m_bDisplayProfileData = false; m_bExitFlagSet = false; m_bExitFlagResponse = false; m_bIsHighPrio = true; #if GTA_REPLAY m_bCleanupReplay = false; #endif // GTA_REPLAY } void GtaThread::TidyUpMiniGameFlag() { if (scriptVerifyf(bIsThisAMiniGameScript, "Script Name = %s SET_MINIGAME_IN_PROGRESS FALSE - this script is not a minigame - maybe already called this command with FALSE", GetScriptName())) { if (bThisScriptAllowsNonMiniGameHelpMessages) { if (CScriptHud::NumberOfMiniGamesAllowingNonMiniGameHelpMessages > 0) { CScriptHud::NumberOfMiniGamesAllowingNonMiniGameHelpMessages--; } else { scriptAssertf(0, "Script Name = %s SET_MINIGAME_IN_PROGRESS - setting to FALSE - expecting to decrement NumberOfMiniGamesAllowingNonMiniGameHelpMessages but it's already less than 1", GetScriptName()); } bThisScriptAllowsNonMiniGameHelpMessages = false; } if (CTheScripts::GetNumberOfMiniGamesInProgress() > 0) { CTheScripts::SetNumberOfMiniGamesInProgress(CTheScripts::GetNumberOfMiniGamesInProgress() - 1); } else { scriptAssertf(0, "Script Name = %s SET_MINIGAME_IN_PROGRESS - setting to FALSE so expect to decrement NumberOfMiniGamesInProgress but it's already less than 1", GetScriptName()); } bIsThisAMiniGameScript = false; } } bool GtaThread::IsANetworkThread() { return (m_Handler && m_Handler->RequiresANetworkComponent()); } #if !__NO_OUTPUT GtaThread* GtaThread::GetThreadWithThreadId(const scrThreadId threadId) { for(int i = 0; i < sm_Threads.GetCount(); i++) { GtaThread *pGtaThread = static_cast(sm_Threads[i]); // How do we avoid an active mission script being killed (in SA it would have ThisMustBeTheOnlyMissionRunning set) if ( pGtaThread->IsValid() ) { if (threadId == pGtaThread->GetThreadId()) { return pGtaThread; } } } return NULL; } #endif void GtaThread::ForceCleanupForAllMatchingThreads(u32 ForceCleanupFlags, MatchingMode ForceCleanupMatchingMode, const char *pScriptName, const scrThreadId ThreadId) { bool bFoundAMatch = false; GtaThread *pGtaThread = NULL; for(int i = 0; i < sm_Threads.GetCount(); i++) { pGtaThread = static_cast(sm_Threads[i]); if (pGtaThread->IsValid() && (pGtaThread->GetState() != ABORTED)) { bFoundAMatch = false; switch (ForceCleanupMatchingMode) { case MATCH_SCRIPT_NAME : if (stricmp(pGtaThread->GetScriptName(), pScriptName) == 0) { bFoundAMatch = true; } break; case MATCH_THREAD_ID : if (pGtaThread->GetThreadId() == ThreadId) { bFoundAMatch = true; } break; default : bFoundAMatch = true; break; } if (bFoundAMatch) { pGtaThread->ForceCleanup(ForceCleanupFlags); } } } } bool GtaThread::ForceCleanup( u32 ForceCleanupFlags ) { if ((ForceCleanupFlags & m_ForceCleanupFlags) != 0) { if (m_ForceCleanupTriggered == 0) { m_ForceCleanupTriggered = 2; m_CauseOfMostRecentForceCleanup = (ForceCleanupFlags & m_ForceCleanupFlags); return true; } } return false; } void GtaThread::KillAllThreadsWithThisName(const char *pProgramName) { for(int i = 0; i < sm_Threads.GetCount(); i++) { // How do we avoid an active mission script being killed (in SA it would have ThisMustBeTheOnlyMissionRunning set) if ( static_cast(sm_Threads[i])->IsValid() && (stricmp(sm_Threads[i]->GetScriptName(), pProgramName) == 0)) sm_Threads[i]->Kill(); } } // Kill all scripts that haven't been flagged as safe for network game. // Probably only main, debug and cellphone scripts will be flagged as safe /* void GtaThread::KillAllThreadsForNetworkGame(void) { for(int i = 0; i < sm_Threads.GetCount(); i++) { if ( static_cast(sm_Threads[i])->IsValid() && (false == static_cast(sm_Threads[i])->bSafeForNetworkGame) ) { sm_Threads[i]->Kill(); } } } */ // Kill all scripts that have not been flagged as safe for the single player game. /* void GtaThread::KillAllThreadsForSinglePlayerGame(void) { for(int i = 0; i < sm_Threads.GetCount(); i++) { GtaThread * pThread = static_cast(sm_Threads[i]); if (AssertVerify(pThread) && pThread->IsValid() && pThread->m_Handler && pThread->m_Handler->RequiresANetworkComponent() && !static_cast< CGameScriptHandlerNetwork* >(pThread->m_Handler)->ActiveInSinglePlayer()) { sm_Threads[i]->Kill(); } } } */ // All threads are paused. Apart from the one specified. void GtaThread::PauseAllThreadsBarThisOne(GtaThread *pThread) { for(int i = 0; i < sm_Threads.GetCount(); i++) { // How do we avoid an active mission script being killed (in SA it would have ThisMustBeTheOnlyMissionRunning set) if ( static_cast(sm_Threads[i])->IsValid()) { if ( (pThread != sm_Threads[i]) && (static_cast(sm_Threads[i])->bThisScriptCanBePaused) ) { static_cast(sm_Threads[i])->bThreadHasBeenPaused = true; } } } } // All threads are Unpaused. void GtaThread::UnpauseAllThreads() { for(int i = 0; i < sm_Threads.GetCount(); i++) { // How do we avoid an active mission script being killed (in SA it would have ThisMustBeTheOnlyMissionRunning set) if ( static_cast(sm_Threads[i])->IsValid()) { static_cast(sm_Threads[i])->bThreadHasBeenPaused = false; } } } // Returns true if any active scripts are not safe for network game. // Once this function returns false, the game knows that all ambient scripts have been shut down and it is safe to launch the network game. /* bool GtaThread::AreThereAnyNonNetworkScriptsRunning(void) { for(int i = 0; i < sm_Threads.GetCount(); i++) { if ( static_cast(sm_Threads[i])->IsValid() && (sm_Threads[i]->GetState() != ABORTED) && (false == static_cast(sm_Threads[i])->bSafeForNetworkGame) ) { return true; } } return false; } */ s32 GtaThread::CountActiveThreads() { s32 number_of_threads = 0; for(int i = 0; i < sm_Threads.GetCount(); i++) { const GtaThread *pThread = static_cast(sm_Threads[i]); if (pThread->IsValid() && (pThread->GetState() != ABORTED)) { number_of_threads++; } } return number_of_threads; } s32 GtaThread::CountActiveThreadsWithThisProgramID(const scrProgramId programid) { s32 number_of_threads = 0; for(int i = 0; i < sm_Threads.GetCount(); i++) { const GtaThread *pThread = static_cast(sm_Threads[i]); if (pThread->IsValid() && (pThread->GetState() != ABORTED) && (pThread->GetProgram() == programid) ) { number_of_threads++; } } return number_of_threads; } u32 GtaThread::CountSinglePlayerOnlyScripts() { u32 number_of_threads = 0; for(int i = 0; i < sm_Threads.GetCount(); i++) { GtaThread *pCurrentGtaThread = static_cast(sm_Threads[i]); if ( pCurrentGtaThread->IsValid() && (pCurrentGtaThread->GetState() != ABORTED) && (!pCurrentGtaThread->IsANetworkThread()) && (!pCurrentGtaThread->bSafeForNetworkGame) ) { if (pCurrentGtaThread->GetProgramCounter(0) != 0) { scriptAssertf((pCurrentGtaThread->m_ForceCleanupFlags & FORCE_CLEANUP_FLAG_SP_TO_MP) != 0, "GtaThread::CountSinglePlayerOnlyScripts - single player script %s doesn't handle FORCE_CLEANUP_FLAG_SP_TO_MP", pCurrentGtaThread->GetScriptName()); } scriptDisplayf("Single player script %s is running", pCurrentGtaThread->GetScriptName()); number_of_threads++; } } scriptDisplayf("%d single player scripts are running", number_of_threads); return number_of_threads; } void GtaThread::SetForceCleanupReturnPoint(u32 ForceCleanupBitField) { if(SCRIPT_VERIFY(ForceCleanupPC == 0, "%s:HAS_FORCE_CLEANUP_OCCURRED has already been called for this script - you can only call it once")) { ForceCleanupPC = (m_Serialized.m_PC + c_NativeInsnLength); ForceCleanupFP = m_Serialized.m_FP; ForceCleanupSP = m_Serialized.m_SP; m_ForceCleanupFlags = ForceCleanupBitField; m_CauseOfMostRecentForceCleanup = 0; } } ///////////////////////////////////////////////////////////////////////////////// // FUNCTION : DoForceCleanupCheck // PURPOSE : Performs the ForceCleanup check if it has been enabled ///////////////////////////////////////////////////////////////////////////////// void GtaThread::DoForceCleanupCheck() { if (ForceCleanupPC) // && CTheScripts::PlayerIsOnAMission) { scriptAssertf(FindPlayerPed(), "%s:CRunningScript::DoForceCleanupCheck - Player exists but doesn't have a character pointer", GetScriptName()); bool bRunForceCleanupCode = false; #if __DEV bool bPlayerIsDeadOrArrested = false; #endif CPed * player = FindPlayerPed(); if (player && !CTheScripts::IsPlayerPlaying(player)) { if (m_ForceCleanupFlags & FORCE_CLEANUP_FLAG_PLAYER_KILLED_OR_ARRESTED) { // If this script is set up to listen for the death/arrest event if (!NetworkInterface::IsNetworkOpen()) { // For network games, death and arrest don't actually trigger the ForceCleanup code. // Only the FORCE_CLEANUP command will trigger the ForceCleanup code bRunForceCleanupCode = true; m_CauseOfMostRecentForceCleanup = FORCE_CLEANUP_FLAG_PLAYER_KILLED_OR_ARRESTED; } } #if __DEV bPlayerIsDeadOrArrested = true; #endif } if ( (!bRunForceCleanupCode) && m_ForceCleanupTriggered) // CTheScripts::sm_FakeDeatharrest ) { scriptDisplayf("%d GtaThread::DoForceCleanupCheck - ForceCleanup has triggered for %s \n", fwTimer::GetSystemFrameCount(), GetScriptName()); bRunForceCleanupCode = true; #if __DEV if (player && !bPlayerIsDeadOrArrested) { if (m_ForceCleanupFlags & FORCE_CLEANUP_FLAG_PLAYER_KILLED_OR_ARRESTED) { // only set this flag if the script has been set up to listen for the player being killed/arrested player->m_nDEflags.bCheckedForDead = TRUE; } } #endif } if (bRunForceCleanupCode) { // got to deal with the player being arrested/dead.... Joy... m_Serialized.m_PC = ForceCleanupPC; m_Serialized.m_FP = ForceCleanupFP; m_Serialized.m_SP = ForceCleanupSP; Pushi(true); // Clear any blocked execution flag SetThreadBlockState(false); // Reset these variables now - the script should really have been written to terminate within this frame // but if not, the ForceCleanup would re-trigger again next frame ForceCleanupPC = 0; ForceCleanupFP = 0; ForceCleanupSP = 0; } else { #if __DEV if (m_ForceCleanupFlags & FORCE_CLEANUP_FLAG_PLAYER_KILLED_OR_ARRESTED) { // only set this flag if the script has been set up to listen for the player being killed/arrested FindPlayerPed()->m_nDEflags.bCheckedForDead = TRUE; } #endif } } if (m_ForceCleanupTriggered) { m_ForceCleanupTriggered--; } } int GtaThread::GetIDForThisLineInThisThread() { // int ReturnID = ((int) m_Serialized.m_ThreadId) << 16; int ReturnID = (int) fwIdKeyGenerator::Get((void*)m_Serialized.m_ThreadId); ReturnID += m_Serialized.m_PC; return ReturnID; } void GtaThread::ScriptThreadIteratorReset() { ScriptThreadIteratorIndex = 0; } s32 GtaThread::ScriptThreadIteratorGetNextThreadId() { while (ScriptThreadIteratorIndex < sm_Threads.GetCount()) { GtaThread *pGtaThread = static_cast(sm_Threads[ScriptThreadIteratorIndex]); if (pGtaThread->IsValid() && (pGtaThread->GetState() != ABORTED)) { s32 returnIndex = static_cast(pGtaThread->GetThreadId()); ScriptThreadIteratorIndex++; return returnIndex; } else { ScriptThreadIteratorIndex++; } } return 0; } void GtaThread::SetHasLoadedAllPathNodes(scrThreadId threadID) { Assertf(ms_LastPathNodeScriptThreadID == THREAD_INVALID || threadID == ms_LastPathNodeScriptThreadID, // Script calls repeatedly, until the nodes are reported as loaded. "GtaThread: Thread <%s> trying to load all path nodes, when another thread <%s> has already loaded them!", scrThread::GetThread( threadID )->GetScriptName(), scrThread::GetThread( ms_LastPathNodeScriptThreadID ) ? scrThread::GetThread( ms_LastPathNodeScriptThreadID )->GetScriptName():"Unknown"); ms_LastPathNodeScriptThreadID = threadID; } void GtaThread::ClearHasLoadedAllPathNodes(ASSERT_ONLY(scrThreadId threadID)) { Assertf(ms_LastPathNodeScriptThreadID == THREAD_INVALID || threadID == ms_LastPathNodeScriptThreadID, "GtaThread: Thread <%s> trying to clear path nodes, not thread <%s> that allocated them!", scrThread::GetThread( threadID )->GetScriptName(), scrThread::GetThread( ms_LastPathNodeScriptThreadID ) ? scrThread::GetThread( ms_LastPathNodeScriptThreadID )->GetScriptName():"Unknown"); ms_LastPathNodeScriptThreadID = THREAD_INVALID; } #if __ASSERT void GtaThread::VerifyNetworkSafety() { if (CGameLogic::GetCurrentLevelIndex() == 1) { // Only check this on the proper GTA5 level - not on the testbed levels if (IsValid() && (GetState() != ABORTED) ) { if (NetworkInterface::IsGameInProgress()) { // Graeme - I'll have to leave this commented out for now because single player scripts do exist for the first // few frames of the network session. They don't kill themselves until main_persistent.sc calls FORCE_CLEANUP(FORCE_CLEANUP_FLAG_SP_TO_MP) // scriptVerifyf( IsANetworkThread() || bSafeForNetworkGame, // "GtaThread::VerifyNetworkSafety - %s is a single player script that hasn't called NETWORK_SET_SCRIPT_IS_SAFE_FOR_NETWORK_GAME but it's running during a multiplayer game. Does it handle FORCE_CLEANUP_FLAG_SP_TO_MP properly?", GetScriptName()); } else { bool bCleansUpWhenEnteringMP = false; if ((m_ForceCleanupFlags & FORCE_CLEANUP_FLAG_SP_TO_MP) != 0) { bCleansUpWhenEnteringMP = true; } bool bIsANetworkScript = false; if (IsANetworkThread()) { // scriptAssertf(0, "GtaThread::VerifyNetworkSafety - %s is a network script that is running during the single player session. Is this valid?", GetScriptName()); scriptAssertf(!bCleansUpWhenEnteringMP, "GtaThread::VerifyNetworkSafety - %s is a network script that also handles FORCE_CLEANUP_FLAG_SP_TO_MP", GetScriptName()); bIsANetworkScript = true; } if (bSafeForNetworkGame) { scriptAssertf(!bCleansUpWhenEnteringMP, "GtaThread::VerifyNetworkSafety - %s shouldn't call NETWORK_SET_SCRIPT_IS_SAFE_FOR_NETWORK_GAME and also handle FORCE_CLEANUP_FLAG_SP_TO_MP", GetScriptName()); } scriptAssertf( bCleansUpWhenEnteringMP || bIsANetworkScript || bSafeForNetworkGame, "GtaThread::VerifyNetworkSafety - %s is a single player script that hasn't called NETWORK_SET_SCRIPT_IS_SAFE_FOR_NETWORK_GAME and doesn't handle FORCE_CLEANUP_FLAG_SP_TO_MP", GetScriptName()); } } // if (IsValid() && (GetState() != ABORTED) ) } // if (CGameLogic::GetCurrentLevelIndex() == 1) } #endif // __ASSERT s32* GtaThread::GetCodeStackSize( const atHashValue& name ) { if(name==ATSTRINGHASH("DEFAULT",0xe4df46d5)) return &sm_DefaultStackSize; else if(name==ATSTRINGHASH("MISSION",0x8bc157a3)) return &sm_MissionStackSize; #if !__FINAL else if(name==ATSTRINGHASH("SOAK_TEST",0x1E2F6CC)) return &sm_SoakTestStackSize; else if(name==ATSTRINGHASH("NETWORK_BOT",0x908A0827)) return &sm_NetworkBotStackSize; else if(name==ATSTRINGHASH("DEBUG_MENU",0x458318E2)) return &sm_DebugMenuStackSize; #endif //!__FINAL else return 0; } bool GtaThread::IsDebugStack( const atHashValue& name ) { return name==ATSTRINGHASH("SOAK_TEST",0x1E2F6CC) || name==ATSTRINGHASH("NETWORK_BOT",0x908A0827) || name==ATSTRINGHASH("DEBUG_SCRIPT",0xA0E550E4) || name==ATSTRINGHASH("DEBUG_MENU",0x458318E2); } #if __BANK void GtaThread::AllocateDebugger() { if (scriptVerifyf(pDebugger == NULL, "%s:AllocateDebugger - thread already has a debugger", GetScriptName())) { if (CGameLogic::GetCurrentLevelIndex() >= 1 && CGameLogic::GetCurrentLevelIndex() <= 5) { CFileMgr::SetDir(CScriptDebug::GetScriptDebuggerPath()); pDebugger = rage_new scrDebugger(GetScriptName(), m_Serialized.m_ThreadId, NULL, false); CFileMgr::SetDir(""); } } } void GtaThread::AllocateDebuggerForThread(scrThreadId ThreadID) { scrThread *pThreadToDebug = GetThread(ThreadID); if (scriptVerifyf(pThreadToDebug, "AllocateDebuggerForThread - can't find thread with the given ID")) { ( static_cast(pThreadToDebug) )->AllocateDebugger(); } } void GtaThread::AllocateDebuggerForThreadInSlot(s32 ArrayIndex) { if (scriptVerifyf( (ArrayIndex >= 0) && (ArrayIndex < sm_Threads.GetCount()), "GtaThread::AllocateDebuggerForThreadInSlot - array index %d is out of range 0 to %d", ArrayIndex, sm_Threads.GetCount())) { GtaThread *pGtaThread = static_cast(sm_Threads[ArrayIndex]); if (pGtaThread->IsValid() && (pGtaThread->GetState() != ABORTED)) { pGtaThread->AllocateDebugger(); } } } // Clear the m_bDisplayProfileData flag for all script threads except the one in the specified slot void GtaThread::EnableProfilingForThreadInSlot(s32 ArrayIndex) { if (scriptVerifyf( (ArrayIndex >= 0) && (ArrayIndex < sm_Threads.GetCount()), "GtaThread::EnableProfilingForThreadInSlot - array index %d is out of range 0 to %d", ArrayIndex, sm_Threads.GetCount())) { for(int i = 0; i < sm_Threads.GetCount(); i++) { GtaThread *pCurrentGtaThread = static_cast(sm_Threads[i]); if (pCurrentGtaThread) { if (i == ArrayIndex) { pCurrentGtaThread->m_bDisplayProfileData = true; } else { pCurrentGtaThread->m_bDisplayProfileData = false; } } } } } void GtaThread::UpdateArrayOfNamesOfRunningThreads(atArray &ArrayOfThreadNames) { static char EmptyEntry[4] = "---"; const s32 SizeOfNamesArray = ArrayOfThreadNames.GetCount(); const s32 TotalNumberOfThreads = sm_Threads.GetCount(); scriptAssertf((SizeOfNamesArray == 0) || (SizeOfNamesArray == TotalNumberOfThreads), "GtaThread::UpdateArrayOfNamesOfRunningThreads - array of script names has size %d. Expected it to be 0 or %d", SizeOfNamesArray, TotalNumberOfThreads); if (SizeOfNamesArray != TotalNumberOfThreads) { ArrayOfThreadNames.Reset(); ArrayOfThreadNames.Resize(TotalNumberOfThreads); } for (int i=0; i(sm_Threads[i]); if (pGtaThread->IsValid() && (pGtaThread->GetState() != ABORTED)) { ArrayOfThreadNames[i] = pGtaThread->GetScriptName(); } else { ArrayOfThreadNames[i] = EmptyEntry; } } } #endif #if __BANK void GtaThread::DisplayAllRunningScripts(bool bOutputToScreen) { for (int i=0; iGetThreadId()) { int InstructionsUsed = sm_Threads[i]->GetInstructionsUsed(); int MaxStackUseForThisThread = -1; #if __DEV MaxStackUseForThisThread = sm_Threads[i]->GetMaxStackUse(); #endif int CurrentStackSize = ((GtaThread*) sm_Threads[i])->GetThreadSP(); int MaxAllowedStackSize = ((GtaThread*) sm_Threads[i])->GetAllocatedStackSize(); const u32 MaxLengthOfStateString = 12; char StateString[MaxLengthOfStateString]; safecpy(StateString, "", MaxLengthOfStateString); switch (sm_Threads[i]->GetState()) { case RUNNING : safecpy(StateString, "RUNNING", MaxLengthOfStateString); break; case BLOCKED : // Don't bother saying that the thread is BLOCKED since this is the normal state break; case ABORTED : safecpy(StateString, "ABORTED", MaxLengthOfStateString); break; case HALTED : safecpy(StateString, "HALTED", MaxLengthOfStateString); break; default : scriptAssertf(0, "GtaThread::DisplayAllRunningScripts - unknown thread state"); break; } const u32 MaxLengthOfNetworkString = 12; char NetworkString[MaxLengthOfNetworkString]; safecpy(NetworkString, "", MaxLengthOfNetworkString); if (((GtaThread*) sm_Threads[i])->IsANetworkThread()) { safecat(NetworkString, "[N]", MaxLengthOfNetworkString); } if (((GtaThread*) sm_Threads[i])->bSafeForNetworkGame) { safecat(NetworkString, "[NS]", MaxLengthOfNetworkString); } if (bOutputToScreen) { grcDebugDraw::AddDebugOutput("%s%s(Id=%d) InstCount=%d CurrStack=%d MaxStackSoFar=%d StackLimit=%d %s", NetworkString, sm_Threads[i]->GetScriptName(), sm_Threads[i]->GetThreadId(), InstructionsUsed, CurrentStackSize, MaxStackUseForThisThread, MaxAllowedStackSize, StateString); } else { Displayf("%s%s(Id=%d) InstCount=%d CurrStack=%d MaxStackSoFar=%d StackLimit=%d %s\n", NetworkString, sm_Threads[i]->GetScriptName(), sm_Threads[i]->GetThreadId(), InstructionsUsed, CurrentStackSize, MaxStackUseForThisThread, MaxAllowedStackSize, StateString); } } } } #endif