/////////////////////////////////////////////////////////////////////////////////////// // // script.cpp KSH 1 Nov 2001 // // CScript class member functions and misc script functions. // /////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include // for SKATE_TYPE_SKATER #include #include // For Net::Client class #ifndef __PLAT_WN32__ #include // For GameNet::Manager #endif #include #ifdef __SCRIPT_EVENT_TABLE__ #include #include #endif //char foo[sizeof(Script::SReturnAddress)/0]; //#define SEND_SCRIPT_NAMES_TO_DEBUGGER DefinePoolableClass(Script::CScript); namespace Obj { #ifdef __NOPT_ASSERT__ extern bool DebugSkaterScripts; #endif } namespace Script { static CScript * sCurrentlyUpdating = NULL; // Parse.cpp needs this, for getting the script pointer to send to any cfunc it // encounters when evaluating an expression. // It also uses it when processing RandomNoRepeat tokens. CScript *GetCurrentScript() { return sCurrentlyUpdating; } #ifdef __NOPT_ASSERT__ // for debugging, return the info of script we are currently updating // as the function that Asserts by it might not have a pointer // to the script const char *GetCurrentScriptInfo() { if (sCurrentlyUpdating) { return sCurrentlyUpdating->GetScriptInfo(); } else { return "No Script currently updating"; } } #ifdef SEND_SCRIPT_NAMES_TO_DEBUGGER static void s_send_script_name(uint32 script_name, int numReturnAddresses) { static uint32 sp_ignored_script_names[]= { CRCC(0x1f8909cb,"timeupscript"), CRCC(0x1aef674f,"game_update"), CRCC(0x18b3b291,"waitonegameframe"), CRCC(0x7c4264dd,"checkforswitchvehicles"), CRCC(0x98f4c454,"just_coasting"), CRCC(0x30e72e33,"do_random_effect"), CRCC(0x7737d7b8,"do_random_effect2"), CRCC(0x99443e63,"do_blur_effect"), CRCC(0x3e6ab5cb,"do_blur_effect_accept"), CRCC(0x9c9dfcf8,"disable_two_player_option"), CRCC(0x380ec7a7,"playbrakeanim"), CRCC(0x838116e9,"playbrakeidle"), CRCC(0x7ae0d8aa,"handbrake"), CRCC(0x9f6a63e3,"playturnanimorturnidle"), CRCC(0x642587a0,"cessbrake"), CRCC(0x603ea7f3,"waitanimfinished"), CRCC(0x76792736,"checkfornetbrake"), CRCC(0xa4f522e9,"parked_make_set_menu_item"), CRCC(0xbeed50b6,"make_text_sub_menu_item"), CRCC(0x277b7b33,"parked_make_piece_menu_item"), CRCC(0x3ee4627d,"doapush"), CRCC(0x493a52f,"helper_text_update_element"), CRCC(0x5479735a,"show_panel_message"), CRCC(0x7a449180,"DestroyMemStatsScreenElements"), CRCC(0x901fefcf,"setexception"), CRCC(0xe9242afd,"vibrateoff"), CRCC(0xc4c8c400,"switchonboard"), CRCC(0xf8d27a88,"switchoffboard"), CRCC(0x1195c40a,"bloodparticlesoff"), CRCC(0x1946686b,"skaterbloodoff"), CRCC(0x243e716,"verifyparam"), CRCC(0x2c0c9e7b,"onexceptionrun"), CRCC(0x64df5e4c,"WaitAnimWhilstChecking"), CRCC(0xcf14cc35,"ClothingLandBounce"), CRCC(0x46f7302f,"OnGroundExceptions"), CRCC(0x500eb95b,"InAirExceptions"), CRCC(0x10da1c88,"ClearExceptions"), CRCC(0xaaef4fb5,"ClearOnExceptionRun"), CRCC(0xac281817,"model_hide_geom"), CRCC(0x3b9039a5,"process_cas_command"), CRCC(0xdea7fe56,"goal_editor_update_cursor_position"), }; uint32 *p_ignored_script_names=sp_ignored_script_names; for (uint32 i=0; iStreamMessage(&msg); } #endif #endif static CScript *sp_scripts=NULL; static uint32 s_num_cscripts=0; static bool s_done_one_per_frame; int CScript::s_next_unique_id = 0; uint32 GetNumCScripts() { return s_num_cscripts; } // For use when external functions want to step through all existing scripts. // Pass it NULL to get the first CScript. // Should probably eventually change all the script code to use the iterators in the // standard container library at some point I guess. CScript *GetNextScript(CScript *p_script) { if (p_script==NULL) { // They want the first CScript return sp_scripts; } else { return p_script->mp_next; } } CScript::CScript() { // Zero all the members. CScript is not derived from CClass so we don't get the auto-zeroing. uint32 size=sizeof(CScript); Dbg_MsgAssert((size&3)==0,("sizeof(CScript) not a multiple of 4 ??")); size>>=2; uint32 *p_longs=(uint32*)this; for (uint32 i=0; imp_previous=this; } sp_scripts=this; ++s_num_cscripts; m_unique_id = s_next_unique_id++; mNode = -1; #ifdef __NOPT_ASSERT__ m_last_instruction_time_taken=-1.0f; m_total_instruction_time_taken=0.0f; #endif #if 0 // if we go over 100 scripts, tell us why static uint32 max = 100; if (s_num_cscripts > max) { max = s_num_cscripts; printf ("Record number of scripts: %d of %d\nvvvvvvvvvvvvvvvvvvvvvvvvvvv\n",CScript::SGetNumUsedItems(),CScript::SGetTotalItems()); Script::DumpScripts(); DumpUnwindStack(20,0); printf (" ^^^^^^^^^^^^^^^^^^^^^^^^\nRecord number of scripts: %d of %d\n",CScript::SGetNumUsedItems(),CScript::SGetTotalItems()); } #endif #ifdef __SCRIPT_EVENT_TABLE__ mp_event_handler_table = NULL; #endif mp_return_addresses=mp_return_addresses_small_buffer; mp_loops=mp_loops_small_buffer; } CScript::~CScript() { ClearScript(); if (mpCallbackScriptParams) { delete mpCallbackScriptParams; } // Check to see if the script has already been deleted // perhaps we are deleteing it twice??? Dbg_MsgAssert(mp_next != (CScript *)-1,("mp_next is -1 in script we are trying to delete!!!")); Dbg_MsgAssert(mp_previous != (CScript *)-1,("mp_previous is -1 in script we are trying to delete!!!")); // Remove from the linked list. if (mp_previous==NULL) { sp_scripts=mp_next; } if (mp_next) { Dbg_MsgAssert(mp_next!=mp_previous,("mp_next same as mp_prev for script we are deleting")); mp_next->mp_previous=mp_previous; } if (mp_previous) { Dbg_MsgAssert(mp_next!=mp_previous,("mp_next same as mp_prev for script we are deleting")); mp_previous->mp_next=mp_next; } #ifdef __NOPT_ASSERT__ mp_next = (CScript *)-1; mp_previous = (CScript *)-1; #endif // Release any stored RandomNoRepeat or RandomPermute info. ReleaseStoredRandoms(this); #ifdef __NOPT_ASSERT__ #ifndef __PLAT_WN32__ if (m_being_watched_in_debugger) { // Tell the debugger that this script has died. Net::MsgDesc msg; msg.m_Data = &m_unique_id;; msg.m_Length = 4; msg.m_Id = GameNet::vMSG_ID_DBG_SCRIPT_DIED; Dbg::CScriptDebugger::Instance()->StreamMessage(&msg); } #endif #endif #ifdef __SCRIPT_EVENT_TABLE__ if (mp_event_handler_table) { // Remove the references to the event handler from the tracker // mp_event_handler_table->unregister_all(this); ClearEventHandlerTable(); // Need this to clean up structs and stuff if (mp_event_handler_table->m_in_immediate_use_counter) { // table still in use by its pass_event() function, // so leave it up to that function to destroy the table mp_event_handler_table->m_valid = false; } else { delete mp_event_handler_table; } } #endif Dbg_MsgAssert(s_num_cscripts,("Zero s_num_cscripts")); --s_num_cscripts; } #ifdef __NOPT_ASSERT__ #ifndef __PLAT_WN32__ // This gets called whenever mScriptChecksum is changed. void CScript::check_if_needs_to_be_watched_in_debugger() { Dbg::SWatchedScript *p_watched_script_info=Dbg::CScriptDebugger::Instance()->GetScriptWatchInfo(mScriptChecksum); if (p_watched_script_info) { WatchInDebugger(p_watched_script_info->mStopScriptImmediately); } } uint32 *CScript::write_callstack_entry( uint32 *p_buf, int bufferSize, uint32 scriptNameChecksum, uint8 *p_PC, CStruct *p_params, Obj::CObject *p_ob) { Dbg_MsgAssert(bufferSize >= 8,("write_callstack_entry buffer overflow")); *p_buf++=scriptNameChecksum; *p_buf++=Script::GetLineNumber(p_PC); bufferSize-=8; // Find out the source file and write it in. const char *p_source_file_name=""; CSymbolTableEntry *p_sym=LookUpSymbol(scriptNameChecksum); if (p_sym) { p_source_file_name=FindChecksumName(p_sym->mSourceFileNameChecksum); } int len=strlen(p_source_file_name)+1; len=(len+3)&~3; Dbg_MsgAssert(bufferSize >= len,("write_callstack_entry buffer overflow")); strcpy((char*)p_buf,p_source_file_name); p_buf+=len/4; bufferSize-=len; Dbg_MsgAssert(bufferSize >= 4,("write_callstack_entry buffer overflow")); *p_buf++=(uint32)p_ob; bufferSize -= 4; if (p_ob) { Dbg_MsgAssert(bufferSize >= 12,("write_callstack_entry buffer overflow")); *p_buf++=p_ob->GetType(); *p_buf++=p_ob->GetID(); *p_buf++=p_ob->MainScriptIs(this); bufferSize -= 12; // Write out the object's tags. int bytes_written=Script::WriteToBuffer(p_ob->GetTags(),(uint8*)p_buf,bufferSize); bytes_written=(bytes_written+3)&~3; Dbg_MsgAssert(bufferSize >= bytes_written,("write_callstack_entry buffer overflow")); p_buf+=bytes_written/4; bufferSize-=bytes_written; } // Write out the parameters. int bytes_written=Script::WriteToBuffer(p_params,(uint8*)p_buf,bufferSize); bytes_written=(bytes_written+3)&~3; Dbg_MsgAssert(bufferSize >= bytes_written,("write_callstack_entry buffer overflow")); p_buf+=bytes_written/4; return p_buf; } // Sets the comment string, which the script debugger displays. // This is so that the c-code can describe where the script was created from. void CScript::SetCommentString(const char *p_comment) { int len=strlen(p_comment); if (len>MAX_COMMENT_STRING_CHARS) { len=MAX_COMMENT_STRING_CHARS; } for (int i=0; i= 16,("Dbg::gpDebugInfoBuffer overflow")); // Stuff that identifies the CScript *p_buf++=(uint32)this; *p_buf++=m_unique_id; *(float*)p_buf=m_last_instruction_time_taken; ++p_buf; *(float*)p_buf=m_total_instruction_time_taken; ++p_buf; // The originating script info, ie which script this got spawned from if it was created // by a SpawnScript command. Will be two zeros if no such info is available. *p_buf++=m_originating_script_line_number; *p_buf++=m_originating_script_name; space_left-=24; // Write out the comment string. int comment_len=strlen(mp_comment_string)+1; int comment_space_needed=(comment_len+3)&~3; Dbg_MsgAssert(space_left >= comment_space_needed,("Dbg::gpDebugInfoBuffer overflow")); char *p_source=mp_comment_string; char *p_dest=(char*)p_buf; for (int i=0; i= structure_bytes_written,("Dbg::gpDebugInfoBuffer overflow")); p_buf+=structure_bytes_written/4; space_left-=structure_bytes_written; // Write out the callstack Dbg_MsgAssert(space_left >= 4,("Dbg::gpDebugInfoBuffer overflow")); // Adding one because the current script is considered the last entry in the callstack // when displayed in the debugger. *p_buf++=m_num_return_addresses+1; space_left-=4; uint32 *p_old=p_buf; for (int i=0; i= 20,("Dbg::gpDebugInfoBuffer overflow")); // Whether spawned or not *p_buf++=mIsSpawned; // Wait state *p_buf++=m_wait_type; // Paused or not *p_buf++=mPaused; // Whether 'session specific' or not *p_buf++=mNotSessionSpecific; // Script debugger needs this so that it knows to expand and highlight the // last entry in the callstack when it receives the info. *p_buf++=m_single_step_mode; space_left-=20; int message_size=((uint8*)p_buf)-((uint8*)Dbg::gpDebugInfoBuffer); Dbg_MsgAssert(message_size <= Dbg::SCRIPT_DEBUGGER_BUFFER_SIZE,("Script info message too big !")); Dbg_MsgAssert(message_size+space_left == Dbg::SCRIPT_DEBUGGER_BUFFER_SIZE,("What ??")); uint32 checksum=Crc::GenerateCRCCaseSensitive((const char *)Dbg::gpDebugInfoBuffer,message_size); if (checksum != m_last_transmitted_info_checksum || definitely_transmit) { printf("CScript id=0x%08x Sending script info, message size=%d\n",m_unique_id,message_size); Net::MsgDesc msg; msg.m_Data = Dbg::gpDebugInfoBuffer; msg.m_Length = message_size; msg.m_Id = GameNet::vMSG_ID_DBG_CSCRIPT_INFO; Dbg::CScriptDebugger::Instance()->StreamMessage(&msg); m_last_transmitted_info_checksum=checksum; } } void CScript::TransmitBasicInfoToDebugger() { uint32 *p_buf=Dbg::gpDebugInfoBuffer; *p_buf++=m_unique_id; *p_buf++=mScriptChecksum; *p_buf++=Script::GetLineNumber(mp_pc); // Find out the source file and write it in. const char *p_source_file_name=""; CSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum); if (p_sym) { p_source_file_name=FindChecksumName(p_sym->mSourceFileNameChecksum); } int len=strlen(p_source_file_name)+1; len=(len+3)&~3; strcpy((char*)p_buf,p_source_file_name); p_buf+=len/4; int message_size=((uint8*)p_buf)-((uint8*)Dbg::gpDebugInfoBuffer); Dbg_MsgAssert(message_size <= Dbg::SCRIPT_DEBUGGER_BUFFER_SIZE,("Script info message too big !")); //printf("Sending vMSG_ID_DBG_BASIC_CSCRIPT_INFO\n"); Net::MsgDesc msg; msg.m_Data = Dbg::gpDebugInfoBuffer; msg.m_Length = message_size; msg.m_Id = GameNet::vMSG_ID_DBG_BASIC_CSCRIPT_INFO; Dbg::CScriptDebugger::Instance()->StreamMessage(&msg); } void CScript::WatchInDebugger(bool stopScriptImmediately) { m_being_watched_in_debugger=true; if (stopScriptImmediately) { m_single_step_mode=STEP_INTO; } else { m_single_step_mode=OFF; } } void CScript::StopWatchingInDebugger() { m_being_watched_in_debugger=false; } void CScript::DebugStop() { bool was_running=m_single_step_mode==OFF; m_single_step_mode=WAITING; if (was_running) { // This is so that the debugger registers the fact that m_single_step_mode has changed // and hence updates the window to indicate that the script has stopped. TransmitInfoToDebugger(); } } void CScript::DebugStepInto() { m_single_step_mode=STEP_INTO; Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause(); } void CScript::DebugStepOver() { m_single_step_mode=STEP_OVER; Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause(); } void CScript::DebugGo() { m_single_step_mode=OFF; Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause(); } #endif // #ifndef __PLAT_WN32__ #endif // #ifdef __NOPT_ASSERT__ void CScript::SetWait(EWaitType type, Obj::CBaseComponent *p_component) { m_wait_type=type; Dbg_MsgAssert(mp_wait_component==NULL,("\n%s\nExpected mp_wait_component to be NULL here?",GetScriptInfo())); mp_wait_component=p_component; } void CScript::ClearWait() { m_wait_type=WAIT_TYPE_NONE; mp_wait_component=NULL; } void CScript::Block() { m_wait_type=WAIT_TYPE_BLOCKED; } void CScript::UnBlock() { m_wait_type=WAIT_TYPE_NONE; } bool CScript::getBlocked() { return m_wait_type==WAIT_TYPE_BLOCKED; } bool CScript::GotScript() { return mp_pc?true:false; } void CScript::SwitchOnIfDebugging() { #ifdef __NOPT_ASSERT__ m_if_debug_on=true; #endif } void CScript::SwitchOffIfDebugging() { #ifdef __NOPT_ASSERT__ m_if_debug_on=false; #endif } void CScript::set_script(uint32 scriptChecksum, uint8 *p_script, CStruct *p_params, Obj::CObject *p_object) { #ifdef __NOPT_ASSERT__ #ifdef SEND_SCRIPT_NAMES_TO_DEBUGGER // Tell the debugger that this script is begin run. s_send_script_name(scriptChecksum,0); #endif #endif #ifdef __NOPT_ASSERT__ if (Obj::DebugSkaterScripts) { if (p_object && p_object->GetType()==SKATE_TYPE_SKATER) { printf("%d: ############### %s ############### [%i]\n",(int)Tmr::GetRenderFrame(),FindChecksumName(scriptChecksum),m_unique_id); } } #endif CStruct *p_params_copy=MakeParamsSafeFromDeletionByClearScript(p_params); // Reset the script. ClearScript(); Dbg_MsgAssert(mp_function_params==NULL,("mp_function_params not NULL")); mp_function_params=new CStruct; #ifdef __NOPT_ASSERT__ // This is so that the structure can print line number info in case one of // the CStruct member function asserts goes off. mp_function_params->SetParentScript(this); #endif mScriptChecksum=scriptChecksum; #ifdef __NOPT_ASSERT__ check_if_needs_to_be_watched_in_debugger(); #endif mpObject=p_object; Dbg_MsgAssert(mp_params==NULL,("mp_params not NULL")); mp_params=new CStruct; #ifdef __NOPT_ASSERT__ // This is so that the structure can print line number info in case one of // the CStruct member function asserts goes off. mp_params->SetParentScript(this); #endif if (p_script) { mp_pc=p_script; } else { Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); mp_pc=p_script_cache->GetScript(scriptChecksum); if (!mp_pc) { #ifdef __NOPT_ASSERT__ if (Script::GetInteger(CRCD(0x22d1f89,"AssertOnMissingScripts"))) { Dbg_MsgAssert(false, ("Script %s not found.",FindChecksumName(scriptChecksum))); } #endif if (p_params_copy) { delete p_params_copy; } ClearScript(); ClearEventHandlerTable(); return; } } // Load the default parameters into mp_params mp_pc=AddComponentsUntilEndOfLine(mp_params, mp_pc); // Now merge what is in p_params onto mp_params. if (p_params_copy) { mp_params->AppendStructure(p_params_copy); delete p_params_copy; } else { mp_params->AppendStructure(p_params); } if (GetOnExitScriptChecksum()) { // Pass in the name of the exception so that certain exceptions can be ignored. Script::CStruct *pFoo=new Script::CStruct; // Mick - that has no meaning now, as this is triggered by everything // pFoo->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int)p_entry->exception); // RunScript(GetOnExitScriptChecksum(), pFoo, mpObject ); // Dan: interrupt instead of run uint32 checksum = GetOnExitScriptChecksum(); SetOnExitScriptChecksum(0); // clear it once it has been run Interrupt(checksum, pFoo); delete pFoo; } } void CScript::SetScript(const SStructScript *p_structScript, CStruct *p_params, Obj::CObject *p_object) { Dbg_MsgAssert(p_structScript,("NULL p_structScript")); Dbg_MsgAssert(p_structScript->mNameChecksum,("Zero p_structScript->mNameChecksum")); Dbg_MsgAssert(p_structScript->mpScriptTokens,("NULL p_structScript->mpScriptTokens")); // Calculate the size of the script uint32 size=SkipOverScript(p_structScript->mpScriptTokens)-p_structScript->mpScriptTokens; // Allocate a buffer and make a copy of the script (since the source may get deleted) Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); uint8 *p_new_script=(uint8*)Mem::Malloc(size); Mem::Manager::sHandle().PopContext(); uint8 *p_source=p_structScript->mpScriptTokens; uint8 *p_dest=p_new_script; for (uint32 i=0; imNameChecksum,p_new_script,p_params,p_object); // Now set mp_struct_script, so that the buffer can be deleted later. // Note: The setting of mp_struct_script cannot be done before set_script, because set_script calls // ClearScript, which would have deleted mp_struct_script if it was not NULL. mp_struct_script=p_new_script; } void CScript::SetScript(uint32 scriptChecksum, CStruct *p_params, Obj::CObject *p_object) { set_script(scriptChecksum,NULL,p_params,p_object); } void CScript::SetScript(const char *p_scriptName, CStruct *p_params, Obj::CObject *p_object) { SetScript(Crc::GenerateCRCFromString(p_scriptName),p_params,p_object); } CStruct *CScript::MakeParamsSafeFromDeletionByClearScript(CStruct *p_params) { // It may be that p_params is mp_params or mp_function_params, or is a substructure of one // of those. // For example, when a manual is triggered, the passed p_params will be mp_function_params. // Since ClearScript() deletes both mp_params and mp_function_params, // this will result in p_params getting deleted too. // So if either mp_params or mp_function_params references p_params, store the contents // of p_params in p_new_params & return it. // This is a speed optimization, because it would be slow to always store the contents of // p_params. (Copying structures can be slow) CStruct *p_new_params=NULL; if (p_params) { if ((mp_function_params && mp_function_params->References(p_params)) || (mp_params && mp_params->References(p_params))) { p_new_params=new CStruct; p_new_params->AppendStructure(p_params); } } return p_new_params; } // Stops the script from executing, and deletes the parameters, clears the stack, etc. // (REQUIREMENT: Must set mp_pc to NULL, so script will be deleted by UpdateSpawnedScripts) void CScript::ClearScript() { if (mp_struct_script) { Mem::Free(mp_struct_script); mp_struct_script=NULL; } Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); if (mp_pc) { // If a script was being executed, then decrement its usage in the script cache since // it is no longer needed by this CScript. p_script_cache->DecrementScriptUsage(mScriptChecksum); } mp_pc=NULL; if (mp_params) { delete mp_params; mp_params=NULL; } if (mp_function_params) { delete mp_function_params; mp_function_params=NULL; } for (int i=0; iDecrementScriptUsage(mp_return_addresses[i].mScriptNameChecksum); Dbg_MsgAssert(mp_return_addresses[i].mpParams,("NULL mpParams in return stack")); if (mp_return_addresses[i].mpParams) { delete mp_return_addresses[i].mpParams; mp_return_addresses[i].mpParams=NULL; } } m_num_return_addresses=0; if (mp_return_addresses != mp_return_addresses_small_buffer) { Dbg_MsgAssert(mp_return_addresses,("NULL mp_return_addresses ?")); Mem::Free(mp_return_addresses); } mp_return_addresses=mp_return_addresses_small_buffer; mp_current_loop=NULL; if (mp_loops != mp_loops_small_buffer) { Dbg_MsgAssert(mp_loops,("NULL mp_loops ?")); Mem::Free(mp_loops); } mp_loops=mp_loops_small_buffer; m_wait_timer=0; m_wait_type=WAIT_TYPE_NONE; mp_wait_component=NULL; mpObject=NULL; mScriptChecksum=0; m_interrupted=false; m_skip_further_process_waits=false; #ifdef __NOPT_ASSERT__ m_if_status=0; #endif } // Cleans up the script's event handler table. Call after ClearScript if you are not about to reset the script // (REQUIREMENT: Must set mp_pc to NULL, so script will be deleted by UpdateSpawnedScripts) void CScript::ClearEventHandlerTable() { #ifdef __SCRIPT_EVENT_TABLE__ if (mp_event_handler_table) { // Remove the references to the event handler from the tracker mp_event_handler_table->unregister_all(this); mp_event_handler_table->remove_group(CRCD(0x8b713e0e, "all_groups")); } #endif } // Restarts the script. void CScript::Restart() { // This is a structure for temporarily storing the old parameters, since the SetScript call will clear // everything in the script, including deleting the structure pointed to by mp_params, and the call stack. CStruct *p_params=new CStruct; if (m_num_return_addresses) { // If there are things in the call stack, then the script that we want to restart is // the first one in the call stack, since this is the original script. Dbg_MsgAssert(mp_return_addresses[0].mpParams,("NULL p_params in return stack")); *p_params=*mp_return_addresses[0].mpParams; SetScript(mp_return_addresses[0].mScriptNameChecksum,p_params,mp_return_addresses[0].mpObject); } else { // If there is nothing in the call stack, restart the current script. *p_params=*mp_params; // The parameters are now safe in p_params, so restart the script by reinitialising it. SetScript(mScriptChecksum,p_params,mpObject); } // Delete the temporary structure. delete p_params; } // Stops the script from being associated with an object, ie, sets mpObject to NULL. // Allows the script to continue executing though, so the script must not contain any // further member functions, otherwise the code will assert. // This function also sets m_wait_type=WAIT_TYPE_NONE; to ensure the Update function does // not assert if it is waiting for the object to finish doing something. void CScript::DisassociateWithObject(Obj::CObject *pObjectToDisconnectFrom) { if (!pObjectToDisconnectFrom) return; #ifdef __NOPT_ASSERT__ pObjectToDisconnectFrom->SetLockAssertOff(); #endif if (mpObject == pObjectToDisconnectFrom) { mpObject=NULL; } for (int i=0; imSourceFileNameChecksum),FindChecksumName(mScriptChecksum)); } } else { sprintf(sp_script_info,"GetScriptInfo: Script symbol not found. Strange."); } } strcat(sp_script_info,"\nScript call stack:\n"); CSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum); if (p_sym) { strcat(sp_script_info,FindChecksumName(p_sym->mSourceFileNameChecksum)); } else { strcat(sp_script_info,"Unknown source file"); } sprintf(sp_script_info+strlen(sp_script_info)," line %d, in ",GetLineNumber(mp_pc)); strcat(sp_script_info,FindChecksumName(mScriptChecksum)); strcat(sp_script_info,"\n"); for (int i=m_num_return_addresses-1; i>=0; --i) { if (mp_return_addresses[i].mpParams) { sprintf(sp_script_info+strlen(sp_script_info)," line %d, in ",GetLineNumber(mp_return_addresses[i].mpReturnAddress)); strcat(sp_script_info,FindChecksumName(mp_return_addresses[i].mScriptNameChecksum)); strcat(sp_script_info,"\n"); } else { strcat(sp_script_info,"-\n"); } } Dbg_MsgAssert(strlen(sp_script_info)mSourceFileNameChecksum); } else { return "Unknown"; } } #else // Need these to be fast on Xbox because they are used in Dbg_MsgAsserts(), which will call it // regardless of whether the assert condition is false. const char *CScript::GetScriptInfo() { return ""; } int CScript::GetCurrentLineNumber(void) { return 0; } const char *CScript::GetSourceFile(void) { return ""; } #endif // #ifndef __PLAT_XBOX__ #endif #ifdef __NOPT_ASSERT__ #ifndef __PLAT_WN32__ // For testing how many scripts end up needing the bigger buffer. bool CScript::UsingBigCallstackBuffer() { if (mp_return_addresses == mp_return_addresses_small_buffer) { return false; } return true; } bool CScript::UsingBigLoopBuffer() { if (mp_loops == mp_loops_small_buffer) { return false; } return true; } #endif #endif // Used by CScript::Update when calling a script. // call_script is also used by CScript::Interrupt below. void CScript::call_script(uint32 newScriptChecksum, uint8 *p_script, CStruct *p_params, Obj::CObject *p_object, bool interrupt) { // K: If calling a script when nothing is currently being run, jump to it instead. // This is so that the mp_function_params gets created. // Fixes a bug where when a 2-player trick attack game was started, it would immediately // assert because call_script was called straight away on a newly created CScript. // (CGoal::RunCallbackScript would run a script which would do a MakeSkaterGosub, and the // skater's script was not running anything at that point) if (!mp_pc) { set_script(newScriptChecksum,p_script,p_params,p_object); // This may not really be necessary since there is no script to return to that might be affected, // but set it anyway for consistency. m_interrupted=interrupt; return; } // Calling a subroutine, so store the current script info on the stack. if (mp_return_addresses == mp_return_addresses_small_buffer && m_num_return_addresses == RETURN_ADDRESSES_SMALL_BUFFER_SIZE) { Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); mp_return_addresses=(SReturnAddress*)Mem::Malloc(MAX_RETURN_ADDRESSES * sizeof(SReturnAddress)); Mem::Manager::sHandle().PopContext(); for (int i=0; imScriptNameChecksum=mScriptChecksum; p_stack_top->mpParams=mp_params; p_stack_top->mpReturnAddress=mp_pc; p_stack_top->mpObject=mpObject; p_stack_top->mpLoop=mp_current_loop; #ifdef __NOPT_ASSERT__ p_stack_top->m_if_status=m_if_status; #endif p_stack_top->mWaitType=m_wait_type; p_stack_top->mpWaitComponent=mp_wait_component; p_stack_top->mWaitTimer=m_wait_timer; p_stack_top->mWaitPeriod=m_wait_period; p_stack_top->mStartTime=m_start_time; p_stack_top->mInterrupted=m_interrupted; // Set the new mScriptChecksum mScriptChecksum=newScriptChecksum; #ifdef __NOPT_ASSERT__ check_if_needs_to_be_watched_in_debugger(); #endif // Set the new mp_pc mp_pc=p_script; Dbg_MsgAssert(mp_pc,("NULL p_script sent to call_script")); // Create a new parameters structure, and fill it in using the default values listed after // the script name. // Note: Not asserting if mp_params is not NULL, because it probably won't be. // The old value of mp_params has been safely stored in the mp_return_addresses array above. mp_params=new CStruct; #ifdef __NOPT_ASSERT__ // This is so that the structure can print line number info in case one of // the CStruct member function asserts goes off. mp_params->SetParentScript(this); #endif mp_pc=AddComponentsUntilEndOfLine(mp_params,mp_pc); // Now overlay the parameters passed to the script. if (p_params) { *mp_params+=*p_params; } // Set the object pointer, which may get changed if executing another object's // script using the : operator. mpObject=p_object; ClearWait(); #ifdef __NOPT_ASSERT__ // Not in an if-statement on starting the new routine. m_if_status=0; #endif m_interrupted=interrupt; } // Uses the above function to implement an interrupt. // This will jump to the passed script and also do one call to update. // Once the interrupt script is finished it will return to what it was doing before. EScriptReturnVal CScript::Interrupt(uint32 newScriptChecksum, CStruct *p_params) { Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); uint8 *p_script=p_script_cache->GetScript(newScriptChecksum); // The true means interrupt the script, which means that the call to Update will not continue // execution of the interrupted script when the called script finishes. call_script(newScriptChecksum,p_script,p_params,mpObject,true); return Update(); } bool CScript::Finished() { return mp_pc && *mp_pc==ESCRIPTTOKEN_KEYWORD_ENDSCRIPT; } #ifdef __NOPT_ASSERT__ static bool sExcludeFromSkaterDebug(uint32 name) { CArray *p_array=GetArray(0xdaa3a3a5/*ExcludeFromSkaterDebug*/,NO_ASSERT); if (!p_array) { return false; } for (uint32 i=0; iGetSize(); ++i) { if (name==p_array->GetChecksum(i)) { return true; } } return false; } #endif // This looks at m_wait_type, does the appropriate wait logic, and resets // m_wait_type back to WAIT_TYPE_NONE if the wait has finished. // Otherwise m_wait_type is left the same. void CScript::process_waits() { switch (m_wait_type) { case WAIT_TYPE_NONE: break; case WAIT_TYPE_COUNTER: if (m_wait_timer) { --m_wait_timer; } else { // Finished counting down, so reset wait type // so that script execution continues. m_wait_type=WAIT_TYPE_NONE; } break; case WAIT_TYPE_ONE_PER_FRAME: if (m_wait_timer) { --m_wait_timer; } else { if (s_done_one_per_frame) { // already done one this frame } else { // Finished counting down, so reset wait type // so that script execution continues. s_done_one_per_frame = true; m_wait_type=WAIT_TYPE_NONE; } } break; case WAIT_TYPE_TIMER: { // if (Tmr::ElapsedTime(m_start_time) >= m_wait_period) // Mick, instead of using global time, we decrement the timer whenever // the script is updated. That way paused scripts will be genuinely paused // // Note, I use m_start_time as a patch variable // if it is not zero, I know we have not been round this loop // at least once. // If you feel like changing this, then note that m_start_time gets pushed on the stack // when calling another script, you you will need to duplicate that functionality // int tick = (int) (Tmr::FrameLength() * 1000.0f); if ((int)m_wait_period < tick) { if (m_start_time == 0) // only if we've waited at least one frame { // Finished counting down, so reset wait type // so that script execution continues. m_wait_type=WAIT_TYPE_NONE; } else { // finished counting down, but this was the first frame. // so we don't want to exit, as we cannot have a zero frame delay if // the time specified was not zero // be we want for sure to exit next time around, so set the wait period to 0 // m_wait_period = 0; } } else { m_wait_period -= tick; } m_start_time = 0; // flag that we have waited at least one frame break; } case WAIT_TYPE_BLOCKED: // Nothing to do except wait for whatever blocked this script to unblock it. break; // These are the object wait options, which only apply if the script // has a parent object. case WAIT_TYPE_OBJECT_MOVE: case WAIT_TYPE_OBJECT_ANIM_FINISHED: case WAIT_TYPE_OBJECT_JUMP_FINISHED: case WAIT_TYPE_OBJECT_STOP_FINISHED: case WAIT_TYPE_OBJECT_ROTATE: case WAIT_TYPE_STREAM_FINISHED: // Call the parent object's wait function. Dbg_MsgAssert(mp_wait_component,("\n%s\nNULL mp_wait_component, cannot call ProcessWait",GetScriptInfo())); mp_wait_component->ProcessWait(this); break; default: Dbg_MsgAssert(0,("\n%s\nBad wait type value of %d",GetScriptInfo(),m_wait_type)); break; } } void CScript::load_function_params() { #ifdef STOPWATCH_STUFF pFunctionParamsStopWatch->Start(); #endif // Clear the function parameters structure and add the new params, using mp_params to get // any parameters referenced using the <,> operators. Dbg_MsgAssert(mp_function_params,("NULL mp_function_params")); mp_function_params->Clear(); // AddComponentsUntilEndOfLine will assert if mp_pc is NULL mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params); #ifdef STOPWATCH_STUFF pFunctionParamsStopWatch->Stop(); #endif } // Runs the specified member function. Uses mpObject by default, but uses p_substituteObject // if it is not NULL. p_substituteObject defaults to NULL if it is not specified. // Does not affect mp_pc bool CScript::run_member_function(uint32 functionName, Obj::CObject *p_substituteObject) { Obj::CObject *p_obj; // By default member function calls operate on mpObject, but if // a substitute object has been specified use that instead. if (p_substituteObject) { p_obj=p_substituteObject; } else { p_obj=mpObject; } bool return_value=false; if (p_obj) { #ifdef STOPWATCH_STUFF pUpdateStopWatch->Stop(); pCFunctionStopWatch->Start(); #endif #ifdef STOPWATCH_STUFF Tmr::CPUCycles TimeBefore,TimeAfter; TimeBefore=Tmr::GetTimeInCPUCycles(); #endif return_value=p_obj->CallMemberFunction(functionName,mp_function_params,this); #ifdef STOPWATCH_STUFF TimeAfter=Tmr::GetTimeInCPUCycles(); float TimeTaken; TimeTaken=((int)(TimeAfter-TimeBefore))/150.0f; if (TimeTaken>200) { //SSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum); //printf("Memb: %s, %f (%s, line %d)\n",FindChecksumName(NameChecksum),TimeTaken,p_sym->pFilename,GetLineNumber(mp_pc)); } p_entry->mAverageExecutionTime = (p_entry->mAverageExecutionTime*p_entry->mNumCalls+TimeTaken)/(p_entry->mNumCalls+1); ++p_entry->mNumCalls; p_entry->mNumCallsInThisFrame=600; #endif #ifdef STOPWATCH_STUFF pCFunctionStopWatch->Stop(); pUpdateStopWatch->Start(); #endif } else { #ifdef __PLAT_WN32__ if (functionName != 0xb3c262ec) // "DisassociateFromObject" is okay, no harm in trying to break non-existent association { Dbg_MsgAssert(0,("\n%s\nTried to call member function %s from a script\nnot associated with a CObject",GetScriptInfo(),FindChecksumName(functionName))); } #else GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); // It network games, it's expected that some objects will no longer exist by the time // some object member functions are called via remote scripts. One example being, when // someone joins a game-in-progress, they will be instructed to execute a series of scripts // and it's possible that the objects which originally spawned those scripts are no // longer in the game or that the client has yet to create skaters for them. So in // those cases, just print a warning and try to continue processing the script if( gamenet_man->InNetGame()) { Dbg_Warning( "\n%s\nTried to call member function %s from a script\nnot associated with a CObject",GetScriptInfo(),FindChecksumName(functionName)); } else if (functionName != 0xb3c262ec) // "DisassociateFromObject" is okay, no harm in trying to break non-existent association { Dbg_MsgAssert(0,("\n%s\nTried to call member function %s from a script\nnot associated with a CObject",GetScriptInfo(),FindChecksumName(functionName))); } #endif } return return_value; } // Executes the passed c-function pointer, using the current mp_function_params as parameters. bool CScript::run_cfunction(bool (*p_cfunc)(CStruct *pParams, CScript *pCScript)) { Dbg_MsgAssert(p_cfunc,("\n%s\nNULL p_cfunc",GetScriptInfo())); #ifdef STOPWATCH_STUFF pUpdateStopWatch->Stop(); pCFunctionStopWatch->Start(); #endif #ifdef STOPWATCH_STUFF Tmr::CPUCycles TimeBefore,TimeAfter; TimeBefore=Tmr::GetTimeInCPUCycles(); #endif /* Tmr::Time TimeBefore,TimeAfter; TimeBefore=Tmr::GetTimeInCPUCycles(); */ bool return_value=(*p_cfunc)(mp_function_params,this); /* TimeAfter=Tmr::GetTimeInCPUCycles(); float TimeTaken; TimeTaken=((int)(TimeAfter-TimeBefore))/150.0f; if (TimeTaken>Script::GetFloat("Threshold")) { CArray *p_exclude=Script::GetArray("Exclude"); bool exclude=false; for (uint32 i=0; iGetSize(); ++i) { if (p_exclude->GetChecksum(i)==name_checksum) { exclude=true; break; } } if (!exclude) { CSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum); printf("CFunc: %s, %f (%s, line %d)\n",FindChecksumName(name_checksum),TimeTaken,FindChecksumName(p_sym->mSourceFileNameChecksum),GetLineNumber(mp_pc)); } } */ #ifdef STOPWATCH_STUFF TimeAfter=Tmr::GetTimeInCPUCycles(); float TimeTaken; TimeTaken=((int)(TimeAfter-TimeBefore))/150.0f; if (TimeTaken>200) { //SSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum); //printf("CFunc: %s, %f (%s, line %d)\n",FindChecksumName(NameChecksum),TimeTaken,p_sym->pFilename,GetLineNumber(mp_pc)); } p_entry->mAverageExecutionTime = (p_entry->mAverageExecutionTime*p_entry->mNumCalls+TimeTaken)/(p_entry->mNumCalls+1); ++p_entry->mNumCalls; p_entry->mNumCallsInThisFrame=600; #endif #ifdef STOPWATCH_STUFF pCFunctionStopWatch->Stop(); pUpdateStopWatch->Start(); #endif return return_value; } // Gets the name from mp_pc. // If mp_pc points to a <,> type name, it will look up that name in mp_params. // It will advance mp_pc to after the name. uint32 CScript::get_name() { uint32 name_checksum=0; switch (*mp_pc) { case ESCRIPTTOKEN_KEYWORD_ALLARGS: { ++mp_pc; mp_params->GetChecksum(NO_NAME,&name_checksum); break; } case ESCRIPTTOKEN_ARG: { ++mp_pc; Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_NAME,("\n%s\nExpected '<' token to be followed by a name.",GetScriptInfo())); ++mp_pc; uint32 arg_checksum=Read4Bytes(mp_pc).mChecksum; mp_pc+=4; mp_params->GetChecksum(arg_checksum,&name_checksum); break; } case ESCRIPTTOKEN_NAME: { ++mp_pc; name_checksum=Read4Bytes(mp_pc).mChecksum; mp_pc+=4; break; } default: { Dbg_MsgAssert(0,("\n%s\nUnexpected '%s' token when expecting some sort of name",GetScriptInfo(),GetTokenName((EScriptToken)*mp_pc))); break; } } return name_checksum; } // Executes the command pointed to by mp_pc, and returns true or false, which is the value to // be used by any preceding if-statement. // After executing the command, mp_pc will have changed. It might be pointing to the next command in // the current script, or to a command in a different script, or it might even be NULL, who knows? // A 'command' is either a cfunc,member func, or script call, in which the command has the form of // some sort of name followed by a list of parameters to be sent to it. // It also covers the direct setting of one of the script's parameters, such as =3 // It also covers expressions enclosed in parentheses that can be used in if's, eg if (1>0) // Note that like in C, expressions on their own are valid commands, eg (23*67), but unless preceded // by an if their result will be discarded. // // The term 'command' does not cover things like if's, begin-repeat's, or switch statements. // Generally the logic for interpreting that stuff is done in CScript::Update. bool CScript::execute_command() { uint8 token=*mp_pc; // Check for the pre-processed member function token. if (token==ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION) { ++mp_pc; uint32 member_function_checksum=Read4Bytes(mp_pc).mChecksum; mp_pc+=4; load_function_params(); return run_member_function(member_function_checksum); } // Check for the pre-processed cfunction token. if (token==ESCRIPTTOKEN_RUNTIME_CFUNCTION) { ++mp_pc; bool (*p_cfunc)(CStruct *pParams, CScript *pCScript)=Read4Bytes(mp_pc).mpCFunction; mp_pc+=4; load_function_params(); return run_cfunction(p_cfunc); } // Check for an expression enclosed in parentheses if (token==ESCRIPTTOKEN_OPENPARENTH) { // Note: Not skipping past the open-parenth token because Evaluate() expects it. CComponent *p_comp=new CComponent; mp_pc=Evaluate(mp_pc,mp_params,p_comp); Dbg_MsgAssert(p_comp->mType==ESYMBOLTYPE_INTEGER,("\n%s\nBad type of '%s' returned by expression, expected integer",GetScriptInfo(),GetTypeName(p_comp->mType))); bool return_value=p_comp->mIntegerValue; CleanUpComponent(p_comp); delete p_comp; return return_value; } // Otherwise, expect some sort of name, ie Blaa or uint32 name=get_name(); // Check if the name is followed by a colon, in which case the name is the id of some object. if (*mp_pc==ESCRIPTTOKEN_COLON) { Obj::CObject *p_substitute_object = NULL; // Find the object #ifndef __PLAT_WN32__ p_substitute_object=Obj::ResolveToObject(name); #endif Dbg_MsgAssert(p_substitute_object,("\n%s\nCould not resolve '%s' to a CObject instance",GetScriptInfo(),FindChecksumName(name))); if( p_substitute_object == NULL ) { return true; } // if (p_substitute_object == mpObject) // { // printf("\n\n----------------------------------->\n\%s\nThis script is already running on %s\n",GetScriptInfo(),FindChecksumName(name)); // } ++mp_pc; mp_pc=DoAnyRandomsOrJumps(mp_pc); // Now we're expecting some sort of function to run on the object. // Check for a pre-processed member function if (*mp_pc==ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION) { ++mp_pc; uint32 member_function_checksum=Read4Bytes(mp_pc).mChecksum; mp_pc+=4; load_function_params(); return run_member_function(member_function_checksum,p_substitute_object); } // No pre-processed member function, so expect some sort of name. uint32 function_checksum=get_name(); // Get the parameters that follow the name. load_function_params(); // Look-up what kind of function it is. CSymbolTableEntry *p_entry=Resolve(function_checksum); // if the script is "runmenow" then a syntax error // should just printf a warning and return if (!p_entry && mScriptChecksum == 0xfb11e6cd) // RunMeNow { #ifdef __NOPT_ASSERT__ printf("WARNING !! Confused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",FindChecksumName(function_checksum)); #endif return false; } Dbg_MsgAssert(p_entry,("\n%s\nConfused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",GetScriptInfo(),FindChecksumName(function_checksum))); // Expecting the function to be either a member function, or a script. switch (p_entry->mType) { case ESYMBOLTYPE_QSCRIPT: { #ifdef __NOPT_ASSERT__ if (Obj::DebugSkaterScripts) { if (p_substitute_object->GetType()==SKATE_TYPE_SKATER) { if (!sExcludeFromSkaterDebug(function_checksum)) { printf("%d: Calling %s [%i]\n",(int)Tmr::GetRenderFrame(),FindChecksumName(function_checksum),m_unique_id); } } } #endif Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); uint8 *p_script=p_script_cache->GetScript(p_entry->mNameChecksum); call_script(function_checksum,p_script,mp_function_params,p_substitute_object); return true; break; } case ESYMBOLTYPE_MEMBERFUNCTION: return run_member_function(function_checksum,p_substitute_object); break; default: Dbg_MsgAssert(0,("\n%s\n'%s' is not a member function or script",GetScriptInfo(),FindChecksumName(function_checksum))); return false; break; } } // Check if the name is followed by equals. // In this case, the name is the name of the parameter we want to set. if (*mp_pc==ESCRIPTTOKEN_EQUALS) { // The get_name call will have looked up any <,> name in the structure, so re-get the // preceding name, because we want both x and to mean x. mp_pc-=5; Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_NAME,("\n%s\nEquals must be preceded by a param name",GetScriptInfo())); ++mp_pc; name=Read4Bytes(mp_pc).mChecksum; mp_pc+=5; // +5 to skip over the equals too. mp_pc=DoAnyRandomsOrJumps(mp_pc); // Calculate the value of whatever follows the equals, and store it in p_comp CComponent *p_comp=new CComponent; if (*mp_pc==ESCRIPTTOKEN_OPENPARENTH) { // It's an expression, so evaluate it. mp_pc=Evaluate(mp_pc,mp_params,p_comp); } else { // It's not an expression, so just load the value in. mp_pc=FillInComponentUsingQB(mp_pc,mp_params,p_comp); } if (p_comp->mType!=ESYMBOLTYPE_NONE) { // Got some sort of value, so name it and stick it into mp_params. p_comp->mNameChecksum=name; mp_params->AddComponent(p_comp); // Not deleting p_comp because it has been given to mp_params } else { // Did not get a value, so clean up p_comp delete p_comp; } return true; } // The name is not followed by a colon or an equals, so it must be a function call. // Load in the parameters that follow. load_function_params(); // Look up the function to see what it is. CSymbolTableEntry *p_entry=Resolve(name); // if the script is "runmenow" then a syntax error // should just printf a warning and return if (!p_entry && mScriptChecksum == 0xfb11e6cd) // RunMeNow { #ifdef __NOPT_ASSERT__ printf("WARNING !! Confused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",FindChecksumName(name)); #endif return false; } if (!p_entry) { #ifdef __NOPT_ASSERT__ if (Script::GetInteger(CRCD(0x22d1f89,"AssertOnMissingScripts"))) { Dbg_MsgAssert(p_entry,("\n%s\nConfused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",GetScriptInfo(),FindChecksumName(name))); } else #endif { #ifdef __PLAT_WN32__ // Don't printf if compiling on PC, otherwise LevelAssetLister prints lots // of warning messages when running the load sound scripts. #else printf ("WARNING: script %s not found, ignoring in default level.\n",FindChecksumName(name)); #endif return true; } } switch (p_entry->mType) { case ESYMBOLTYPE_CFUNCTION: return run_cfunction(p_entry->mpCFunction); break; case ESYMBOLTYPE_MEMBERFUNCTION: return run_member_function(name); break; case ESYMBOLTYPE_QSCRIPT: { #ifdef __NOPT_ASSERT__ if (Obj::DebugSkaterScripts) { if (mpObject && mpObject->GetType()==SKATE_TYPE_SKATER) { if (!sExcludeFromSkaterDebug(name)) { printf("%d: Calling %s [%i]\n",(int)Tmr::GetRenderFrame(),FindChecksumName(name),m_unique_id); } } } #endif Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); uint8 *p_script=p_script_cache->GetScript(p_entry->mNameChecksum); call_script(name,p_script,mp_function_params,mpObject); // Script calls always return true. return true; break; } default: Dbg_MsgAssert(0,("\n%s\n'%s' cannot be called, because it's a '%s'",GetScriptInfo(),FindChecksumName(name),GetTypeName(p_entry->mType))); break; } return false; } void CScript::execute_if() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_IF,("Unexpected *mp_pc='%s', expected keyword 'if'",GetTokenName((EScriptToken)*mp_pc))); // Skip over the if token. ++mp_pc; bool negate=false; if (*mp_pc==ESCRIPTTOKEN_KEYWORD_NOT) { ++mp_pc; negate=true; } bool return_value=execute_command(); // mp_pc will have changed, and might be NULL. if (!mp_pc) { return; } if (negate) { return_value=!return_value; } #ifdef __NOPT_ASSERT__ Dbg_MsgAssert((m_if_status&0x80000000)==0,("\n%s\nToo many nested if's",GetScriptInfo())); m_if_status<<=1; #endif // If the return value is false, skip forward to the 'else' or 'endif' statement. if (!return_value) { int in_nested_if=0; // Skip mp_pc to the current if's 'else' or 'endif', // but ignore the else & endifs belonging to nested ifs. #ifdef STOPWATCH_STUFF pIfSkipStopWatch->Start(); #endif while (in_nested_if || !(*mp_pc==ESCRIPTTOKEN_KEYWORD_ELSE || *mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF)) { Dbg_MsgAssert(*mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT,("endif or else keywords not found after if keyword.")); // Keep track of nested ifs. if (*mp_pc==ESCRIPTTOKEN_KEYWORD_IF) { ++in_nested_if; } else if (*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF) { Dbg_MsgAssert(in_nested_if,("Got endif within true part of if statement without corresponding nested if")); --in_nested_if; } mp_pc=SkipToken(mp_pc); } #ifdef __NOPT_ASSERT__ if (*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF) { m_if_status>>=1; } #endif // Skip over the 'else' or 'endif' ++mp_pc; #ifdef STOPWATCH_STUFF pIfSkipStopWatch->Stop(); #endif } else { // Otherwise, if the return value was true, mp_pc remains unchanged so that the instructions // following the if get executed. // Record the if-status so that spurious else's can be detected. #ifdef __NOPT_ASSERT__ m_if_status|=1; #endif } // I'm pretty sure if-debug isn't used, check with Scott /* #ifdef __NOPT_ASSERT__ if (m_if_debug_on) { printf("%s: If %s ",FindChecksumName(mScriptChecksum),FindChecksumName(name_checksum)); if (return_value) { printf("TRUE\n"); } else { printf("FALSE\n"); } } #endif */ } // On entry, mp_pc must point to an else token. // Skips forward till it hits an endif, then skips over the endif. void CScript::execute_else() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_ELSE,("Unexpected *mp_pc='%s', expected keyword 'else'",GetTokenName((EScriptToken)*mp_pc))); // else's are only allowed in the true blocks of if-statements. // Bit 0 of m_if_status indicates the status of the last if. Dbg_MsgAssert(m_if_status&1,("\n%s\nSpurious 'else'",GetScriptInfo())); #ifdef STOPWATCH_STUFF pIfSkipStopWatch->Start(); #endif // Skip over the code within the else, since the code before it must have executed. int in_nested_if=0; while (in_nested_if || *mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDIF) { Dbg_MsgAssert(*mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT,("endif keyword not found after else keyword.")); // Keep track of nested ifs. if (*mp_pc==ESCRIPTTOKEN_KEYWORD_IF) { ++in_nested_if; } else if (*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF) { Dbg_MsgAssert(in_nested_if,("Got endif within else part of if statement without corresponding nested if")); --in_nested_if; } mp_pc=SkipToken(mp_pc); } // We've hit the endif, so skip over it. ++mp_pc; #ifdef __NOPT_ASSERT__ m_if_status>>=1; #endif #ifdef STOPWATCH_STUFF pIfSkipStopWatch->Stop(); #endif } // We can hit an endif either by finishing the 'true' block of an if-statement // that has no else, or by finishing the 'false' block following an else. // Either way there is nothing to do, so just skip over it. void CScript::execute_endif() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF,("Unexpected *mp_pc='%s', expected keyword 'endif'",GetTokenName((EScriptToken)*mp_pc))); #ifdef STOPWATCH_STUFF pIfSkipStopWatch->Start(); #endif ++mp_pc; #ifdef __NOPT_ASSERT__ m_if_status>>=1; #endif #ifdef STOPWATCH_STUFF pIfSkipStopWatch->Stop(); #endif } // Skips mp_pc forward till it finds an endswitch, skipping over any nested switch // statements in between. // Afterwards, mp_pc will point to the token following the endswitch. void CScript::skip_to_after_endswitch() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); while (*mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDSWITCH) { if (*mp_pc==ESCRIPTTOKEN_KEYWORD_SWITCH) { // Skip over the switch ++mp_pc; skip_to_after_endswitch(); } else { mp_pc=SkipToken(mp_pc); } } // Skip over the endswitch ++mp_pc; } // Given that mp_pc points to a switch token, this will skip mp_pc forward to the // code of the matching case. Or if none matches, it will point to after the endswitch. void CScript::execute_switch() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_SWITCH,("Unexpected *mp_pc='%s', expected keyword 'switch'",GetTokenName((EScriptToken)*mp_pc))); #ifdef __NOPT_ASSERT__ // Remember the old pc value so that the printed line number is correct if // no endswitch is found before the end of file is hit. uint8 *start_of_switch_pc=mp_pc; #endif // Skip over the switch token ++mp_pc; // Put the parameters following the switch keyword into mp_function_params Dbg_MsgAssert(mp_function_params,("NULL mp_function_params")); mp_function_params->Clear(); mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params); // Get the first component and make a copy of it, putting it into p_comp. // Making a copy cos mp_function_params is needed later to hold the params following // each case statement that follows. CComponent *p_switch_comp=new CComponent; CComponent *p_first_comp=mp_function_params->GetNextComponent(); if (p_first_comp) { Dbg_MsgAssert(mp_function_params->GetNextComponent(p_first_comp)==NULL,("\n%s\nswitch argument contains more than one component",GetScriptInfo())); CopyComponent(p_switch_comp,p_first_comp); } // Skip forward till we hit a matching case, or a default, or endswitch // But, if we hit another switch statement whilst looking, // skip over it, ignoring its cases. bool found=false; while (!found) { switch (*mp_pc) { case ESCRIPTTOKEN_KEYWORD_SWITCH: { // Skip over the switch ++mp_pc; skip_to_after_endswitch(); // mp_pc now points to the token following the ESCRIPTTOKEN_KEYWORD_ENDSCRIPT break; } case ESCRIPTTOKEN_KEYWORD_CASE: { ++mp_pc; mp_function_params->Clear(); mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params); CComponent *p_case_comp=mp_function_params->GetNextComponent(); if (p_case_comp) { Dbg_MsgAssert(mp_function_params->GetNextComponent(p_case_comp)==NULL,("\n%s\ncase argument contains more than one component",GetScriptInfo())); if (*p_switch_comp==*p_case_comp) { found=true; } } if (found) { // We've found a match, but we need to skip over any case statements // that immediately follow. This is for when multiple cases want to // execute the same chunk of code. while (true) { mp_pc=SkipEndOfLines(mp_pc); if (*mp_pc==ESCRIPTTOKEN_KEYWORD_CASE) { ++mp_pc; mp_function_params->Clear(); mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params); } else { break; } } } break; } case ESCRIPTTOKEN_KEYWORD_DEFAULT: case ESCRIPTTOKEN_KEYWORD_ENDSWITCH: ++mp_pc; found=true; break; case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT: #ifdef __NOPT_ASSERT__ mp_pc=start_of_switch_pc; Dbg_MsgAssert(0,("\n%s\nMissing endswitch",GetScriptInfo())); #endif break; default: mp_pc=SkipToken(mp_pc); break; } } CleanUpComponent(p_switch_comp); delete p_switch_comp; } void CScript::execute_begin() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_BEGIN,("Unexpected *mp_pc='%s', expected keyword 'begin'",GetTokenName((EScriptToken)*mp_pc))); ++mp_pc; // First, check whether we're about to run out of space on the small statically allocated loop buffer. if (mp_current_loop==mp_loops_small_buffer+NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE-1) { // There won't be enough room for the new loop! // A quick check, to ensure no memory leaks. Dbg_MsgAssert(mp_loops==mp_loops_small_buffer,("Expected mp_loops==mp_loops_small_buffer, %x, %x",mp_loops,mp_loops_small_buffer)); // Dynamically allocate a bigger buffer, and change mp_loops to point to that instead Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); mp_loops=(SLoop*)Mem::Malloc(MAX_NESTED_BEGIN_REPEATS * sizeof(SLoop)); Mem::Manager::sHandle().PopContext(); // Then copy over the contents of the small buffer, and update mp_current_loop to point into the new buffer. int i; for (i=0; i= mp_loops_small_buffer && mp_return_addresses[i].mpLoop < mp_loops_small_buffer+NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE,("Bad mp_return_addresses[i].mpLoop")); mp_return_addresses[i].mpLoop=(mp_return_addresses[i].mpLoop-mp_loops_small_buffer)+mp_loops; } } } if (mp_current_loop) { Dbg_MsgAssert(mp_current_loop-mp_loopsmpStart=mp_pc; // These get filled in once the repeat is reached. mp_current_loop->mpEnd=NULL; mp_current_loop->mGotCount=false; mp_current_loop->mNeedToReadCount=true; mp_current_loop->mCount=0; } void CScript::execute_repeat() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_REPEAT,("Unexpected *mp_pc='%s', expected keyword 'repeat'",GetTokenName((EScriptToken)*mp_pc))); Dbg_MsgAssert(mp_current_loop,("\n%s\nEncountered repeat with NULL mp_current_loop",GetScriptInfo())); if (mp_current_loop->mNeedToReadCount) { // Skip over the repeat token. ++mp_pc; Dbg_MsgAssert(mp_function_params,("NULL mp_function_params")); mp_function_params->Clear(); mp_current_loop->mpEnd=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params); mp_current_loop->mGotCount=mp_function_params->GetInteger(NO_NAME,&mp_current_loop->mCount); if (!mp_current_loop->mGotCount) { Dbg_MsgAssert(mp_current_loop->mpEnd==mp_pc,("\n%s\nNo count value found following 'repeat'",GetScriptInfo())); } mp_current_loop->mNeedToReadCount=false; } if (!mp_current_loop->mGotCount) { // It's an infinite loop. mp_pc=mp_current_loop->mpStart; } else { Dbg_MsgAssert(mp_current_loop->mCount,("\n%s\nZero count given to a begin-repeat loop",GetScriptInfo())); --mp_current_loop->mCount; if (mp_current_loop->mCount) { // Finite loop, but it has not finished yet, so jump back to the start. mp_pc=mp_current_loop->mpStart; } else { // The loop has finished! // Jump PC to the next instruction after the repeat. Dbg_MsgAssert(mp_current_loop->mpEnd,("NULL mp_current_loop->pEnd ??")); mp_pc=mp_current_loop->mpEnd; // Rewind to the previous loop. if (mp_current_loop==mp_loops) { mp_current_loop=NULL; } else { --mp_current_loop; Dbg_MsgAssert(mp_current_loop>=mp_loops,("\n%s\nBad mp_current_loop",GetScriptInfo())); } } } } void CScript::execute_break() { Dbg_MsgAssert(mp_pc,("NULL mp_pc")); Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_BREAK,("Unexpected *mp_pc='%s', expected keyword 'break'",GetTokenName((EScriptToken)*mp_pc))); Dbg_MsgAssert(mp_current_loop,("\n%s\nEncountered break with NULL mp_current_loop",GetScriptInfo())); // Step over every token until repeat is reached. #ifdef __NOPT_ASSERT__ int nested_if_count=0; #endif int nested_loop_count=0; bool in_loop=true; // Skip mp_pc to the end of the loop. while (in_loop) { switch (*mp_pc) { case ESCRIPTTOKEN_KEYWORD_BEGIN: ++nested_loop_count; break; case ESCRIPTTOKEN_KEYWORD_REPEAT: if (nested_loop_count) { --nested_loop_count; } else { in_loop=false; } break; #ifdef __NOPT_ASSERT__ case ESCRIPTTOKEN_KEYWORD_IF: ++nested_if_count; break; case ESCRIPTTOKEN_KEYWORD_ENDIF: if (nested_if_count) { --nested_if_count; } else { // Make sure that the stored if-status is rewound back to its // state before the loop was entered, otherwise the // 'too many nested ifs' assert will go off erroneously. m_if_status>>=1; } break; #endif case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT: Dbg_MsgAssert(0,("\n%s\nRepeat keyword not found after break keyword.",GetScriptInfo())); break; } mp_pc=SkipToken(mp_pc); } // Use AddComponentsUntilEndOfLine just to step over the possible repeat argument. // Ugh! Kind of ugly, but shouldn't be too slow because repeat will probably be followed // by nothing or just an integer. Dbg_MsgAssert(mp_function_params,("NULL mp_function_params")); mp_function_params->Clear(); mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc); // Rewind to the previous loop. if (mp_current_loop==mp_loops) { mp_current_loop=NULL; } else { --mp_current_loop; Dbg_MsgAssert(mp_current_loop>=mp_loops,("\n%s\nBad mp_current_loop",GetScriptInfo())); } } // Returns from a sub-script by popping the info (mp_pc etc) off the stack. // If there is nothing on the stack, it sets mp_pc to NULL // Returns true if the script being returned from was a script triggered by Interrupt. // This then allows Update() to not continue execution of the script that was interrupted. bool CScript::execute_return() { // The current script is finished with, so decrement its usage in the script cache. Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance(); Dbg_MsgAssert(p_script_cache,("NULL p_script_cache")); p_script_cache->DecrementScriptUsage(mScriptChecksum); bool was_interrupted=m_interrupted; if (m_num_return_addresses) { // This script was called from another, either by a regular call or by an interrupt. --m_num_return_addresses; // Delete the current mp_params, because it was created when this routine was called. Dbg_MsgAssert(mp_params,("\n%s\nNULL p_params",GetScriptInfo())); delete mp_params; // Restore the info stored on the stack. SReturnAddress *p_info=mp_return_addresses+m_num_return_addresses; mScriptChecksum = p_info->mScriptNameChecksum; mp_params = p_info->mpParams; mp_pc = p_info->mpReturnAddress; mpObject = p_info->mpObject; mp_current_loop = p_info->mpLoop; #ifdef __NOPT_ASSERT__ m_if_status = p_info->m_if_status; #endif m_wait_type = p_info->mWaitType; if (m_wait_type) { m_wait_timer = p_info->mWaitTimer; m_wait_period = p_info->mWaitPeriod; m_start_time = p_info->mStartTime; mp_wait_component = p_info->mpWaitComponent; } m_interrupted=p_info->mInterrupted; #ifdef __NOPT_ASSERT__ check_if_needs_to_be_watched_in_debugger(); #endif // Check that the script being returned to does still exist, ie mp_pc is valid. // However, if we're running an embedded script, don't do the check, because the // script name may be the name of the embedded script rather than a global script. if (!mp_struct_script) { Dbg_MsgAssert(LookUpSymbol(mScriptChecksum),("Non-existent script '%s' on call stack",FindChecksumName(mScriptChecksum))); } // Actually, we need a better check to see if mp_pc is still valid, because the // script we're returning to might have got reloaded and hence moved in memory. // Should use a smart pointer. // But, doesn't seem to have caused any problems so far, so leave for the moment ... } else { // Nothing to return to, so stop the script by setting mp_pc to NULL. // This occurs when an endscript is hit. mp_pc=NULL; } return was_interrupted; } #ifdef __NOPT_ASSERT__ #ifndef __PLAT_WN32__ void CScript::advance_pc_to_next_line_and_halt() { if (m_being_watched_in_debugger) { if (m_single_step_mode==STEP_OVER || m_single_step_mode==STEP_INTO) { // Record the current return address count so that it can be detected whether // the next command is a script call. Needed for stepping over script calls. m_last_num_return_addresses_when_halted=m_num_return_addresses; if (mp_pc) { mp_pc=SkipEndOfLines(mp_pc); } TransmitInfoToDebugger(); m_last_instruction_time_taken=0.0f; Dbg::CScriptDebugger::Instance()->ScriptDebuggerPause(); } } } #endif #endif //static int sInstructionCount=0; //static uint64 sLastVBlanks=0; // Update the script, executing instructions // REQUIREMENT: is mp_pc is NULL, then return ESCRIPTRETURNVAL_FINISHED, so the script is deleted by UpdateSpawnedScript EScriptReturnVal CScript::Update() { #ifdef __NOPT_ASSERT__ m_last_instruction_time_taken=0.0f; m_total_instruction_time_taken=0.0f; if (m_being_watched_in_debugger) { TransmitInfoToDebugger(); } #endif #ifdef STOPWATCH_STUFF pUpdateStopWatch->Start(); #endif #ifdef __NOPT_ASSERT__ Tmr::CPUCycles start_time = Tmr::GetTimeInCPUCycles(); #endif //if (sLastVBlanks!=Tmr::GetVblanks()) //{ // sLastVBlanks=Tmr::GetVblanks(); // printf("Instruction count = %d\n",sInstructionCount); // sInstructionCount=0; //} #ifdef __NOPT_ASSERT__ Obj::CObjectPtr p_obj = NULL; if (mpObject) { p_obj = mpObject; // remember the object we locked // mpObject->SetLockAssertOn(); } #endif m_skip_further_process_waits=false; // This loop is going to keep executing script commands until either mp_pc // becomes NULL somehow, or if some sort of wait command gets executed. while (true) { if (!mp_pc) { // Break out if mp_pc became NULL during the execution of the // last command. #ifdef STOPWATCH_STUFF pUpdateStopWatch->Stop(); #endif sCurrentlyUpdating=NULL; #ifdef __NOPT_ASSERT__ // if (mpObject) // { // // check the object we locked is the same one we are unlocking (or deleted) // Dbg_MsgAssert(p_obj == mpObject,("\n%s\nFinished Script has changed objects to %p! Original Object %p Still Locked! Tell Mick\n",GetScriptInfo(),mpObject.Convert(),p_obj.Convert())); // mpObject->SetLockAssertOff(); // } // If script is waiting, then we might be on a different object // if the original object still exists, then unlock it. if (p_obj) { p_obj->SetLockAssertOff(); } #endif #ifdef __NOPT_ASSERT__ start_time = Tmr::GetTimeInCPUCycles() - start_time; m_last_time = (int)start_time; // experiment with uint etc.... m_total_time += m_last_time; #endif // Seems like a reasonable thing to do ... m_interrupted=false; return ESCRIPTRETURNVAL_FINISHED; } else { // Make sure sCurrentlyUpdating is correct, because it may have // got changed during the execution of the last command. sCurrentlyUpdating=this; } #ifdef __NOPT_ASSERT__ bool was_waiting_before; if (m_wait_type==WAIT_TYPE_NONE) { was_waiting_before=false; } else { was_waiting_before=true; } #endif if (!m_skip_further_process_waits) { process_waits(); } // Return straight away if waiting. if (m_wait_type!=WAIT_TYPE_NONE) { #ifdef STOPWATCH_STUFF pUpdateStopWatch->Stop(); #endif sCurrentlyUpdating=NULL; #ifdef __NOPT_ASSERT__ // If script is waiting, then we might be on a different object // if the original object still exists, then unlock it. if (p_obj) { p_obj->SetLockAssertOff(); } #endif #ifdef __NOPT_ASSERT__ start_time = Tmr::GetTimeInCPUCycles() - start_time; m_last_time = (int)start_time; // experiment with uint etc.... m_total_time += m_last_time; #endif // If an interrupt script hits a blocking command, then clear the interrupted flag. // Reason: // If an interrupt script contains no blocking functions, then a single call to CScript::Update() // would execute the whole script, and as soon as the endscript was reached it was exit the Update() // function (due to the interrupt flag being on) hence returning control to the original Update() // function that was executing the interrupted script, and all would be well. // However, if the interrupt script did hit a blocking function, that would cause an early exit from // the Update() function, leaving it to the original Update() function, ie the one that was executing the // interrupted script, to finish completion of the interrupt script. In that case, we don't want // the interrupt flag to be on any more, because otherwise it would cause a premature exit from // the original Update() function as soon as the endscript was reached. m_interrupted=false; // Set this to prevent any previous CScript::Update() loop that the return may return to // from processing the waits further, otherwise if an interrupt script contains a wait n gameframes // it will end up waiting only n-1 gameframes, due to the interrupted scripts Update() loop calling // process_waits again. m_skip_further_process_waits=true; if (m_wait_type==WAIT_TYPE_BLOCKED) { return ESCRIPTRETURNVAL_BLOCKED; } else { return ESCRIPTRETURNVAL_WAITING; } } else { #ifdef __NOPT_ASSERT__ if (was_waiting_before) { advance_pc_to_next_line_and_halt(); } #endif } // Execute whatever is at mp_pc #ifdef __NOPT_ASSERT__ Tmr::CPUCycles instruction_start_time = Tmr::GetTimeInCPUCycles(); #endif switch (*mp_pc) { case ESCRIPTTOKEN_KEYWORD_IF: //++sInstructionCount; // This will execute the function call or expression following the if, // and if the command or expression returns false, it will skip // mp_pc forward to the token after the else or endif. // Otherwise, mp_pc will be left pointing to the first token of the 'true' block. execute_if(); // Note: mp_pc may be NULL at this point break; case ESCRIPTTOKEN_KEYWORD_ELSE: //++sInstructionCount; // The only time an else token should be hit is at the end of executing // the 'true' block following the if. // So this will just skip mp_pc over the 'false' block to after the endif. execute_else(); break; case ESCRIPTTOKEN_KEYWORD_ENDIF: //++sInstructionCount; // We can hit an endif either by finishing the 'true' block of an if-statement // that has no else, or by finishing the 'false' block following an else. // Either way there is nothing to do, so just skip over it, but assert if we're // not in an if-statement to catch spurious else's. execute_endif(); break; case ESCRIPTTOKEN_KEYWORD_SWITCH: //++sInstructionCount; // Skips mp_pc forward to the token following the matching case. // Or if none matches, it will point to after the endswitch. execute_switch(); break; case ESCRIPTTOKEN_KEYWORD_CASE: case ESCRIPTTOKEN_KEYWORD_DEFAULT: case ESCRIPTTOKEN_KEYWORD_ENDSWITCH: //++sInstructionCount; // If we hit a case, default or endswitch, it must be due to the completion of the // previous case statement's commands. // So skip mp_pc forward so that it points to after the endswitch token. skip_to_after_endswitch(); break; case ESCRIPTTOKEN_KEYWORD_BEGIN: //++sInstructionCount; // Initialise a new loop counter execute_begin(); break; case ESCRIPTTOKEN_KEYWORD_REPEAT: //++sInstructionCount; // This will either jump mp_pc back to the start of the loop, // or skip it past the repeat if the loop has finished. execute_repeat(); break; case ESCRIPTTOKEN_KEYWORD_BREAK: //++sInstructionCount; // This will skip mp_pc to after the repeat of the current loop execute_break(); break; case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT: //++sInstructionCount; if (execute_return()) { // If we returned from a script call caused by an interrupt, then do not continue // with execution of the interrupted script, since an interrupt should not affect the // interrupted script. return ESCRIPTRETURNVAL_FINISHED_INTERRUPT; } // Note: mp_pc may be NULL at this point, if there is no calling script. break; case ESCRIPTTOKEN_KEYWORD_RETURN: //++sInstructionCount; // The return keyword works very similar to hitting the endscript token. // The difference is that 'return' will merge the parameters that follow the // return keyword onto the parameters of the calling script, if there is one. // Skip over the return token. ++mp_pc; // Put any parameters that follow into mp_function_params Dbg_MsgAssert(mp_function_params,("NULL mp_function_params")); mp_function_params->Clear(); mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params); if (execute_return()) { // If we returned from a script call caused by an interrupt, then do not continue // with execution of the interrupted script, since an interrupt should not affect the // interrupted script. // For the same reason we are bailing out here before the returned parameters are merged onto // the interrupted scripts params, so that they will not be affected by the interrupt either. return ESCRIPTRETURNVAL_FINISHED_INTERRUPT; } // Note: mp_pc may be NULL at this point, if there is no calling script. // Merge the return values onto the parameters of the calling script. Dbg_MsgAssert(mp_params,("NULL mp_params ?")); mp_params->AppendStructure(mp_function_params); break; case ESCRIPTTOKEN_KEYWORD_RANDOM: case ESCRIPTTOKEN_KEYWORD_RANDOM2: case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT: case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE: case ESCRIPTTOKEN_JUMP: // Modify mp_pc according to any jump or random operators, and repeat until mp_pc no // longer points to a jump or random. mp_pc=DoAnyRandomsOrJumps(mp_pc); break; case ESCRIPTTOKEN_ENDOFLINE: ++mp_pc; break; case ESCRIPTTOKEN_ENDOFLINENUMBER: mp_pc+=5; // 1 for the token, 4 for the line number. break; default: //++sInstructionCount; //printf("Running line %d in script '%s'\n",GetLineNumber(mp_pc),FindChecksumName(mScriptChecksum)); execute_command(); // Note: mp_pc may be NULL at this point break; } #ifdef __NOPT_ASSERT__ if (m_being_watched_in_debugger) { Tmr::CPUCycles t=Tmr::GetTimeInCPUCycles()-instruction_start_time; float last_instruction_time_taken=((float)((int)t))/150.0f; m_last_instruction_time_taken+=last_instruction_time_taken; m_total_instruction_time_taken+=last_instruction_time_taken; } if (m_single_step_mode==STEP_OVER && m_num_return_addresses > m_last_num_return_addresses_when_halted) { // If the number of return addresses has increased beyond what it was when last halted // in the debugger, then the last command must have been a script call, so do not halt // but keep executing instructions until the script call has finished, which will be // indicated by the return address count returning to what it was or lower. // (It would be lower if say a goto occurred during the script call) } else { if (m_wait_type == WAIT_TYPE_NONE) { advance_pc_to_next_line_and_halt(); } } #endif } } void CScript::Wait(int num_frames) { m_wait_type=WAIT_TYPE_COUNTER; m_wait_timer=num_frames; } void CScript::WaitOnePerFrame(int num_frames) { m_wait_type=WAIT_TYPE_ONE_PER_FRAME; m_wait_timer=num_frames; } void CScript::WaitTime(Tmr::Time period) { m_wait_type=WAIT_TYPE_TIMER; m_wait_period=period; // Record the start time. m_start_time=Tmr::GetTime(); } bool CScript::RefersToScript(uint32 checksum) { if (mScriptChecksum==checksum) { return true; } for (int i=0; iInNetGame()) { Net::MsgDesc msg_desc; msg.m_ScriptName = scriptChecksum; msg.m_ObjID = -1; if( p_object ) { msg.m_ObjID = p_object->GetID(); } size = WriteToBuffer(p_params, (uint8 *) msg.m_Data, GameNet::MsgRunScript::vMAX_SCRIPT_PARAMS_LEN ); Dbg_MsgAssert( size <= GameNet::MsgRunScript::vMAX_SCRIPT_PARAMS_LEN,( "Script too large to send over the net\n" )); client = gamenet_man->GetClient( 0 ); Dbg_Assert( client ); msg_size = ( sizeof( GameNet::MsgRunScript ) - GameNet::MsgRunScript::vMAX_SCRIPT_PARAMS_LEN ) + size; msg_desc.m_Data = &msg; msg_desc.m_Length = msg_size; msg_desc.m_Id = GameNet::MSG_ID_RUN_SCRIPT; msg_desc.m_Queue = Net::QUEUE_SEQUENCED; msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS; client->EnqueueMessageToServer( &msg_desc ); } #endif } #ifdef __NOPT_ASSERT__ static const char *sGetRunScriptName(uint32 scriptChecksum, const char *p_scriptName) { if (p_scriptName) { return p_scriptName; } return FindChecksumName(scriptChecksum); } #endif // Used for running a simple script from start to end. void RunScript(uint32 scriptChecksum, CStruct *p_params, Obj::CObject *p_object, bool netScript, const char *p_scriptName ) { // First, see what type of symbol scriptChecksum is referring to. CSymbolTableEntry *p_entry=Resolve(scriptChecksum); if (!p_entry) { #ifdef __NOPT_ASSERT__ printf("Warning! RunScript could not find '%s'\n",sGetRunScriptName(scriptChecksum,p_scriptName)); #endif return; } if (p_entry->mType!=ESYMBOLTYPE_QSCRIPT && p_entry->mType!=ESYMBOLTYPE_CFUNCTION && p_entry->mType!=ESYMBOLTYPE_MEMBERFUNCTION) { #ifdef __NOPT_ASSERT__ printf("Warning! RunScript sent '%s' which is not a script, a c-function or a member function. Type=%s\n",sGetRunScriptName(scriptChecksum,p_scriptName),GetTypeName(p_entry->mType)); #endif return; } if( netScript ) { SendScript( scriptChecksum, p_params, p_object ); } switch (p_entry->mType) { case ESYMBOLTYPE_CFUNCTION: { // If the symbol is actually a c-function rather than a script, then run the c-function. // This is handy sometimes because it saves having to write a special script just to run // one c-function. Dbg_MsgAssert(p_entry->mpCFunction,("NULL pCFunction")); // Mick: We now pass in NULL, as creating a dummy script is very slow (*p_entry->mpCFunction)(p_params,NULL); break; } case ESYMBOLTYPE_MEMBERFUNCTION: { Dbg_MsgAssert(p_object,("Tried to run member function '%s' on NULL p_object",Script::FindChecksumName(scriptChecksum))); // Mick: We now pass in NULL, as creating a dummy script is very slow p_object->CallMemberFunction(scriptChecksum,p_params,NULL); break; } case ESYMBOLTYPE_QSCRIPT: { Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); CScript *p_script=new CScript; #ifdef __NOPT_ASSERT__ p_script->SetCommentString("Created by RunScript(...)"); #endif p_script->SetScript(scriptChecksum,p_params,p_object); Mem::Manager::sHandle().PopContext(); while (true) { EScriptReturnVal ret_val=p_script->Update(); if (ret_val==ESCRIPTRETURNVAL_FINISHED) { break; } //Dbg_MsgAssert(0,("\n%s\nScript did not finish when run by RunScript.",p_script->GetScriptInfo())); // Script must not get blocked, otherwise it'll hang in this loop forever. Dbg_MsgAssert(ret_val!=ESCRIPTRETURNVAL_BLOCKED,("\n%s\nScript got blocked when being run by RunScript.",p_script->GetScriptInfo())); // Note: OK if the script returns ESCRIPTRETURNVAL_WAITING, because the wait period will eventually end. } delete p_script; break; } default: break; } } void RunScript(const char *p_scriptName, CStruct *p_params, Obj::CObject *p_object, bool netScript ) { // Quite often, if a script cannot be found then FindChecksumName will not be able to find // the checksum name either, so since we know the name here it gets passed along to // the other version of RunScript so that the name can be printed in any warning message. RunScript(Crc::GenerateCRCFromString(p_scriptName), p_params, p_object, netScript, p_scriptName); } ///////////////////////////////////////////////////////////////// // // Spawning script stuff // ///////////////////////////////////////////////////////////////// static bool s_updating_scripts = false; static bool s_delete_scripts_pending = false; // This gets called from within DeleteSymbols. void DeleteSpawnedScripts() { // Guard against deleting spawned scripts while we are updating them if( s_updating_scripts ) { s_delete_scripts_pending = true; return; } CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); // Don't kill the script if it is not session-specific. // This feature is currently used by one of Steve's net scripts which needs to not // be killed by ScriptCleanup if (p_script->mIsSpawned && !p_script->mNotSessionSpecific) { delete p_script; } p_script=p_next; } } // This gets called from Front::PauseGame void PauseSpawnedScripts(bool status) { CScript *p_script=GetNextScript(); while (p_script) { if (p_script->mIsSpawned) { p_script->mPaused=status; } p_script=GetNextScript(p_script); } } void UnpauseSpawnedScript(CScript* p_script) { if (p_script && p_script->mIsSpawned) { p_script->mPaused=false; } } // Note: Does this need to be fast? uint32 NumSpawnedScriptsRunning() { uint32 num_spawned_scripts=0; CScript *p_script=GetNextScript(); while (p_script) { if (p_script->mIsSpawned) { ++num_spawned_scripts; } p_script=GetNextScript(p_script); } return num_spawned_scripts; } // Called once a frame. Currently called from Skate::DoUpdate() void UpdateSpawnedScripts() { s_updating_scripts = true; s_done_one_per_frame = false; CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next; // get the next actual spawned script p_next=GetNextScript(p_script); // skip any non-spawned scripts // since they might get deleted in an unsafe manner while (p_next && !p_next->mIsSpawned) { p_next=GetNextScript(p_next); } Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,("Next script in spawned list has been deleted sometime earlier!")); if (p_next == p_script) { Dbg_MsgAssert(0,("%s\nLoop in list of spawned scripts!!",p_script->GetScriptInfo())); } // K: Added the || !p_script->GotScript() so that spawned scripts which have been cleared still get // cleaned up when the game is paused. This was to fix a bug (TT1453) where cleared scripts where accumulating // during the cutscenes. The scripts were the scripts of goal peds, which got cleared when the peds were // killed. if (p_script->mIsSpawned && (!p_script->mPaused || !p_script->GotScript()) && (!p_script->mPauseWithObject || !p_script->mpObject || p_script->mpObject->ShouldUpdatePauseWithObjectScripts())) { if (p_script->Update()==ESCRIPTRETURNVAL_FINISHED) { // just doing the assertion before we delete the script Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,("%s\nNext script in spawned list has been deleted by this script updating",p_script->GetScriptInfo())); // If it had a callback script specified, run it. if (p_script->mCallbackScript) { RunScript(p_script->mCallbackScript, p_script->mpCallbackScriptParams, p_script->mpObject); } // just doing the assertion before we delete the script Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1, ("Next script in spawned list has been deleted by callback script (%s)",FindChecksumName(p_script->mCallbackScript))); // Kill it now that it has finished. delete p_script; } else { Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,("%s\nNext script in spawned list has been deleted by this script",p_script->GetScriptInfo())); } } p_script=p_next; Dbg_MsgAssert(GetNextScript(p_script) != (CScript*)-1,("Next script in spawned list has been deleted by this script")); } s_updating_scripts = false; if( s_delete_scripts_pending ) { DeleteSpawnedScripts(); s_delete_scripts_pending = false; } } // Sned spawn script events to other clients void SendSpawnScript( uint32 scriptChecksum, Obj::CObject *p_object, int node, bool permanent ) { #ifdef __PLAT_WN32__ // No GameNet stuff if compiling on PC #else GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); Net::Client* client; GameNet::MsgSpawnAndRunScript msg; if( gamenet_man->InNetGame()) { Net::MsgDesc msg_desc; msg.m_ScriptName = scriptChecksum; msg.m_ObjID = -1; msg.m_Node = node; msg.m_Permanent = (char) permanent; if( p_object ) { msg.m_ObjID = p_object->GetID(); } client = gamenet_man->GetClient( 0 ); Dbg_Assert( client ); msg_desc.m_Data = &msg; msg_desc.m_Length = sizeof( GameNet::MsgSpawnAndRunScript ); msg_desc.m_Id = GameNet::MSG_ID_SPAWN_AND_RUN_SCRIPT; msg_desc.m_Queue = Net::QUEUE_SEQUENCED; msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS; client->EnqueueMessageToServer( &msg_desc ); } #endif } CScript *GetScriptWithUniqueId(uint32 id) { CScript *p_script=GetNextScript(); while (p_script) { if (p_script->GetUniqueId() == id) return p_script; p_script = GetNextScript(p_script); } return NULL; } // Called from ScriptSpawnScript in cfuncs.cpp // also called by the triggering code in skater.cpp // returns the new script if sucessful, asserts if not // optional "node" parameter is the node number that is // responsible for spawning this script. // Also now takes an optional Id, to allow individual spawned script instances to be killed. CScript* SpawnScript(uint32 scriptChecksum, CStruct *p_scriptParams, uint32 callbackScript, CStruct *p_callbackParams, int node, uint32 id, bool netEnabled, bool permanent, bool not_session_specific, bool pause_with_object ) { Dbg_MsgAssert(scriptChecksum,("Zero checksum sent to SpawnScript")); CSymbolTableEntry *p_entry=Resolve(scriptChecksum); if (p_entry) { if (p_entry->mType==ESYMBOLTYPE_CFUNCTION) { Dbg_MsgAssert(callbackScript==0,("A callbackScript cannot currently be specified when running SpawnScript on a c-function. (cfunc='%s' callback='%s')",FindChecksumName(scriptChecksum),FindChecksumName(callbackScript))); // Creating a dummy script to send to the c-function. Normally the c-function would // only be called from within a script's update function, and it uses the passed script // pointer in any call to GetScriptInfo if an assert goes off. // So we need to pass a dummy rather than NULL so that it does not crash if it // dereferences the pointer. Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); CScript *p_dummy=new CScript; Mem::Manager::sHandle().PopContext(); (*p_entry->mpCFunction)(p_scriptParams,p_dummy); delete p_dummy; return NULL; } Dbg_MsgAssert(p_entry->mType!=ESYMBOLTYPE_MEMBERFUNCTION,("SpawnScript cannot run the member function '%s'",FindChecksumName(scriptChecksum))); } CScript *p_script=new CScript; p_script->SetScript(scriptChecksum, p_scriptParams, NULL); #ifdef __NOPT_ASSERT__ p_script->SetCommentString("Created by SpawnScript"); #endif p_script->mNode = node; p_script->mIsSpawned = true; p_script->mId = id; p_script->mPaused = false; p_script->mNotSessionSpecific=not_session_specific; p_script->mPauseWithObject = pause_with_object; Dbg_MsgAssert(p_script->mpCallbackScriptParams==NULL,("p_script->mpCallbackScriptParams not NULL ?")); if (callbackScript) { p_script->mCallbackScript=callbackScript; p_script->mpCallbackScriptParams=new CStruct; if (p_callbackParams) { *p_script->mpCallbackScriptParams+=*p_callbackParams; } } #ifdef __PLAT_WN32__ // No GameNet stuff if compiling on PC #else GameNet::Manager * gamenet_man = GameNet::Manager::Instance(); if( netEnabled && gamenet_man->InNetGame()) { // TODO: Should pass on the not_session_specific flag ... SendSpawnScript( scriptChecksum, NULL, node, permanent ); } #endif return p_script; } bool ScriptExists(uint32 scriptNameChecksum) { CSymbolTableEntry *p_entry=Resolve(scriptNameChecksum); if (p_entry && p_entry->mType==ESYMBOLTYPE_QSCRIPT) { return true; } return false; } CScript* SpawnScript(const char *p_scriptName, CStruct *p_scriptParams, uint32 callbackScript, CStruct *p_callbackParams, int node, uint32 id, bool netEnabled, bool permanent, bool not_session_specific, bool pause_with_object ) { return SpawnScript(Crc::GenerateCRCFromString(p_scriptName),p_scriptParams,callbackScript,p_callbackParams,node,id, netEnabled, permanent, not_session_specific, pause_with_object); } // Kills a spawned script, given a pointer to the actual script void KillSpawnedScript(CScript *p_script) { if (p_script && p_script->mIsSpawned) { delete p_script; } } // Kills all spawned scripts that are either currently running this script directly, // or have it in their call stack somewhere. (Ie, they will return to it later) void KillSpawnedScriptsThatReferTo(uint32 checksum) { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); if (p_script->mIsSpawned && p_script->RefersToScript(checksum)) { // delete p_script; p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=p_next; } } // Kills all scripts with this particular Id. void KillSpawnedScriptsWithId(uint32 id) { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); if (p_script->mIsSpawned && (p_script->mId==id || p_script->GetUniqueId()==id)) { // Mick, instead of deleteing the script, we now just clear it // this stops it from executing, but leaves it in the linked list // so it can be cleaned up next time around "UpdateSpawnedScripts" // Scripts that have ClearScript() called will automatically be deleted // as they have mp_pc set to NULL // delete p_script; p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=p_next; } } // Kills all spawned scripts that have the passed object as their parent object. // This gets called from within Script::KillAllScriptsWithObject. void KillSpawnedScriptsWithObject(Obj::CObject *p_object) { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); if (p_script->mIsSpawned && p_script->RefersToObject(p_object)) { // delete p_script; p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=p_next; } } // Kills all spawned scripts with a particular Id & parent object. // Called by Matt's Obj_KillSpawnedScript CObject script member function. void KillSpawnedScriptsWithObjectAndId(Obj::CObject *p_object, uint32 id, CScript *p_callingScript) { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); if (p_script->mIsSpawned && p_script!=p_callingScript && p_script->RefersToObject(p_object) && p_script->mId==id) { // delete p_script; p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=p_next; } } // Kills all spawned scripts with a particular Id & parent object. // Called by Matt's Obj_KillSpawnedScript CObject script member function. void KillSpawnedScriptsWithObjectAndName(Obj::CObject *p_object, uint32 name, CScript *p_callingScript ) { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); if (p_script->mIsSpawned && p_script!=p_callingScript && p_script->RefersToObject(p_object) && p_script->mScriptChecksum==name) { // delete p_script; p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=p_next; } } // Returns: 0 if spawned script has no ID, or if the script isn't a spawned script // Otherwise, returns the ID: uint32 FindSpawnedScriptID(CScript *p_script) { if (!p_script) { return 0; } if (!p_script->mIsSpawned) { return 0; } return p_script->mId; } CScript* FindSpawnedScriptWithID(uint32 id) { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); if (p_script->mIsSpawned && p_script->mId==id) { return p_script; } p_script=p_next; } return NULL; } // TODO: This just included to allow compilation of GenerateCRC, remove later. uint32 GenerateCRC(const char *p_string) { return Crc::GenerateCRCFromString(p_string); } // Stops all scripts. // Currently only used by the StopAllScripts script function, which currently isn't used // anywhere, but might be handy one day. void StopAllScripts() { CScript *p_script=GetNextScript(); while (p_script) { p_script->ClearScript(); p_script->ClearEventHandlerTable(); p_script=GetNextScript(p_script); } } // Stops all scripts with a particular object as their parent, but does not delete them. // They cannot be restarted after being stopped. // This is basically a way of killing the scripts without actually // deleting them, so it won't cause any invalid script pointers. // Currently gets called at the end of the CObject destructor. void StopAllScriptsUsingThisObject(Obj::CObject *p_object) { StopScriptsUsingThisObject(p_object, 0); } // If scriptCrc != 0, then only stop scripts with that ID, otherwise stop all scripts using object void StopScriptsUsingThisObject(Obj::CObject *p_object, uint32 scriptCrc) { CScript *p_script=GetNextScript(); while (p_script) { if (p_script->RefersToObject(p_object) && (!scriptCrc || p_script->mScriptChecksum == scriptCrc)) { p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=GetNextScript(p_script); } } // TODO: brad - this is a last minute fix to StopScriptsUsingThisObject. // Rather than fixing the existing function so close to release, I've added // this version to be called only when we know we want to use it. void StopScriptsUsingThisObject_Proper(Obj::CObject *p_object, uint32 scriptCrc) { CScript *p_script=GetNextScript(); while ( p_script ) { if ( p_script->RefersToObject( p_object ) && ( !scriptCrc || p_script->RefersToScript( scriptCrc ) ) ) { p_script->ClearScript(); p_script->ClearEventHandlerTable(); } p_script=GetNextScript( p_script ); } } // Kills any stopped scripts, ie scripts with their mpPC null. These // would be scripts stopped by the above StopAllScriptsUsingThisObject void KillStoppedScripts() { CScript *p_script=GetNextScript(); while (p_script) { CScript *p_next=GetNextScript(p_script); // Delete the script if it has stopped (mpPC==NULL), but not if it is a spawned script, // because otherwise it will leave a dangling pointer in the array of spawned scripts, // which will then get dereferenced in UpdateSpawnedScripts and crash. // If the script has stopped, the next call to UpdateSpawnedScripts will delete it, so // the script is guaranteed to get cleaned up eventually. #ifdef __SCRIPT_EVENT_TABLE__ if (!p_script->GotScript() && !p_script->mIsSpawned && !p_script->GetEventHandlerTable()) #else if (!p_script->GotScript() && !p_script->mIsSpawned) #endif { #ifdef __NOPT_ASSERT__ printf("Cleaning up script '%s'\n",FindChecksumName(p_script->mScriptChecksum)); #endif delete p_script; } p_script=p_next; } } // Run by the DumpScripts script function. // Prints out the names of all the currently existing scripts. void DumpScripts() { #ifdef __NOPT_ASSERT__ printf("###########################################################\n\n"); printf("All the CScripts that currently exist ...\n\n"); CScript *p_scr=GetNextScript(); int n = 0; while (p_scr) { printf ("%3d %8d ",p_scr->m_last_time/150, p_scr->m_total_time/150); p_scr->m_last_time = 0; p_scr->m_total_time = 0; printf ("%3d: ",n++); if (p_scr->mIsSpawned) { printf("S "); } else { printf(" "); } switch (p_scr->GetWaitType()) { case WAIT_TYPE_NONE: printf("None "); break; case WAIT_TYPE_COUNTER: printf("Counter "); break; case WAIT_TYPE_ONE_PER_FRAME: printf("one_per_frame "); break; case WAIT_TYPE_TIMER: printf("Timer "); break; case WAIT_TYPE_BLOCKED: printf("Blocked "); break; case WAIT_TYPE_OBJECT_MOVE: printf("Ob Move "); break; case WAIT_TYPE_OBJECT_ANIM_FINISHED: printf("Ob AnimFinished "); break; case WAIT_TYPE_OBJECT_JUMP_FINISHED: printf("Ob JumpFinished "); break; case WAIT_TYPE_OBJECT_ROTATE: printf("Ob Rotate "); break; default: printf("Unknown ?????? "); break; } if (p_scr->Finished()) { printf("[Fin] "); } uint32 checksum=p_scr->GetBaseScript(); if (checksum) { CSymbolTableEntry *p_sym=LookUpSymbol(checksum); Dbg_MsgAssert(p_sym,("NULL pSym ??")); // printf("%s: %s\n",FindChecksumName(p_sym->mSourceFileNameChecksum),FindChecksumName(checksum)); printf("%s\n",FindChecksumName(checksum)); } else { printf (", Checksum is NULL, probably dead script\n"); } p_scr=GetNextScript(p_scr); } printf("\n"); #endif } #ifdef __SCRIPT_EVENT_TABLE__ /* Sets up the table that specifies which scripts to run in response to which events. See object scripting document. */ void CScript::SetEventHandlers(Script::CArray *pArray, EReplaceEventHandlers replace) { Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap()); if (!mp_event_handler_table) { mp_event_handler_table = new Obj::CEventHandlerTable(); } else { mp_event_handler_table->unregister_all(this); } mp_event_handler_table->add_from_script(pArray, replace); // Mick: We still need the compress_table for this way of adding event handlers // as the "replace" logic otherwise leads to a rapidly growing table. // especially on the on-screen keyboard mp_event_handler_table->compress_table(); mp_event_handler_table->register_all(this); Mem::Manager::sHandle().PopContext(); } void CScript::SetEventHandler(uint32 ex, uint32 scr, uint32 group, bool exception, CStruct *p_params) { Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap()); // Need to create the table if not there if (!mp_event_handler_table) { mp_event_handler_table = new Obj::CEventHandlerTable(); } // Add the event to the table mp_event_handler_table->AddEvent(ex,scr,group, exception, p_params); // Inregistering the event receiver, all we are doing is adding an // script pointer to the list of scripts that handle this // type of exception // there is nothing specific to the actual handler // if the script is already in the list, then nothing needs changing. Obj::CTracker::Instance()->RegisterEventReceiver(ex, this); Mem::Manager::sHandle().PopContext(); } /* Removes an entry in the event table with the given type id */ void CScript::RemoveEventHandler(uint32 type) { if (!mp_event_handler_table) return; mp_event_handler_table->remove_entry(type); // mp_event_handler_table->compress_table(); // Refresh the Obj::Tracker Obj::CTracker::Instance()->UnregisterEventReceiver(type, this); } /* Removes all entries in the event table with the given group id */ void CScript::RemoveEventHandlerGroup(uint32 group) { if (!mp_event_handler_table) return; mp_event_handler_table->unregister_all(this); mp_event_handler_table->remove_group(group); // mp_event_handler_table->compress_table(); mp_event_handler_table->register_all(this); } /* Dumps the event table */ void CScript::PrintEventHandlerTable ( ) { Obj::CEventHandlerTable::sPrintTable(mp_event_handler_table); } bool CScript::PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast) { if (mp_event_handler_table) mp_event_handler_table->pass_event(pEvent, this, broadcast); return true; } void CScript::SetOnExceptionScriptChecksum(uint32 OnExceptionScriptChecksum) { mOnExceptionScriptChecksum = OnExceptionScriptChecksum; } uint32 CScript::GetOnExceptionScriptChecksum() const { return mOnExceptionScriptChecksum; } void CScript::SetOnExitScriptChecksum(uint32 OnExitScriptChecksum) { mOnExitScriptChecksum = OnExitScriptChecksum; } uint32 CScript::GetOnExitScriptChecksum() const { return mOnExitScriptChecksum; } #endif } // namespace Script