From 07ead5b95df4f70e892e4534e4839ec9dc7b6d42 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Mon, 11 Sep 2023 00:25:36 +0200 Subject: [PATCH] Mission system: use event callback to trigger setup/teardown. The mission-specific callbacks `loadMission(filename, rg)` and `unloadMission()` were decomissioned; instead the name and RG of the .mission file gets delivered via SE_ANGELSCRIPT_MANIPULATIONS+ASMANIP_SCRIPT_LOADED, (params #6 and #7) and unloading is triggered by SE_ANGELSCRIPT_MANIPULATIONS+ASMANIP_SCRIPT_UNLOADING. This archives the same result with less copypasted callback-invocation code. Minor changes: MANIP_ enum fields got renamed to ASMANIP_ to match C++ with AngelScript. Doxygen docs were added. --- doc/angelscript/Script2Game/globals.h | 10 ++++- resources/scripts/mission_default.as | 33 +++++++++------- source/main/GameContext.cpp | 23 ++++++++--- source/main/GameContext.h | 3 +- source/main/gameplay/ScriptEvents.h | 9 +++-- source/main/main.cpp | 11 +++--- source/main/resources/CacheSystem.cpp | 6 +++ source/main/scripting/ScriptEngine.cpp | 38 ------------------- source/main/scripting/ScriptEngine.h | 6 +-- .../bindings/ScriptEventsAngelscript.cpp | 6 +-- source/main/system/ConsoleCmd.cpp | 2 +- source/main/terrain/Terrain.cpp | 5 +-- 12 files changed, 72 insertions(+), 80 deletions(-) diff --git a/doc/angelscript/Script2Game/globals.h b/doc/angelscript/Script2Game/globals.h index 8aa950db8d..76b574b201 100644 --- a/doc/angelscript/Script2Game/globals.h +++ b/doc/angelscript/Script2Game/globals.h @@ -67,7 +67,7 @@ void print(const string message); SE_TRUCK_TELEPORT //!< triggered when the user teleports the truck, the argument refers to the Actor Instance ID (use `game.getTruckByNum()`) SE_TRUCK_MOUSE_GRAB //!< triggered when the user uses the mouse to interact with the actor, the argument refers to the Actor Instance ID (use `game.getTruckByNum()`) - SE_ANGELSCRIPT_MANIPULATIONS //!< triggered when the user tries to dynamically use the scripting capabilities (prevent cheating) + SE_ANGELSCRIPT_MANIPULATIONS //!< triggered when the user tries to dynamically use the scripting capabilities (prevent cheating) args: #1 angelScriptManipulationType, #2 ScriptUnitId_t, #3 RoR::ScriptCategory, #4 unused, #5 script file name (*.as), #6 associated file name (i.e. *.mission), #7 associated file resource group (i.e. *.mission). SE_ANGELSCRIPT_MSGCALLBACK //!< The diagnostic info directly from AngelScript engine (see `asSMessageInfo`), args: #1 ScriptUnitID, #2 asEMsgType, #3 row, #4 col, #5 sectionName, #6 message SE_ANGELSCRIPT_LINECALLBACK //!< The diagnostic info directly from AngelScript engine (see `SetLineCallback()`), args: #1 ScriptUnitID, #2 LineNumber, #3 CallstackSize, #4 unused, #5 FunctionName, #6 FunctionObjectTypeName #7 ObjectName SE_ANGELSCRIPT_EXCEPTIONCALLBACK //!< The diagnostic info directly from AngelScript engine (see `SetExceptionCallback()`), args: #1 ScriptUnitID, #2 unused, #3 row (`GetExceptionLineNumber()`), #4 unused, #5 funcName, #6 message (`GetExceptionString()`) @@ -79,6 +79,14 @@ void print(const string message); }; +/// Argument #2 of script event `SE_ANGELSCRIPT_MANIPULATIONS` +enum angelScriptManipulationType +{ + ASMANIP_CONSOLE_SNIPPET_EXECUTED = 0, // 0 for Backwards compatibility. + ASMANIP_SCRIPT_LOADED, //!< Triggered after the script's `main()` completed; may trigger additional processing (for example, it delivers the *.mission file to mission system script). + ASMANIP_SCRIPT_UNLOADING //!< Triggered before unloading the script to let it clean up (important for missions). +}; + enum angelScriptManipulationType { MANIP_CONSOLE_SNIPPET_EXECUTED = 0, // Backwards compat diff --git a/resources/scripts/mission_default.as b/resources/scripts/mission_default.as index 71e42c3176..7ba137cf9b 100644 --- a/resources/scripts/mission_default.as +++ b/resources/scripts/mission_default.as @@ -16,24 +16,31 @@ MissionManager g_missions(); /* A mandatory startup function */ void main() { + game.registerForEvent(SE_ANGELSCRIPT_MANIPULATIONS); // Necessary to receive mission setup info game.log("default mission script loaded"); } -/* A mandatory callback for setting up a mission */ -bool loadMission(string filename, string resource_group) +/* Event handler callback */ +void eventCallbackEx(scriptEvents ev, int arg1, int arg2ex, int arg3ex, int arg4ex, string arg5ex, string arg6ex, string arg7ex, string arg8ex) { - game.log("loading mission file '"+filename+"' (resource group '"+resource_group+"')"); - bool result = g_missions.loadMission(filename, resource_group); - game.log("finished loading mission file '"+filename+"' (resource group '"+resource_group+"'), result: " + result); - return result; + if (ev == SE_ANGELSCRIPT_MANIPULATIONS) + { + // args: #1 angelScriptManipulationType, #2 ScriptUnitId_t, #3 RoR::ScriptCategory, #4 unused, #5 script file name (*.as), #6 associated file name (i.e. *.mission), #7 associated file resource group (i.e. *.mission). + angelScriptManipulationType manip = angelScriptManipulationType(arg1); + int nid = arg2ex; + + if (manip == ASMANIP_SCRIPT_LOADED && nid == thisScript) + { + bool result = g_missions.loadMission(arg6ex, arg7ex); + //game.log("DBG mission_default.as: finished loading mission file '"+arg6ex+"' (resource group '"+arg7ex+"'), result: " + result); + } + else if (manip == ASMANIP_SCRIPT_UNLOADING && nid == thisScript) + { + g_missions.unloadMission(); + //game.log("DBG mission_default.as: finished unloading mission"); + } + } } -/* A mandatory callback for cleanup after a mission */ -void unloadMission() -{ - game.log("unloading mission"); - g_missions.unloadMission(); - game.log("finished unloading mission"); -} diff --git a/source/main/GameContext.cpp b/source/main/GameContext.cpp index ed67e733e4..3f576cc9df 100644 --- a/source/main/GameContext.cpp +++ b/source/main/GameContext.cpp @@ -173,16 +173,29 @@ bool GameContext::LoadMission(CacheEntry* mission_entry) // Load script std::string scriptname = (mission_entry->mission_script != "") ? mission_entry->mission_script : DEFAULT_MISSION_SCRIPT; - ScriptUnitId_t scriptID = App::GetScriptEngine()->loadScript(scriptname, ScriptCategory::MISSION, /*associatedActor:*/nullptr, "", mission_entry); - if (scriptID == SCRIPTUNITID_INVALID) + ScriptUnitId_t nid = App::GetScriptEngine()->loadScript(scriptname, ScriptCategory::MISSION, /*associatedActor:*/nullptr, "", mission_entry); + if (nid == SCRIPTUNITID_INVALID) { App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR, fmt::format(_L("Could not load mission script '{}'"), scriptname)); return false; } - - // Execute the setup routine - return App::GetScriptEngine()->invokeLoadMission(scriptID, mission_entry->fname, mission_entry->resource_group); + + // Broadcast the SCRIPT_LOADED event with the *.mission file details; + // the handler script will intercept the event and process the file. + TRIGGER_EVENT_ASYNC(SE_ANGELSCRIPT_MANIPULATIONS, ASMANIP_SCRIPT_LOADED, + nid, (int)ScriptCategory::MISSION, 0, scriptname, mission_entry->fname, mission_entry->resource_group); + return true; +} + +void GameContext::UnloadMission(ScriptUnitId_t nid) +{ + ScriptUnit& unit = App::GetScriptEngine()->getScriptUnit(nid); + // we want to notify any running scripts that we might change something (prevent cheating) + App::GetScriptEngine()->triggerEvent(SE_ANGELSCRIPT_MANIPULATIONS, + ASMANIP_SCRIPT_UNLOADING, nid, (int)unit.scriptCategory, 0, unit.scriptName); + App::GetCacheSystem()->UnLoadResource(*unit.missionEntry); + App::GetScriptEngine()->unloadScript(nid); } // -------------------------------- diff --git a/source/main/GameContext.h b/source/main/GameContext.h index c0a34c9f1f..2036e65770 100644 --- a/source/main/GameContext.h +++ b/source/main/GameContext.h @@ -113,6 +113,7 @@ class GameContext void UnloadTerrain(); const TerrainPtr& GetTerrain() { return m_terrain; } bool LoadMission(CacheEntry* entry); + void UnloadMission(ScriptUnitId_t nid); /// @} /// @name Actors @@ -164,7 +165,7 @@ class GameContext /// @{ RaceSystem& GetRaceSystem() { return m_race_system; } - RepairMode& GetRepairMode() { return m_recovery_mode; } + RepairMode& GetRepairMode() { return m_recovery_mode; } SceneMouse& GetSceneMouse() { return m_scene_mouse; } void TeleportPlayer(float x, float z); void UpdateGlobalInputEvents(); diff --git a/source/main/gameplay/ScriptEvents.h b/source/main/gameplay/ScriptEvents.h index af3fde7d18..037e7ceb96 100644 --- a/source/main/gameplay/ScriptEvents.h +++ b/source/main/gameplay/ScriptEvents.h @@ -51,7 +51,7 @@ enum scriptEvents SE_TRUCK_TELEPORT = BITMASK(16), //!< triggered when the user teleports the truck, the argument refers to the actor ID of the vehicle SE_TRUCK_MOUSE_GRAB = BITMASK(17), //!< triggered when the user uses the mouse to interact with the actor, the argument refers to the actor ID - SE_ANGELSCRIPT_MANIPULATIONS = BITMASK(18), //!< triggered when the user tries to dynamically use the scripting capabilities (prevent cheating) args: #1 angelScriptManipulationType, #2 ScriptUnitId_t, #3 RoR::ScriptCategory, #4 unused, #5 filename + SE_ANGELSCRIPT_MANIPULATIONS = BITMASK(18), //!< triggered when the user tries to dynamically use the scripting capabilities (prevent cheating) args: #1 angelScriptManipulationType, #2 ScriptUnitId_t, #3 RoR::ScriptCategory, #4 unused, #5 script file name (*.as), #6 associated file name (i.e. *.mission), #7 associated file resource group (i.e. *.mission). SE_ANGELSCRIPT_MSGCALLBACK = BITMASK(19), //!< The diagnostic info directly from AngelScript engine (see `asSMessageInfo`), args: #1 ScriptUnitID, #2 asEMsgType, #3 row, #4 col, #5 sectionName, #6 message SE_ANGELSCRIPT_LINECALLBACK = BITMASK(20), //!< The diagnostic info directly from AngelScript engine (see `SetLineCallback()`), args: #1 ScriptUnitID, #2 LineNumber, #3 CallstackSize, #4 unused, #5 FunctionName, #6 FunctionObjectTypeName #7 ObjectName SE_ANGELSCRIPT_EXCEPTIONCALLBACK = BITMASK(21), //!< The diagnostic info directly from AngelScript engine (see `SetExceptionCallback()`), args: #1 ScriptUnitID, #2 unused, #3 row (`GetExceptionLineNumber()`), #4 unused, #5 funcName, #6 message (`GetExceptionString()`) @@ -65,11 +65,12 @@ enum scriptEvents }; +/// Argument #2 of script event `SE_ANGELSCRIPT_MANIPULATIONS` enum angelScriptManipulationType { - MANIP_CONSOLE_SNIPPET_EXECUTED = 0, // Backwards compat - MANIP_SCRIPT_LOADED, - MANIP_SCRIPT_UNLOADED + ASMANIP_CONSOLE_SNIPPET_EXECUTED = 0, // 0 for Backwards compatibility. + ASMANIP_SCRIPT_LOADED, //!< Triggered after the script's `main()` completed; may trigger additional processing (for example, it delivers the *.mission file to mission system script). + ASMANIP_SCRIPT_UNLOADING //!< Triggered before unloading the script to let it clean up (important for missions). }; enum angelScriptThreadStatus diff --git a/source/main/main.cpp b/source/main/main.cpp index 3f5927afc8..fb45fe6b63 100644 --- a/source/main/main.cpp +++ b/source/main/main.cpp @@ -405,7 +405,7 @@ int main(int argc, char *argv[]) ScriptUnitId_t nid = App::GetScriptEngine()->loadScript(request->lsr_filename, request->lsr_category, actor, request->lsr_buffer); // we want to notify any running scripts that we might change something (prevent cheating) App::GetScriptEngine()->triggerEvent(SE_ANGELSCRIPT_MANIPULATIONS, - MANIP_SCRIPT_LOADED, nid, (int)request->lsr_category, 0, request->lsr_filename); + ASMANIP_SCRIPT_LOADED, nid, (int)request->lsr_category, 0, request->lsr_filename); delete request; break; } @@ -416,7 +416,7 @@ int main(int argc, char *argv[]) ScriptUnit& unit = App::GetScriptEngine()->getScriptUnit(*id); // we want to notify any running scripts that we might change something (prevent cheating) App::GetScriptEngine()->triggerEvent(SE_ANGELSCRIPT_MANIPULATIONS, - MANIP_SCRIPT_UNLOADED, *id, (int)unit.scriptCategory, 0, unit.scriptName); + ASMANIP_SCRIPT_UNLOADING, *id, (int)unit.scriptCategory, 0, unit.scriptName); App::GetScriptEngine()->unloadScript(*id); delete id; break; @@ -828,10 +828,9 @@ int main(int argc, char *argv[]) case MSG_SIM_UNLOAD_MISSION_REQUESTED: { - ScriptUnitId_t* id = (ScriptUnitId_t*)m.payload; - App::GetScriptEngine()->invokeUnloadMission(*id); - App::GetScriptEngine()->unloadScript(*id); - delete id; + ScriptUnitId_t* nid = (ScriptUnitId_t*)m.payload; + App::GetGameContext()->UnloadMission(*nid); + delete nid; break; } diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index 547fc62cff..4a4a1d17a6 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -1175,6 +1175,12 @@ void CacheSystem::LoadResource(CacheEntry& t) ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/true); ResourceGroupManager::getSingleton().addResourceLocation(t.resource_bundle_path, t.resource_bundle_type, group); } + else if (t.fext == "mission") + { + // This is a MissionZip bundle - use `inGlobalPool=false` to prevent resource name conflicts. + ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/false); + ResourceGroupManager::getSingleton().addResourceLocation(t.resource_bundle_path, t.resource_bundle_type, group); + } else if (t.fext == "skin") { // This is a SkinZip bundle - use `inGlobalPool=false` to prevent resource name conflicts. diff --git a/source/main/scripting/ScriptEngine.cpp b/source/main/scripting/ScriptEngine.cpp index 934d90cf4f..230d99f920 100644 --- a/source/main/scripting/ScriptEngine.cpp +++ b/source/main/scripting/ScriptEngine.cpp @@ -903,9 +903,6 @@ int ScriptEngine::setupScriptUnit(int unit_id) m_script_units[unit_id].defaultEventCallbackFunctionPtr = this->getFunctionByDeclAndLogCandidates( unit_id, GETFUNCFLAG_OPTIONAL, GETFUNC_DEFAULTEVENTCALLBACK_NAME, GETFUNC_DEFAULTEVENTCALLBACK_SIGFMT); - m_script_units[unit_id].loadMissionFunctionPtr = m_script_units[unit_id].scriptModule->GetFunctionByDecl("bool loadMission(string, string)"); - m_script_units[unit_id].unloadMissionFunctionPtr = m_script_units[unit_id].scriptModule->GetFunctionByDecl("void unloadMission()"); - // Find the function that is to be called. auto main_func = m_script_units[unit_id].scriptModule->GetFunctionByDecl("void main()"); if ( main_func == nullptr ) @@ -1030,41 +1027,6 @@ ScriptUnit& ScriptEngine::getScriptUnit(ScriptUnitId_t unique_id) return m_script_units[unique_id]; } -bool ScriptEngine::invokeLoadMission(ScriptUnitId_t id, const std::string& filename, const std::string& resource_group) -{ - context->Prepare(this->getScriptUnit(id).loadMissionFunctionPtr); - context->SetArgObject(0, (void*)&filename); - context->SetArgObject(1, (void*)&resource_group); - m_currently_executing_script_unit = id; - int r = context->Execute(); - m_currently_executing_script_unit = SCRIPTUNITID_INVALID; - - if (r == AngelScript::asEXECUTION_FINISHED) - { - // The return value is only valid if the execution finished successfully - return static_cast(context->GetReturnDWord()); - } - else - { - LOG(fmt::format("WARNING: Invoking `loadMission(filename='{}', resource_group='{}')` in '{}' ended with error code {}", - filename, resource_group, this->getScriptUnit(id).scriptName, r)); - return false; - } -} - -void ScriptEngine::invokeUnloadMission(ScriptUnitId_t id) -{ - context->Prepare(this->getScriptUnit(id).unloadMissionFunctionPtr); - m_currently_executing_script_unit = id; - int r = context->Execute(); - m_currently_executing_script_unit = SCRIPTUNITID_INVALID; - - if (r != AngelScript::asEXECUTION_FINISHED) - { - LOG(fmt::format("WARNING: Invoking `unloadMission()` in '{}' ended with error code {}", this->getScriptUnit(id).scriptName, r)); - } -} - int ScriptEngine::getNumLoadedMissions() { int count = 0; diff --git a/source/main/scripting/ScriptEngine.h b/source/main/scripting/ScriptEngine.h index 07345a1fc3..99b4301e8e 100644 --- a/source/main/scripting/ScriptEngine.h +++ b/source/main/scripting/ScriptEngine.h @@ -79,13 +79,11 @@ struct ScriptUnit AngelScript::asIScriptFunction* eventCallbackFunctionPtr = nullptr; //!< script function pointer to the event callback function AngelScript::asIScriptFunction* eventCallbackExFunctionPtr = nullptr; //!< script function pointer to the event callback function AngelScript::asIScriptFunction* defaultEventCallbackFunctionPtr = nullptr; //!< script function pointer for spawner events - AngelScript::asIScriptFunction* loadMissionFunctionPtr = nullptr; //!< only `ScriptCategory::MISSION`, called to set up the mission. - AngelScript::asIScriptFunction* unloadMissionFunctionPtr = nullptr; //!< only `ScriptCategory::MISSION`, called to clean up the mission. ActorPtr associatedActor; //!< For ScriptCategory::ACTOR Ogre::String scriptName; Ogre::String scriptHash; Ogre::String scriptBuffer; - CacheEntry* missionEntry = nullptr; + CacheEntry* missionEntry = nullptr; //!< For ScriptCategory::MISSION }; typedef std::map ScriptUnitMap; @@ -226,8 +224,6 @@ class ScriptEngine : public Ogre::LogListener AngelScript::asIScriptEngine* getEngine() { return engine; }; - bool invokeLoadMission(ScriptUnitId_t id, const std::string& filename, const std::string& resource_group); - void invokeUnloadMission(ScriptUnitId_t id); int getNumLoadedMissions(); // method from Ogre::LogListener diff --git a/source/main/scripting/bindings/ScriptEventsAngelscript.cpp b/source/main/scripting/bindings/ScriptEventsAngelscript.cpp index 37f9c09894..050be9003c 100644 --- a/source/main/scripting/bindings/ScriptEventsAngelscript.cpp +++ b/source/main/scripting/bindings/ScriptEventsAngelscript.cpp @@ -69,9 +69,9 @@ void RoR::RegisterScriptEvents(asIScriptEngine *engine) // enum angelScriptManipulationType result = engine->RegisterEnum("angelScriptManipulationType"); ROR_ASSERT(result>=0); - result = engine->RegisterEnumValue("angelScriptManipulationType", "ASMANIP_CONSOLE_SNIPPET_EXECUTED", MANIP_CONSOLE_SNIPPET_EXECUTED); ROR_ASSERT(result >= 0); - result = engine->RegisterEnumValue("angelScriptManipulationType", "ASMANIP_SCRIPT_LOADED", MANIP_SCRIPT_LOADED); ROR_ASSERT(result >= 0); - result = engine->RegisterEnumValue("angelScriptManipulationType", "ASMANIP_SCRIPT_UNLOADED", MANIP_SCRIPT_UNLOADED); ROR_ASSERT(result >= 0); + result = engine->RegisterEnumValue("angelScriptManipulationType", "ASMANIP_CONSOLE_SNIPPET_EXECUTED", ASMANIP_CONSOLE_SNIPPET_EXECUTED); ROR_ASSERT(result >= 0); + result = engine->RegisterEnumValue("angelScriptManipulationType", "ASMANIP_SCRIPT_LOADED", ASMANIP_SCRIPT_LOADED); ROR_ASSERT(result >= 0); + result = engine->RegisterEnumValue("angelScriptManipulationType", "ASMANIP_SCRIPT_UNLOADING", ASMANIP_SCRIPT_UNLOADING); ROR_ASSERT(result >= 0); // enum angelScriptThreadStatus result = engine->RegisterEnum("angelScriptThreadStatus"); ROR_ASSERT(result>=0); diff --git a/source/main/system/ConsoleCmd.cpp b/source/main/system/ConsoleCmd.cpp index bf178da127..e573a2a554 100644 --- a/source/main/system/ConsoleCmd.cpp +++ b/source/main/system/ConsoleCmd.cpp @@ -331,7 +331,7 @@ class AsCmd: public ConsoleCmd #ifdef USE_ANGELSCRIPT // we want to notify any running scripts that we might change something (prevent cheating) - App::GetScriptEngine()->triggerEvent(SE_ANGELSCRIPT_MANIPULATIONS, MANIP_CONSOLE_SNIPPET_EXECUTED); + App::GetScriptEngine()->triggerEvent(SE_ANGELSCRIPT_MANIPULATIONS, ASMANIP_CONSOLE_SNIPPET_EXECUTED); // Re-compose the code snippet Str<1000> code; diff --git a/source/main/terrain/Terrain.cpp b/source/main/terrain/Terrain.cpp index 5143158f6a..9a223f9edf 100644 --- a/source/main/terrain/Terrain.cpp +++ b/source/main/terrain/Terrain.cpp @@ -146,10 +146,9 @@ void RoR::Terrain::dispose() mission_script_IDs.push_back(pair.second.uniqueId); } } - for (ScriptUnitId_t id : mission_script_IDs) + for (ScriptUnitId_t nid : mission_script_IDs) { - App::GetScriptEngine()->invokeUnloadMission(id); - App::GetScriptEngine()->unloadScript(id); + App::GetGameContext()->UnloadMission(nid); } m_disposed = true;