Files
GTASource/game/frontend/CScriptMenu.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

361 lines
11 KiB
C++

#include "CScriptMenu.h"
#include "parsercore/element.h"
#include "Parsercore/attribute.h"
#include "script/script_channel.h"
#include "script/script.h"
#include "script/script_hud.h"
#include "Script/gta_thread.h"
#include "script/streamedscripts.h"
#include "streaming/streamingvisualize.h"
#include "game/config.h"
XPARAM(scriptProtectGlobals);
#if __ASSERT
#include "control/gamelogic.h"
#endif
FRONTEND_MENU_OPTIMISATIONS()
#define __CHECK_FOR_CLEANUPS 0
#undef __script_channel
#define __script_channel script
CScriptMenu::CScriptMenu(CMenuScreen& owner) : CMenuBase(owner)
, m_MyProgramId(srcpidNONE)
, m_MyThreadId(THREAD_INVALID)
, m_pMyThread(NULL)
, m_MyStreamRequest(-1)
, m_StackSize(0)
#if __BANK
, m_pMyGroup(NULL)
#endif
{
owner.FindDynamicData("path", m_ScriptPath);
owner.FindDynamicData("handlesButtons", m_bHasButtonLogic);
owner.FindDynamicData("continual", m_bContinualUpdateMode);
owner.FindDynamicData("canpersist", m_bAllowToLive);
const char* pszStack = NULL;
bool bWasSpecified = owner.FindDynamicData("stack", pszStack, "PAUSE_MENU_SCRIPT");
atHashValue stackSize(pszStack);
// do a lookup
const CConfigScriptStackSizes& configData = CGameConfig::Get().GetConfigScriptStackSizes();
for(int i=0; i < configData.m_StackSizeData.GetCount(); ++i )
{
if( configData.m_StackSizeData[i].m_StackName == stackSize )
{
m_StackSize = configData.m_StackSizeData[i].m_SizeOfStack;
break;
}
}
if( bWasSpecified && !scriptVerifyf(m_StackSize!=0, "Unable to find a match for stack='%s', reverting to default size of %i", pszStack, GtaThread::GetDefaultStackSize() ) )
m_StackSize = GtaThread::GetDefaultStackSize();
}
void CScriptMenu::Init()
{
// punt off the streaming request a bit early so it may be ready later
HandleStreaming();
}
CScriptMenu::~CScriptMenu()
{
#if __CHECK_FOR_CLEANUPS
if(m_pMyThread != NULL)
scriptErrorf("CScriptMenu::~CScriptMenu - expected the script thread to have already been killed by an earlier call to LoseFocus(). We'll tidy this up... for now.");
scriptAssertf(m_QueuedActions.GetCount() == 0, "CScriptMenu::~CScriptMenu (%s) - expected the QueuedActions array to have already been Reset by an earlier call to LoseFocus()", m_Owner.MenuScreen.GetParserName());
#endif
KillScript();
if( m_MyStreamRequest != -1 )
{
g_StreamedScripts.ClearRequiredFlag(m_MyStreamRequest, STRFLAG_MISSION_REQUIRED);
}
m_MyProgramId = srcpidNONE;
#if __BANK
if( m_pMyGroup )
m_pMyGroup->Destroy();
#endif
}
#if !__FINAL
void CScriptMenu::Render(const PauseMenuRenderDataExtra* UNUSED_PARAM(renderData))
{
if( m_bContinualUpdateMode && GetScriptPath().length() == 0 )
{
CTextLayout DeadTextDebug;
DeadTextDebug.SetScale(Vector2(0.5f, 1.0f));
DeadTextDebug.SetColor( Color32(0xFFE15050) );
DeadTextDebug.Render( Vector2(0.27f, 0.35f), "The script of this page");
DeadTextDebug.Render( Vector2(0.27f, 0.42f), "Stopped unexpectedly");
DeadTextDebug.Render( Vector2(0.27f, 0.49f), " Forever broken");
CText::Flush();
}
}
#endif
void CScriptMenu::LaunchScript( const SPauseMenuScriptStruct& args )
{
atString& scriptPath = GetScriptPath();
// do nothing if there's no script path
if( scriptPath.length() == 0 )
return;
#if RSG_PC && ENABLE_SCRIPT_TAMPER_CHECKER
CScriptGlobalTamperChecker::UncheckedScope uncheckedScope;
#endif
// script isn't running, boot it up
if( !m_pMyThread )
{
if( m_MyProgramId != srcpidNONE )
{
scriptDisplayf("[ScriptMenu] Launching script '%s' with Program 0x%08x and stack size of %i.", scriptPath.c_str(), m_MyProgramId, m_StackSize);
scriptDisplayf("[ScriptMenu] Args of eType: %i\t\tMenuScreenId: %i '%s'\t\tPreviousId: %i '%s'\t\tUniqueId: %i"
, args.eTypeOfInteraction.Int
, args.MenuScreenId.Int, MenuScreenId(args.MenuScreenId.Int).GetParserName()
, args.PreviousId.Int, MenuScreenId(args.PreviousId.Int).GetParserName()
, args.iUniqueId.Int);
m_MyThreadId = CTheScripts::GtaStartNewThreadWithProgramId(m_MyProgramId, &args, sizeof(args), m_StackSize, scriptPath.c_str() );
m_pMyThread = static_cast<GtaThread*>( scrThread::GetThread(m_MyThreadId) );
m_pMyThread->SetThreadPriority(scrThread::THREAD_PRIO_MANUAL_UPDATE);
if (m_pMyThread == NULL)
{
#if __ASSERT
scriptAssertf(0, "CScriptMenu::LaunchScript - Could not get a pointer to the new thread %s", scriptPath.c_str());
#else
scriptErrorf("CScriptMenu::LaunchScript - Could not get a pointer to the new thread %s", scriptPath.c_str());
#endif // __ASSERT
}
}
// program's not loaded yet, queue up this action for later
// else if( !m_bContinualUpdateMode )
else
{
m_QueuedActions.PushAndGrow(args,4);
scriptDisplayf("[ScriptMenu] %s: Queued up desired action #0: T%i M%i P%i U%i", m_Owner.MenuScreen.GetParserName(), args.eTypeOfInteraction.Int, args.MenuScreenId.Int, args.PreviousId.Int, args.iUniqueId.Int);
}
} // </Creation>
bool bScriptHasAborted = false;
if(m_pMyThread)
{
scrThread::State endResult = m_pMyThread->Update(DEFAULT_INSTRUCTION_COUNT);
bScriptHasAborted = (endResult == scrThread::ABORTED);
if( bScriptHasAborted && m_bContinualUpdateMode )
{
scriptAssertf(0, "Unexpected termination of %s! Never running it again!", GetScriptPath().c_str() );
// nuke the script path so it doesn't run again (anymore)
GetScriptPath().Reset();
}
}
if( (!m_bAllowToLive && !m_bContinualUpdateMode) || bScriptHasAborted)
KillScript();
}
void CScriptMenu::KillScript()
{
if(m_pMyThread)
{
// Script thread can be killed externally during SHUTDOWN_SESSION, so validate ID before proceeding
if(m_pMyThread->GetThreadId() != THREAD_INVALID)
{
#if RSG_PC && ENABLE_SCRIPT_TAMPER_CHECKER
CScriptGlobalTamperChecker::UncheckedScope uncheckedScope;
#endif
// if the script is continually updating, give it one last chance to clean up (if it so desires)
if(m_bContinualUpdateMode || m_bAllowToLive)
{
if( m_pMyThread->ForceCleanup(GtaThread::FORCE_CLEANUP_PAUSE_MENU_TERMINATED) )
m_pMyThread->Update(DEFAULT_INSTRUCTION_COUNT);
}
m_pMyThread->Kill();
}
m_pMyThread = NULL;
}
m_MyThreadId = THREAD_INVALID;
}
bool CScriptMenu::Populate(MenuScreenId newScreenId)
{
if( !m_bContinualUpdateMode )
{
SPauseMenuScriptStruct newArgs(kFill, newScreenId.GetValue(), -1,-1);
LaunchScript(newArgs);
}
return false;
}
void CScriptMenu::LoseFocus()
{
KillScript();
m_QueuedActions.Reset();
}
void CScriptMenu::LayoutChanged( MenuScreenId iPreviousLayout, MenuScreenId iNewLayout, s32 iUniqueId, eLAYOUT_CHANGED_DIR UNUSED_PARAM(eDir) )
{
if( !m_bContinualUpdateMode )
{
SPauseMenuScriptStruct newArgs(kLayoutChange, iNewLayout.GetValue(), iPreviousLayout.GetValue(), iUniqueId);
LaunchScript(newArgs);
}
}
bool CScriptMenu::TriggerEvent(MenuScreenId MenuId, s32 iUniqueId)
{
if( !m_bContinualUpdateMode )
{
SPauseMenuScriptStruct newArgs(kTriggerEvent, MenuId.GetValue(), -1, iUniqueId);
LaunchScript(newArgs);
return true;
}
return false;
}
void CScriptMenu::PrepareInstructionalButtons( MenuScreenId MenuId, s32 iUniqueId)
{
if( !m_bHasButtonLogic )
return;
if( !m_bContinualUpdateMode )
{
SPauseMenuScriptStruct newArgs(kPrepareButtons, MenuId.GetValue(), -1, iUniqueId);
LaunchScript(newArgs);
}
}
void CScriptMenu::HandleStreaming()
{
atString& scriptPath = GetScriptPath();
// bail if we have nothing to stream OR the program's ready
if( scriptPath.length() <= 0 || m_MyProgramId != srcpidNONE)
return;
// check if it's resident
m_MyProgramId = scrProgram::IsCompiledAndResident(scriptPath);
if( m_MyProgramId == srcpidNONE )
{
STRVIS_AUTO_CONTEXT(strStreamingVisualize::SCRIPTMENU);
// nope. Check if it's streamed
m_MyStreamRequest = g_StreamedScripts.FindSlot(scriptPath.c_str()).Get();
ASSERT_ONLY(bool bIgnoreMissingScripts = CGameLogic::GetCurrentLevelIndex() != 1);
scriptAssertf(m_MyStreamRequest != -1 || bIgnoreMissingScripts, "%s:Script doesn't exist", scriptPath.c_str() );
if( m_MyStreamRequest == -1 )
{
// bad file, just clear it out and give up forever
scriptPath.Reset();
// wipe out the FILL_CONTENT_SCRIPT flag
for( int i=0; i < m_Owner.MenuItems.GetCount(); ++i )
{
if( m_Owner.MenuItems[i].MenuAction == MENU_OPTION_ACTION_FILL_CONTENT_FROM_SCRIPT )
m_Owner.MenuItems.Delete(i);
}
return;
}
// if we can fetch its program, it's streamed.
if( scrProgram* pProgram = g_StreamedScripts.Get(strLocalIndex(m_MyStreamRequest)) )
{
m_MyProgramId = pProgram->GetProgramId();
g_StreamedScripts.SetRequiredFlag(m_MyStreamRequest, STRFLAG_MISSION_REQUIRED);
scriptDisplayf("[ScriptMenu] Program of 0x%08x found for '%s' ", m_MyProgramId, scriptPath.c_str());
}
else
{
// otherwise, request that shiz.
g_StreamedScripts.StreamingRequest(strLocalIndex(m_MyStreamRequest), STRFLAG_PRIORITY_LOAD|STRFLAG_MISSION_REQUIRED);
scriptDisplayf("[ScriptMenu] Requesting '%s' with request of %i", scriptPath.c_str(), m_MyStreamRequest);
}
}
else
{
scriptDisplayf("[ScriptMenu] Script '%s' is compiled and resident! Using that program id 0x%08x", scriptPath.c_str(), m_MyProgramId);
}
}
void CScriptMenu::Update()
{
#if __ASSERT
CScriptHud::bLockScriptrendering = !NetworkInterface::IsGameInProgress();
#endif
// check if streamed and stream it if not
HandleStreaming();
if(m_bContinualUpdateMode || (m_bAllowToLive && m_pMyThread))
{
// need to reset the per frame hud flags
if( !CTheScripts::ShouldBeProcessed())
CScriptHud::ClearMapVisibilityPerFrameFlags();
SPauseMenuScriptStruct newArgs(kUpdate, -1, CPauseMenu::GetMenuceptionForceFocus().GetValue(), -1);
LaunchScript(newArgs);
}
// if we had to queue any actions, fire them all off now
// assuming that we don't already have a thread (rare), and our program's streamed
else if( !m_pMyThread && m_MyProgramId != srcpidNONE && !m_QueuedActions.empty() )
{
for( int i=0; i < m_QueuedActions.GetCount(); ++i )
{
scriptDisplayf("[ScriptMenu] %s: Blowing away Queued Action #i: T%i M%i P%i U%i", m_Owner.MenuScreen.GetParserName(), m_QueuedActions[i].eTypeOfInteraction.Int, m_QueuedActions[i].MenuScreenId.Int, m_QueuedActions[i].PreviousId.Int, m_QueuedActions[i].iUniqueId.Int);
LaunchScript(m_QueuedActions[i]);
}
m_QueuedActions.Reset();
}
#if __ASSERT
CScriptHud::bLockScriptrendering = false;
#endif
}
#if __BANK
void CScriptMenu::AddWidgets(bkBank* pBank)
{
if(m_pMyGroup)
return;
atString name("Scripted Bank: ");
name += m_Owner.MenuScreen.GetParserName();
m_pMyGroup = pBank->PushGroup(name);
{
// not ENTIRELY sure how safe this is
if( m_ScriptPath.length() > 0)
pBank->AddROText("Script Path: ", m_ScriptPath.c_str(), m_ScriptPath.length());
else
pBank->AddTitle("Script Path: -None-");
pBank->AddText("Stream Request", &m_MyStreamRequest);
if( sizeof(scrProgramId) == sizeof(int) )
pBank->AddText("Program ID", reinterpret_cast<int*>(&m_MyProgramId));
pBank->AddText("Thread ID", reinterpret_cast<int*>(&m_MyThreadId));
pBank->AddText("Continual Update", &m_bContinualUpdateMode);
pBank->AddSlider("Queued Action Count", m_QueuedActions.GetCountPointer(), 0, 5, 0 );
}
pBank->PopGroup();
}
#endif