LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpAIManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 5 535 0.9 %
Date: 2023-01-19 00:18:29 Functions: 5 61 8.2 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "simulation2/system/Component.h"
      21             : #include "ICmpAIManager.h"
      22             : 
      23             : #include "simulation2/MessageTypes.h"
      24             : 
      25             : #include "graphics/Terrain.h"
      26             : #include "lib/timer.h"
      27             : #include "lib/tex/tex.h"
      28             : #include "lib/allocators/shared_ptr.h"
      29             : #include "ps/CLogger.h"
      30             : #include "ps/Filesystem.h"
      31             : #include "ps/Profile.h"
      32             : #include "ps/scripting/JSInterface_VFS.h"
      33             : #include "ps/TemplateLoader.h"
      34             : #include "ps/Util.h"
      35             : #include "scriptinterface/FunctionWrapper.h"
      36             : #include "scriptinterface/ScriptContext.h"
      37             : #include "scriptinterface/StructuredClone.h"
      38             : #include "scriptinterface/JSON.h"
      39             : #include "simulation2/components/ICmpAIInterface.h"
      40             : #include "simulation2/components/ICmpCommandQueue.h"
      41             : #include "simulation2/components/ICmpObstructionManager.h"
      42             : #include "simulation2/components/ICmpRangeManager.h"
      43             : #include "simulation2/components/ICmpTemplateManager.h"
      44             : #include "simulation2/components/ICmpTerritoryManager.h"
      45             : #include "simulation2/helpers/HierarchicalPathfinder.h"
      46             : #include "simulation2/helpers/LongPathfinder.h"
      47             : #include "simulation2/serialization/DebugSerializer.h"
      48             : #include "simulation2/serialization/SerializedTypes.h"
      49             : #include "simulation2/serialization/StdDeserializer.h"
      50             : #include "simulation2/serialization/StdSerializer.h"
      51             : 
      52             : extern void QuitEngine();
      53             : 
      54             : /**
      55             :  * @file
      56             :  * Player AI interface.
      57             :  * AI is primarily scripted, and the CCmpAIManager component defined here
      58             :  * takes care of managing all the scripts.
      59             :  *
      60             :  * The original idea was to run CAIWorker in a separate thread to prevent
      61             :  * slow AIs from impacting framerate. However, copying the game-state every turn
      62             :  * proved difficult and rather slow itself (and isn't threadable, obviously).
      63             :  * For these reasons, the design was changed to a single-thread, same-compartment, different-realm design.
      64             :  * The AI can therefore directly use the simulation data via the 'Sim' & 'SimEngine' globals.
      65             :  * As a result, a lof of the code is still designed to be "thread-ready", but this no longer matters.
      66             :  *
      67             :  * TODO: despite the above, it would still be useful to allow the AI to run tasks asynchronously (and off-thread).
      68             :  * This could be implemented by having a separate JS runtime in a different thread,
      69             :  * that runs tasks and returns after a distinct # of simulation turns (to maintain determinism).
      70             :  *
      71             :  * Note also that the RL Interface, by default, uses the 'AI representation'.
      72             :  * This representation, alimented by the JS AIInterface/AIProxy tandem, is likely to grow smaller over time
      73             :  * as the AI uses more sim data directly.
      74             :  */
      75             : 
      76             : /**
      77             :  * AI computation orchestator for CCmpAIManager.
      78             :  */
      79             : class CAIWorker
      80             : {
      81             : private:
      82           0 :     class CAIPlayer
      83             :     {
      84             :         NONCOPYABLE(CAIPlayer);
      85             :     public:
      86           0 :         CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior,
      87           0 :             std::shared_ptr<ScriptInterface> scriptInterface) :
      88             :             m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty), m_Behavior(behavior),
      89           0 :             m_ScriptInterface(scriptInterface), m_Obj(scriptInterface->GetGeneralJSContext())
      90             :         {
      91           0 :         }
      92             : 
      93           0 :         bool Initialise()
      94             :         {
      95             :             // LoadScripts will only load each script once even though we call it for each player
      96           0 :             if (!m_Worker.LoadScripts(m_AIName))
      97           0 :                 return false;
      98             : 
      99           0 :             ScriptRequest rq(m_ScriptInterface);
     100             : 
     101           0 :             OsPath path = L"simulation/ai/" + m_AIName + L"/data.json";
     102           0 :             JS::RootedValue metadata(rq.cx);
     103           0 :             m_Worker.LoadMetadata(path, &metadata);
     104           0 :             if (metadata.isUndefined())
     105             :             {
     106           0 :                 LOGERROR("Failed to create AI player: can't find %s", path.string8());
     107           0 :                 return false;
     108             :             }
     109             : 
     110             :             // Get the constructor name from the metadata
     111           0 :             std::string moduleName;
     112           0 :             std::string constructor;
     113           0 :             JS::RootedValue objectWithConstructor(rq.cx); // object that should contain the constructor function
     114           0 :             JS::RootedValue global(rq.cx, rq.globalValue());
     115           0 :             JS::RootedValue ctor(rq.cx);
     116           0 :             if (!Script::HasProperty(rq, metadata, "moduleName"))
     117             :             {
     118           0 :                 LOGERROR("Failed to create AI player: %s: missing 'moduleName'", path.string8());
     119           0 :                 return false;
     120             :             }
     121             : 
     122           0 :             Script::GetProperty(rq, metadata, "moduleName", moduleName);
     123           0 :             if (!Script::GetProperty(rq, global, moduleName.c_str(), &objectWithConstructor)
     124           0 :                 || objectWithConstructor.isUndefined())
     125             :             {
     126           0 :                 LOGERROR("Failed to create AI player: %s: can't find the module that should contain the constructor: '%s'", path.string8(), moduleName);
     127           0 :                 return false;
     128             :             }
     129             : 
     130           0 :             if (!Script::GetProperty(rq, metadata, "constructor", constructor))
     131             :             {
     132           0 :                 LOGERROR("Failed to create AI player: %s: missing 'constructor'", path.string8());
     133           0 :                 return false;
     134             :             }
     135             : 
     136             :             // Get the constructor function from the loaded scripts
     137           0 :             if (!Script::GetProperty(rq, objectWithConstructor, constructor.c_str(), &ctor)
     138           0 :                 || ctor.isNull())
     139             :             {
     140           0 :                 LOGERROR("Failed to create AI player: %s: can't find constructor '%s'", path.string8(), constructor);
     141           0 :                 return false;
     142             :             }
     143             : 
     144           0 :             Script::GetProperty(rq, metadata, "useShared", m_UseSharedComponent);
     145             : 
     146             :             // Set up the data to pass as the constructor argument
     147           0 :             JS::RootedValue settings(rq.cx);
     148           0 :             Script::CreateObject(
     149             :                 rq,
     150             :                 &settings,
     151             :                 "player", m_Player,
     152             :                 "difficulty", m_Difficulty,
     153             :                 "behavior", m_Behavior);
     154             : 
     155           0 :             if (!m_UseSharedComponent)
     156             :             {
     157           0 :                 ENSURE(m_Worker.m_HasLoadedEntityTemplates);
     158           0 :                 Script::SetProperty(rq, settings, "templates", m_Worker.m_EntityTemplates, false);
     159             :             }
     160             : 
     161           0 :             JS::RootedValueVector argv(rq.cx);
     162           0 :             ignore_result(argv.append(settings.get()));
     163           0 :             m_ScriptInterface->CallConstructor(ctor, argv, &m_Obj);
     164             : 
     165           0 :             if (m_Obj.get().isNull())
     166             :             {
     167           0 :                 LOGERROR("Failed to create AI player: %s: error calling constructor '%s'", path.string8(), constructor);
     168           0 :                 return false;
     169             :             }
     170           0 :             return true;
     171             :         }
     172             : 
     173           0 :         void Run(JS::HandleValue state, int playerID)
     174             :         {
     175           0 :             m_Commands.clear();
     176           0 :             ScriptRequest rq(m_ScriptInterface);
     177           0 :             ScriptFunction::CallVoid(rq, m_Obj, "HandleMessage", state, playerID);
     178           0 :         }
     179             :         // overloaded with a sharedAI part.
     180             :         // javascript can handle both natively on the same function.
     181           0 :         void Run(JS::HandleValue state, int playerID, JS::HandleValue SharedAI)
     182             :         {
     183           0 :             m_Commands.clear();
     184           0 :             ScriptRequest rq(m_ScriptInterface);
     185           0 :             ScriptFunction::CallVoid(rq, m_Obj, "HandleMessage", state, playerID, SharedAI);
     186           0 :         }
     187           0 :         void InitAI(JS::HandleValue state, JS::HandleValue SharedAI)
     188             :         {
     189           0 :             m_Commands.clear();
     190           0 :             ScriptRequest rq(m_ScriptInterface);
     191           0 :             ScriptFunction::CallVoid(rq, m_Obj, "Init", state, m_Player, SharedAI);
     192           0 :         }
     193             : 
     194             :         CAIWorker& m_Worker;
     195             :         std::wstring m_AIName;
     196             :         player_id_t m_Player;
     197             :         u8 m_Difficulty;
     198             :         std::wstring m_Behavior;
     199             :         bool m_UseSharedComponent;
     200             : 
     201             :         // Take care to keep this declaration before heap rooted members. Destructors of heap rooted
     202             :         // members have to be called before the context destructor.
     203             :         std::shared_ptr<ScriptInterface> m_ScriptInterface;
     204             : 
     205             :         JS::PersistentRootedValue m_Obj;
     206             :         std::vector<Script::StructuredClone> m_Commands;
     207             :     };
     208             : 
     209             : public:
     210           0 :     struct SCommandSets
     211             :     {
     212             :         player_id_t player;
     213             :         std::vector<Script::StructuredClone> commands;
     214             :     };
     215             : 
     216           0 :     CAIWorker() :
     217             :         m_TurnNum(0),
     218             :         m_CommandsComputed(true),
     219             :         m_HasLoadedEntityTemplates(false),
     220           0 :         m_HasSharedComponent(false)
     221             :     {
     222           0 :     }
     223             : 
     224           0 :     ~CAIWorker()
     225           0 :     {
     226             :         // Init will always be called.
     227           0 :         JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this);
     228           0 :     }
     229             : 
     230           0 :     void Init(const ScriptInterface& simInterface)
     231             :     {
     232             :         // Create the script interface in the same compartment as the simulation interface.
     233             :         // This will allow us to directly share data from the sim to the AI (and vice versa, should the need arise).
     234           0 :         m_ScriptInterface = std::make_shared<ScriptInterface>("Engine", "AI", simInterface);
     235             : 
     236           0 :         ScriptRequest rq(m_ScriptInterface);
     237             : 
     238           0 :         m_EntityTemplates.init(rq.cx);
     239           0 :         m_SharedAIObj.init(rq.cx);
     240           0 :         m_PassabilityMapVal.init(rq.cx);
     241           0 :         m_TerritoryMapVal.init(rq.cx);
     242             : 
     243             : 
     244           0 :         m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG);
     245             : 
     246           0 :         m_ScriptInterface->SetCallbackData(static_cast<void*> (this));
     247             : 
     248           0 :         JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this);
     249             : 
     250             :         {
     251           0 :             ScriptRequest simrq(simInterface);
     252             :             // Register the sim globals for easy & explicit access. Mark it replaceable for hotloading.
     253           0 :             JS::RootedValue global(rq.cx, simrq.globalValue());
     254           0 :             m_ScriptInterface->SetGlobal("Sim", global, true);
     255           0 :             JS::RootedValue scope(rq.cx, JS::ObjectValue(*simrq.nativeScope.get()));
     256           0 :             m_ScriptInterface->SetGlobal("SimEngine", scope, true);
     257             :         }
     258             : 
     259             : #define REGISTER_FUNC_NAME(func, name) \
     260             :     ScriptFunction::Register<&CAIWorker::func, ScriptInterface::ObjectFromCBData<CAIWorker>>(rq, name);
     261             : 
     262           0 :         REGISTER_FUNC_NAME(PostCommand, "PostCommand");
     263           0 :         REGISTER_FUNC_NAME(LoadScripts, "IncludeModule");
     264           0 :         ScriptFunction::Register<QuitEngine>(rq, "Exit");
     265             : 
     266           0 :         REGISTER_FUNC_NAME(ComputePathScript, "ComputePath");
     267             : 
     268           0 :         REGISTER_FUNC_NAME(DumpImage, "DumpImage");
     269           0 :         REGISTER_FUNC_NAME(GetTemplate, "GetTemplate");
     270             : 
     271             : #undef REGISTER_FUNC_NAME
     272             : 
     273           0 :         JSI_VFS::RegisterScriptFunctions_ReadOnlySimulation(rq);
     274             : 
     275             :         // Globalscripts may use VFS script functions
     276           0 :         m_ScriptInterface->LoadGlobalScripts();
     277             : 
     278           0 :     }
     279             : 
     280           0 :     bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; }
     281             : 
     282           0 :     bool LoadScripts(const std::wstring& moduleName)
     283             :     {
     284             :         // Ignore modules that are already loaded
     285           0 :         if (m_LoadedModules.find(moduleName) != m_LoadedModules.end())
     286           0 :             return true;
     287             : 
     288             :         // Mark this as loaded, to prevent it recursively loading itself
     289           0 :         m_LoadedModules.insert(moduleName);
     290             : 
     291             :         // Load and execute *.js
     292           0 :         VfsPaths pathnames;
     293           0 :         if (vfs::GetPathnames(g_VFS, L"simulation/ai/" + moduleName + L"/", L"*.js", pathnames) < 0)
     294             :         {
     295           0 :             LOGERROR("Failed to load AI scripts for module %s", utf8_from_wstring(moduleName));
     296           0 :             return false;
     297             :         }
     298             : 
     299           0 :         for (const VfsPath& path : pathnames)
     300             :         {
     301           0 :             if (!m_ScriptInterface->LoadGlobalScriptFile(path))
     302             :             {
     303           0 :                 LOGERROR("Failed to load script %s", path.string8());
     304           0 :                 return false;
     305             :             }
     306             :         }
     307             : 
     308           0 :         return true;
     309             :     }
     310             : 
     311           0 :     void PostCommand(int playerid, JS::HandleValue cmd)
     312             :     {
     313           0 :         ScriptRequest rq(m_ScriptInterface);
     314           0 :         for (size_t i=0; i<m_Players.size(); i++)
     315             :         {
     316           0 :             if (m_Players[i]->m_Player == playerid)
     317             :             {
     318           0 :                 m_Players[i]->m_Commands.push_back(Script::WriteStructuredClone(rq, cmd));
     319           0 :                 return;
     320             :             }
     321             :         }
     322             : 
     323           0 :         LOGERROR("Invalid playerid in PostCommand!");
     324             :     }
     325             : 
     326           0 :     JS::Value ComputePathScript(JS::HandleValue position, JS::HandleValue goal, pass_class_t passClass)
     327             :     {
     328           0 :         ScriptRequest rq(m_ScriptInterface);
     329             : 
     330           0 :         CFixedVector2D pos, goalPos;
     331           0 :         std::vector<CFixedVector2D> waypoints;
     332           0 :         JS::RootedValue retVal(rq.cx);
     333             : 
     334           0 :         Script::FromJSVal(rq, position, pos);
     335           0 :         Script::FromJSVal(rq, goal, goalPos);
     336             : 
     337           0 :         ComputePath(pos, goalPos, passClass, waypoints);
     338           0 :         Script::ToJSVal(rq, &retVal, waypoints);
     339             : 
     340           0 :         return retVal;
     341             :     }
     342             : 
     343           0 :     void ComputePath(const CFixedVector2D& pos, const CFixedVector2D& goal, pass_class_t passClass, std::vector<CFixedVector2D>& waypoints)
     344             :     {
     345           0 :         WaypointPath ret;
     346           0 :         PathGoal pathGoal = { PathGoal::POINT, goal.X, goal.Y };
     347           0 :         m_LongPathfinder.ComputePath(m_HierarchicalPathfinder, pos.X, pos.Y, pathGoal, passClass, ret);
     348             : 
     349           0 :         for (Waypoint& wp : ret.m_Waypoints)
     350           0 :             waypoints.emplace_back(wp.x, wp.z);
     351           0 :     }
     352             : 
     353           0 :     CParamNode GetTemplate(const std::string& name)
     354             :     {
     355           0 :         if (!m_TemplateLoader.TemplateExists(name))
     356           0 :             return CParamNode(false);
     357           0 :         return m_TemplateLoader.GetTemplateFileData(name).GetOnlyChild();
     358             :     }
     359             : 
     360             :     /**
     361             :      * Debug function for AI scripts to dump 2D array data (e.g. terrain tile weights).
     362             :      */
     363           0 :     void DumpImage(const std::wstring& name, const std::vector<u32>& data, u32 w, u32 h, u32 max)
     364             :     {
     365             :         // TODO: this is totally not threadsafe.
     366           0 :         VfsPath filename = L"screenshots/aidump/" + name;
     367             : 
     368           0 :         if (data.size() != w*h)
     369             :         {
     370           0 :             debug_warn(L"DumpImage: data size doesn't match w*h");
     371           0 :             return;
     372             :         }
     373             : 
     374           0 :         if (max == 0)
     375             :         {
     376           0 :             debug_warn(L"DumpImage: max must not be 0");
     377           0 :             return;
     378             :         }
     379             : 
     380           0 :         const size_t bpp = 8;
     381           0 :         int flags = TEX_BOTTOM_UP|TEX_GREY;
     382             : 
     383           0 :         const size_t img_size = w * h * bpp/8;
     384           0 :         const size_t hdr_size = tex_hdr_size(filename);
     385           0 :         std::shared_ptr<u8> buf;
     386           0 :         AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
     387           0 :         Tex t;
     388           0 :         if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
     389           0 :             return;
     390             : 
     391           0 :         u8* img = buf.get() + hdr_size;
     392           0 :         for (size_t i = 0; i < data.size(); ++i)
     393           0 :             img[i] = (u8)((data[i] * 255) / max);
     394             : 
     395           0 :         tex_write(&t, filename);
     396             :     }
     397             : 
     398           0 :     void SetRNGSeed(u32 seed)
     399             :     {
     400           0 :         m_RNG.seed(seed);
     401           0 :     }
     402             : 
     403           0 :     bool TryLoadSharedComponent()
     404             :     {
     405           0 :         ScriptRequest rq(m_ScriptInterface);
     406             : 
     407             :         // we don't need to load it.
     408           0 :         if (!m_HasSharedComponent)
     409           0 :             return false;
     410             : 
     411             :         // reset the value so it can be used to determine if we actually initialized it.
     412           0 :         m_HasSharedComponent = false;
     413             : 
     414           0 :         if (LoadScripts(L"common-api"))
     415           0 :             m_HasSharedComponent = true;
     416             :         else
     417           0 :             return false;
     418             : 
     419             :         // mainly here for the error messages
     420           0 :         OsPath path = L"simulation/ai/common-api/";
     421             : 
     422             :         // Constructor name is SharedScript, it's in the module API3
     423             :         // TODO: Hardcoding this is bad, we need a smarter way.
     424           0 :         JS::RootedValue AIModule(rq.cx);
     425           0 :         JS::RootedValue global(rq.cx, rq.globalValue());
     426           0 :         JS::RootedValue ctor(rq.cx);
     427           0 :         if (!Script::GetProperty(rq, global, "API3", &AIModule) || AIModule.isUndefined())
     428             :         {
     429           0 :             LOGERROR("Failed to create shared AI component: %s: can't find module '%s'", path.string8(), "API3");
     430           0 :             return false;
     431             :         }
     432             : 
     433           0 :         if (!Script::GetProperty(rq, AIModule, "SharedScript", &ctor)
     434           0 :             || ctor.isUndefined())
     435             :         {
     436           0 :             LOGERROR("Failed to create shared AI component: %s: can't find constructor '%s'", path.string8(), "SharedScript");
     437           0 :             return false;
     438             :         }
     439             : 
     440             :         // Set up the data to pass as the constructor argument
     441           0 :         JS::RootedValue playersID(rq.cx);
     442           0 :         Script::CreateObject(rq, &playersID);
     443             : 
     444           0 :         for (size_t i = 0; i < m_Players.size(); ++i)
     445             :         {
     446           0 :             JS::RootedValue val(rq.cx);
     447           0 :             Script::ToJSVal(rq, &val, m_Players[i]->m_Player);
     448           0 :             Script::SetPropertyInt(rq, playersID, i, val, true);
     449             :         }
     450             : 
     451           0 :         ENSURE(m_HasLoadedEntityTemplates);
     452             : 
     453           0 :         JS::RootedValue settings(rq.cx);
     454           0 :         Script::CreateObject(
     455             :             rq,
     456             :             &settings,
     457             :             "players", playersID,
     458             :             "templates", m_EntityTemplates);
     459             : 
     460           0 :         JS::RootedValueVector argv(rq.cx);
     461           0 :         ignore_result(argv.append(settings));
     462           0 :         m_ScriptInterface->CallConstructor(ctor, argv, &m_SharedAIObj);
     463             : 
     464           0 :         if (m_SharedAIObj.get().isNull())
     465             :         {
     466           0 :             LOGERROR("Failed to create shared AI component: %s: error calling constructor '%s'", path.string8(), "SharedScript");
     467           0 :             return false;
     468             :         }
     469             : 
     470           0 :         return true;
     471             :     }
     472             : 
     473           0 :     bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior)
     474             :     {
     475           0 :         std::shared_ptr<CAIPlayer> ai = std::make_shared<CAIPlayer>(*this, aiName, player, difficulty, behavior, m_ScriptInterface);
     476           0 :         if (!ai->Initialise())
     477           0 :             return false;
     478             : 
     479             :         // this will be set to true if we need to load the shared Component.
     480           0 :         if (!m_HasSharedComponent)
     481           0 :             m_HasSharedComponent = ai->m_UseSharedComponent;
     482             : 
     483           0 :         m_Players.push_back(ai);
     484             : 
     485           0 :         return true;
     486             :     }
     487             : 
     488           0 :     bool RunGamestateInit(const Script::StructuredClone& gameState, const Grid<NavcellData>& passabilityMap, const Grid<u8>& territoryMap,
     489             :         const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks)
     490             :     {
     491             :         // this will be run last by InitGame.js, passing the full game representation.
     492             :         // For now it will run for the shared Component.
     493             :         // This is NOT run during deserialization.
     494           0 :         ScriptRequest rq(m_ScriptInterface);
     495             : 
     496           0 :         JS::RootedValue state(rq.cx);
     497           0 :         Script::ReadStructuredClone(rq, gameState, &state);
     498           0 :         Script::ToJSVal(rq, &m_PassabilityMapVal, passabilityMap);
     499           0 :         Script::ToJSVal(rq, &m_TerritoryMapVal, territoryMap);
     500             : 
     501           0 :         m_PassabilityMap = passabilityMap;
     502           0 :         m_NonPathfindingPassClasses = nonPathfindingPassClassMasks;
     503           0 :         m_PathfindingPassClasses = pathfindingPassClassMasks;
     504             : 
     505           0 :         m_LongPathfinder.Reload(&m_PassabilityMap);
     506           0 :         m_HierarchicalPathfinder.Recompute(&m_PassabilityMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks);
     507             : 
     508           0 :         if (m_HasSharedComponent)
     509             :         {
     510           0 :             Script::SetProperty(rq, state, "passabilityMap", m_PassabilityMapVal, true);
     511           0 :             Script::SetProperty(rq, state, "territoryMap", m_TerritoryMapVal, true);
     512           0 :             ScriptFunction::CallVoid(rq, m_SharedAIObj, "init", state);
     513             : 
     514           0 :             for (size_t i = 0; i < m_Players.size(); ++i)
     515             :             {
     516           0 :                 if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent)
     517           0 :                     m_Players[i]->InitAI(state, m_SharedAIObj);
     518             :             }
     519             :         }
     520             : 
     521           0 :         return true;
     522             :     }
     523             : 
     524           0 :     void UpdateGameState(const Script::StructuredClone& gameState)
     525             :     {
     526           0 :         ENSURE(m_CommandsComputed);
     527           0 :         m_GameState = gameState;
     528           0 :     }
     529             : 
     530           0 :     void UpdatePathfinder(const Grid<NavcellData>& passabilityMap, bool globallyDirty, const Grid<u8>& dirtinessGrid, bool justDeserialized,
     531             :         const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks)
     532             :     {
     533           0 :         ENSURE(m_CommandsComputed);
     534           0 :         bool dimensionChange = m_PassabilityMap.m_W != passabilityMap.m_W || m_PassabilityMap.m_H != passabilityMap.m_H;
     535             : 
     536           0 :         m_PassabilityMap = passabilityMap;
     537           0 :         if (globallyDirty)
     538             :         {
     539           0 :             m_LongPathfinder.Reload(&m_PassabilityMap);
     540           0 :             m_HierarchicalPathfinder.Recompute(&m_PassabilityMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks);
     541             :         }
     542             :         else
     543             :         {
     544           0 :             m_LongPathfinder.Update(&m_PassabilityMap);
     545           0 :             m_HierarchicalPathfinder.Update(&m_PassabilityMap, dirtinessGrid);
     546             :         }
     547             : 
     548           0 :         ScriptRequest rq(m_ScriptInterface);
     549           0 :         if (dimensionChange || justDeserialized)
     550           0 :             Script::ToJSVal(rq, &m_PassabilityMapVal, m_PassabilityMap);
     551             :         else
     552             :         {
     553             :             // Avoid a useless memory reallocation followed by a garbage collection.
     554           0 :             JS::RootedObject mapObj(rq.cx, &m_PassabilityMapVal.toObject());
     555           0 :             JS::RootedValue mapData(rq.cx);
     556           0 :             ENSURE(JS_GetProperty(rq.cx, mapObj, "data", &mapData));
     557           0 :             JS::RootedObject dataObj(rq.cx, &mapData.toObject());
     558             : 
     559           0 :             u32 length = 0;
     560           0 :             ENSURE(JS::GetArrayLength(rq.cx, dataObj, &length));
     561           0 :             u32 nbytes = (u32)(length * sizeof(NavcellData));
     562             : 
     563             :             bool sharedMemory;
     564           0 :             JS::AutoCheckCannotGC nogc;
     565           0 :             memcpy((void*)JS_GetUint16ArrayData(dataObj, &sharedMemory, nogc), m_PassabilityMap.m_Data, nbytes);
     566             :         }
     567           0 :     }
     568             : 
     569           0 :     void UpdateTerritoryMap(const Grid<u8>& territoryMap)
     570             :     {
     571           0 :         ENSURE(m_CommandsComputed);
     572           0 :         bool dimensionChange = m_TerritoryMap.m_W != territoryMap.m_W || m_TerritoryMap.m_H != territoryMap.m_H;
     573             : 
     574           0 :         m_TerritoryMap = territoryMap;
     575             : 
     576           0 :         ScriptRequest rq(m_ScriptInterface);
     577           0 :         if (dimensionChange)
     578           0 :             Script::ToJSVal(rq, &m_TerritoryMapVal, m_TerritoryMap);
     579             :         else
     580             :         {
     581             :             // Avoid a useless memory reallocation followed by a garbage collection.
     582           0 :             JS::RootedObject mapObj(rq.cx, &m_TerritoryMapVal.toObject());
     583           0 :             JS::RootedValue mapData(rq.cx);
     584           0 :             ENSURE(JS_GetProperty(rq.cx, mapObj, "data", &mapData));
     585           0 :             JS::RootedObject dataObj(rq.cx, &mapData.toObject());
     586             : 
     587           0 :             u32 length = 0;
     588           0 :             ENSURE(JS::GetArrayLength(rq.cx, dataObj, &length));
     589           0 :             u32 nbytes = (u32)(length * sizeof(u8));
     590             : 
     591             :             bool sharedMemory;
     592           0 :             JS::AutoCheckCannotGC nogc;
     593           0 :             memcpy((void*)JS_GetUint8ArrayData(dataObj, &sharedMemory, nogc), m_TerritoryMap.m_Data, nbytes);
     594             :         }
     595           0 :     }
     596             : 
     597           0 :     void StartComputation()
     598             :     {
     599           0 :         m_CommandsComputed = false;
     600           0 :     }
     601             : 
     602           0 :     void WaitToFinishComputation()
     603             :     {
     604           0 :         if (!m_CommandsComputed)
     605             :         {
     606           0 :             PerformComputation();
     607           0 :             m_CommandsComputed = true;
     608             :         }
     609           0 :     }
     610             : 
     611           0 :     void GetCommands(std::vector<SCommandSets>& commands)
     612             :     {
     613           0 :         WaitToFinishComputation();
     614             : 
     615           0 :         commands.clear();
     616           0 :         commands.resize(m_Players.size());
     617           0 :         for (size_t i = 0; i < m_Players.size(); ++i)
     618             :         {
     619           0 :             commands[i].player = m_Players[i]->m_Player;
     620           0 :             commands[i].commands = m_Players[i]->m_Commands;
     621             :         }
     622           0 :     }
     623             : 
     624           0 :     void LoadEntityTemplates(const std::vector<std::pair<std::string, const CParamNode*> >& templates)
     625             :     {
     626           0 :         ScriptRequest rq(m_ScriptInterface);
     627             : 
     628           0 :         m_HasLoadedEntityTemplates = true;
     629             : 
     630           0 :         Script::CreateObject(rq, &m_EntityTemplates);
     631             : 
     632           0 :         JS::RootedValue val(rq.cx);
     633           0 :         for (size_t i = 0; i < templates.size(); ++i)
     634             :         {
     635           0 :             templates[i].second->ToJSVal(rq, false, &val);
     636           0 :             Script::SetProperty(rq, m_EntityTemplates, templates[i].first.c_str(), val, true);
     637             :         }
     638           0 :     }
     639             : 
     640           0 :     void Serialize(std::ostream& stream, bool isDebug)
     641             :     {
     642           0 :         WaitToFinishComputation();
     643             : 
     644           0 :         if (isDebug)
     645             :         {
     646           0 :             CDebugSerializer serializer(*m_ScriptInterface, stream);
     647           0 :             serializer.Indent(4);
     648           0 :             SerializeState(serializer);
     649             :         }
     650             :         else
     651             :         {
     652           0 :             CStdSerializer serializer(*m_ScriptInterface, stream);
     653           0 :             SerializeState(serializer);
     654             :         }
     655           0 :     }
     656             : 
     657           0 :     void SerializeState(ISerializer& serializer)
     658             :     {
     659           0 :         if (m_Players.empty())
     660           0 :             return;
     661             : 
     662           0 :         ScriptRequest rq(m_ScriptInterface);
     663             : 
     664           0 :         std::stringstream rngStream;
     665           0 :         rngStream << m_RNG;
     666           0 :         serializer.StringASCII("rng", rngStream.str(), 0, 32);
     667             : 
     668           0 :         serializer.NumberU32_Unbounded("turn", m_TurnNum);
     669             : 
     670           0 :         serializer.Bool("useSharedScript", m_HasSharedComponent);
     671           0 :         if (m_HasSharedComponent)
     672           0 :             serializer.ScriptVal("sharedData", &m_SharedAIObj);
     673           0 :         for (size_t i = 0; i < m_Players.size(); ++i)
     674             :         {
     675           0 :             serializer.String("name", m_Players[i]->m_AIName, 1, 256);
     676           0 :             serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player);
     677           0 :             serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty);
     678           0 :             serializer.String("behavior", m_Players[i]->m_Behavior, 1, 256);
     679             : 
     680           0 :             serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size());
     681           0 :             for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j)
     682             :             {
     683           0 :                 JS::RootedValue val(rq.cx);
     684           0 :                 Script::ReadStructuredClone(rq, m_Players[i]->m_Commands[j], &val);
     685           0 :                 serializer.ScriptVal("command", &val);
     686             :             }
     687             : 
     688           0 :             serializer.ScriptVal("data", &m_Players[i]->m_Obj);
     689             :         }
     690             : 
     691             :         // AI pathfinder
     692           0 :         Serializer(serializer, "non pathfinding pass classes", m_NonPathfindingPassClasses);
     693           0 :         Serializer(serializer, "pathfinding pass classes", m_PathfindingPassClasses);
     694           0 :         serializer.NumberU16_Unbounded("pathfinder grid w", m_PassabilityMap.m_W);
     695           0 :         serializer.NumberU16_Unbounded("pathfinder grid h", m_PassabilityMap.m_H);
     696           0 :         serializer.RawBytes("pathfinder grid data", (const u8*)m_PassabilityMap.m_Data,
     697           0 :             m_PassabilityMap.m_W*m_PassabilityMap.m_H*sizeof(NavcellData));
     698             :     }
     699             : 
     700           0 :     void Deserialize(std::istream& stream, u32 numAis)
     701             :     {
     702           0 :         m_PlayerMetadata.clear();
     703           0 :         m_Players.clear();
     704             : 
     705           0 :         if (numAis == 0)
     706           0 :             return;
     707             : 
     708           0 :         ScriptRequest rq(m_ScriptInterface);
     709             : 
     710           0 :         ENSURE(m_CommandsComputed); // deserializing while we're still actively computing would be bad
     711             : 
     712           0 :         CStdDeserializer deserializer(*m_ScriptInterface, stream);
     713             : 
     714           0 :         std::string rngString;
     715           0 :         std::stringstream rngStream;
     716           0 :         deserializer.StringASCII("rng", rngString, 0, 32);
     717           0 :         rngStream << rngString;
     718           0 :         rngStream >> m_RNG;
     719             : 
     720           0 :         deserializer.NumberU32_Unbounded("turn", m_TurnNum);
     721             : 
     722           0 :         deserializer.Bool("useSharedScript", m_HasSharedComponent);
     723           0 :         if (m_HasSharedComponent)
     724             :         {
     725           0 :             TryLoadSharedComponent();
     726           0 :             deserializer.ScriptObjectAssign("sharedData", m_SharedAIObj);
     727             :         }
     728             : 
     729           0 :         for (size_t i = 0; i < numAis; ++i)
     730             :         {
     731           0 :             std::wstring name;
     732             :             player_id_t player;
     733             :             u8 difficulty;
     734           0 :             std::wstring behavior;
     735           0 :             deserializer.String("name", name, 1, 256);
     736           0 :             deserializer.NumberI32_Unbounded("player", player);
     737           0 :             deserializer.NumberU8_Unbounded("difficulty",difficulty);
     738           0 :             deserializer.String("behavior", behavior, 1, 256);
     739           0 :             if (!AddPlayer(name, player, difficulty, behavior))
     740           0 :                 throw PSERROR_Deserialize_ScriptError();
     741             : 
     742             :             u32 numCommands;
     743           0 :             deserializer.NumberU32_Unbounded("num commands", numCommands);
     744           0 :             m_Players.back()->m_Commands.reserve(numCommands);
     745           0 :             for (size_t j = 0; j < numCommands; ++j)
     746             :             {
     747           0 :                 JS::RootedValue val(rq.cx);
     748           0 :                 deserializer.ScriptVal("command", &val);
     749           0 :                 m_Players.back()->m_Commands.push_back(Script::WriteStructuredClone(rq, val));
     750             :             }
     751             : 
     752           0 :             deserializer.ScriptObjectAssign("data", m_Players.back()->m_Obj);
     753             :         }
     754             : 
     755             :         // AI pathfinder
     756           0 :         Serializer(deserializer, "non pathfinding pass classes", m_NonPathfindingPassClasses);
     757           0 :         Serializer(deserializer, "pathfinding pass classes", m_PathfindingPassClasses);
     758             :         u16 mapW, mapH;
     759           0 :         deserializer.NumberU16_Unbounded("pathfinder grid w", mapW);
     760           0 :         deserializer.NumberU16_Unbounded("pathfinder grid h", mapH);
     761           0 :         m_PassabilityMap = Grid<NavcellData>(mapW, mapH);
     762           0 :         deserializer.RawBytes("pathfinder grid data", (u8*)m_PassabilityMap.m_Data, mapW*mapH*sizeof(NavcellData));
     763           0 :         m_LongPathfinder.Reload(&m_PassabilityMap);
     764           0 :         m_HierarchicalPathfinder.Recompute(&m_PassabilityMap, m_NonPathfindingPassClasses, m_PathfindingPassClasses);
     765             :     }
     766             : 
     767           0 :     int getPlayerSize()
     768             :     {
     769           0 :         return m_Players.size();
     770             :     }
     771             : 
     772             : private:
     773           0 :     static void Trace(JSTracer *trc, void *data)
     774             :     {
     775           0 :         reinterpret_cast<CAIWorker*>(data)->TraceMember(trc);
     776           0 :     }
     777             : 
     778           0 :     void TraceMember(JSTracer *trc)
     779             :     {
     780           0 :         for (std::pair<const VfsPath, JS::Heap<JS::Value>>& metadata : m_PlayerMetadata)
     781           0 :             JS::TraceEdge(trc, &metadata.second, "CAIWorker::m_PlayerMetadata");
     782           0 :     }
     783             : 
     784           0 :     void LoadMetadata(const VfsPath& path, JS::MutableHandleValue out)
     785             :     {
     786           0 :         if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end())
     787             :         {
     788             :             // Load and cache the AI player metadata
     789           0 :             Script::ReadJSONFile(ScriptRequest(m_ScriptInterface), path, out);
     790           0 :             m_PlayerMetadata[path] = JS::Heap<JS::Value>(out);
     791           0 :             return;
     792             :         }
     793           0 :         out.set(m_PlayerMetadata[path].get());
     794             :     }
     795             : 
     796           0 :     void PerformComputation()
     797             :     {
     798             :         // Deserialize the game state, to pass to the AI's HandleMessage
     799           0 :         ScriptRequest rq(m_ScriptInterface);
     800           0 :         JS::RootedValue state(rq.cx);
     801             :         {
     802           0 :             PROFILE3("AI compute read state");
     803           0 :             Script::ReadStructuredClone(rq, m_GameState, &state);
     804           0 :             Script::SetProperty(rq, state, "passabilityMap", m_PassabilityMapVal, true);
     805           0 :             Script::SetProperty(rq, state, "territoryMap", m_TerritoryMapVal, true);
     806             :         }
     807             : 
     808             :         // It would be nice to do
     809             :         //   Script::FreezeObject(rq, state.get(), true);
     810             :         // to prevent AI scripts accidentally modifying the state and
     811             :         // affecting other AI scripts they share it with. But the performance
     812             :         // cost is far too high, so we won't do that.
     813             :         // If there is a shared component, run it
     814             : 
     815           0 :         if (m_HasSharedComponent)
     816             :         {
     817           0 :             PROFILE3("AI run shared component");
     818           0 :             ScriptFunction::CallVoid(rq, m_SharedAIObj, "onUpdate", state);
     819             :         }
     820             : 
     821           0 :         for (size_t i = 0; i < m_Players.size(); ++i)
     822             :         {
     823           0 :             PROFILE3("AI script");
     824           0 :             PROFILE2_ATTR("player: %d", m_Players[i]->m_Player);
     825           0 :             PROFILE2_ATTR("script: %ls", m_Players[i]->m_AIName.c_str());
     826             : 
     827           0 :             if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent)
     828           0 :                 m_Players[i]->Run(state, m_Players[i]->m_Player, m_SharedAIObj);
     829             :             else
     830           0 :                 m_Players[i]->Run(state, m_Players[i]->m_Player);
     831             :         }
     832           0 :     }
     833             : 
     834             :     std::shared_ptr<ScriptInterface> m_ScriptInterface;
     835             :     boost::rand48 m_RNG;
     836             :     u32 m_TurnNum;
     837             : 
     838             :     JS::PersistentRootedValue m_EntityTemplates;
     839             :     bool m_HasLoadedEntityTemplates;
     840             : 
     841             :     std::map<VfsPath, JS::Heap<JS::Value>> m_PlayerMetadata;
     842             :     std::vector<std::shared_ptr<CAIPlayer>> m_Players; // use shared_ptr just to avoid copying
     843             : 
     844             :     bool m_HasSharedComponent;
     845             :     JS::PersistentRootedValue m_SharedAIObj;
     846             :     std::vector<SCommandSets> m_Commands;
     847             : 
     848             :     std::set<std::wstring> m_LoadedModules;
     849             : 
     850             :     Script::StructuredClone m_GameState;
     851             :     Grid<NavcellData> m_PassabilityMap;
     852             :     JS::PersistentRootedValue m_PassabilityMapVal;
     853             :     Grid<u8> m_TerritoryMap;
     854             :     JS::PersistentRootedValue m_TerritoryMapVal;
     855             : 
     856             :     std::map<std::string, pass_class_t> m_NonPathfindingPassClasses;
     857             :     std::map<std::string, pass_class_t> m_PathfindingPassClasses;
     858             :     HierarchicalPathfinder m_HierarchicalPathfinder;
     859             :     LongPathfinder m_LongPathfinder;
     860             : 
     861             :     bool m_CommandsComputed;
     862             : 
     863             :     CTemplateLoader m_TemplateLoader;
     864             : };
     865             : 
     866             : 
     867             : /**
     868             :  * Implementation of ICmpAIManager.
     869             :  */
     870           0 : class CCmpAIManager final : public ICmpAIManager
     871             : {
     872             : public:
     873         116 :     static void ClassInit(CComponentManager& UNUSED(componentManager))
     874             :     {
     875         116 :     }
     876             : 
     877           0 :     DEFAULT_COMPONENT_ALLOCATOR(AIManager)
     878             : 
     879         116 :     static std::string GetSchema()
     880             :     {
     881         116 :         return "<a:component type='system'/><empty/>";
     882             :     }
     883             : 
     884           0 :     void Init(const CParamNode& UNUSED(paramNode)) override
     885             :     {
     886           0 :         m_Worker.Init(GetSimContext().GetScriptInterface());
     887             : 
     888           0 :         m_TerritoriesDirtyID = 0;
     889           0 :         m_TerritoriesDirtyBlinkingID = 0;
     890           0 :         m_JustDeserialized = false;
     891           0 :     }
     892             : 
     893           0 :     void Deinit() override
     894             :     {
     895           0 :     }
     896             : 
     897           0 :     void Serialize(ISerializer& serialize) override
     898             :     {
     899           0 :         serialize.NumberU32_Unbounded("num ais", m_Worker.getPlayerSize());
     900             : 
     901             :         // Because the AI worker uses its own ScriptInterface, we can't use the
     902             :         // ISerializer (which was initialised with the simulation ScriptInterface)
     903             :         // directly. So we'll just grab the ISerializer's stream and write to it
     904             :         // with an independent serializer.
     905             : 
     906           0 :         m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
     907           0 :     }
     908             : 
     909           0 :     void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
     910             :     {
     911           0 :         Init(paramNode);
     912             : 
     913             :         u32 numAis;
     914           0 :         deserialize.NumberU32_Unbounded("num ais", numAis);
     915           0 :         if (numAis > 0)
     916           0 :             LoadUsedEntityTemplates();
     917             : 
     918           0 :         m_Worker.Deserialize(deserialize.GetStream(), numAis);
     919             : 
     920           0 :         m_JustDeserialized = true;
     921           0 :     }
     922             : 
     923           0 :     void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty, const std::wstring& behavior) override
     924             :     {
     925           0 :         LoadUsedEntityTemplates();
     926             : 
     927           0 :         m_Worker.AddPlayer(id, player, difficulty, behavior);
     928             : 
     929             :         // AI players can cheat and see through FoW/SoD, since that greatly simplifies
     930             :         // their implementation.
     931             :         // (TODO: maybe cleverer AIs should be able to optionally retain FoW/SoD)
     932           0 :         CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
     933           0 :         if (cmpRangeManager)
     934           0 :             cmpRangeManager->SetLosRevealAll(player, true);
     935           0 :     }
     936             : 
     937           0 :     void SetRNGSeed(u32 seed) override
     938             :     {
     939           0 :         m_Worker.SetRNGSeed(seed);
     940           0 :     }
     941             : 
     942           0 :     void TryLoadSharedComponent() override
     943             :     {
     944           0 :         m_Worker.TryLoadSharedComponent();
     945           0 :     }
     946             : 
     947           0 :     void RunGamestateInit() override
     948             :     {
     949           0 :         const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
     950           0 :         ScriptRequest rq(scriptInterface);
     951             : 
     952           0 :         CmpPtr<ICmpAIInterface> cmpAIInterface(GetSystemEntity());
     953           0 :         ENSURE(cmpAIInterface);
     954             : 
     955             :         // Get the game state from AIInterface
     956             :         // We flush events from the initialization so we get a clean state now.
     957           0 :         JS::RootedValue state(rq.cx);
     958           0 :         cmpAIInterface->GetFullRepresentation(&state, true);
     959             : 
     960             :         // Get the passability data
     961           0 :         Grid<NavcellData> dummyGrid;
     962           0 :         const Grid<NavcellData>* passabilityMap = &dummyGrid;
     963           0 :         CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
     964           0 :         if (cmpPathfinder)
     965           0 :             passabilityMap = &cmpPathfinder->GetPassabilityGrid();
     966             : 
     967             :         // Get the territory data
     968             :         // Since getting the territory grid can trigger a recalculation, we check NeedUpdateAI first
     969           0 :         Grid<u8> dummyGrid2;
     970           0 :         const Grid<u8>* territoryMap = &dummyGrid2;
     971           0 :         CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity());
     972           0 :         if (cmpTerritoryManager && cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID))
     973           0 :             territoryMap = &cmpTerritoryManager->GetTerritoryGrid();
     974             : 
     975           0 :         LoadPathfinderClasses(state);
     976           0 :         std::map<std::string, pass_class_t> nonPathfindingPassClassMasks, pathfindingPassClassMasks;
     977           0 :         if (cmpPathfinder)
     978           0 :             cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks);
     979             : 
     980           0 :         m_Worker.RunGamestateInit(Script::WriteStructuredClone(rq, state),
     981             :             *passabilityMap, *territoryMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks);
     982           0 :     }
     983             : 
     984           0 :     void StartComputation() override
     985             :     {
     986           0 :         PROFILE("AI setup");
     987             : 
     988           0 :         const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
     989           0 :         ScriptRequest rq(scriptInterface);
     990             : 
     991           0 :         if (m_Worker.getPlayerSize() == 0)
     992           0 :             return;
     993             : 
     994           0 :         CmpPtr<ICmpAIInterface> cmpAIInterface(GetSystemEntity());
     995           0 :         ENSURE(cmpAIInterface);
     996             : 
     997             :         // Get the game state from AIInterface
     998           0 :         JS::RootedValue state(rq.cx);
     999           0 :         if (m_JustDeserialized)
    1000           0 :             cmpAIInterface->GetFullRepresentation(&state, false);
    1001             :         else
    1002           0 :             cmpAIInterface->GetRepresentation(&state);
    1003           0 :         LoadPathfinderClasses(state); // add the pathfinding classes to it
    1004             : 
    1005             :         // Update the game state
    1006           0 :         m_Worker.UpdateGameState(Script::WriteStructuredClone(rq, state));
    1007             : 
    1008             :         // Update the pathfinding data
    1009           0 :         CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    1010           0 :         if (cmpPathfinder)
    1011             :         {
    1012           0 :             const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation();
    1013             : 
    1014           0 :             if (dirtinessInformations.dirty || m_JustDeserialized)
    1015             :             {
    1016           0 :                 const Grid<NavcellData>& passabilityMap = cmpPathfinder->GetPassabilityGrid();
    1017             : 
    1018           0 :                 std::map<std::string, pass_class_t> nonPathfindingPassClassMasks, pathfindingPassClassMasks;
    1019           0 :                 cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks);
    1020             : 
    1021           0 :                 m_Worker.UpdatePathfinder(passabilityMap,
    1022           0 :                     dirtinessInformations.globallyDirty, dirtinessInformations.dirtinessGrid, m_JustDeserialized,
    1023             :                     nonPathfindingPassClassMasks, pathfindingPassClassMasks);
    1024             :             }
    1025             : 
    1026           0 :             cmpPathfinder->FlushAIPathfinderDirtinessInformation();
    1027             :         }
    1028             : 
    1029             :         // Update the territory data
    1030             :         // Since getting the territory grid can trigger a recalculation, we check NeedUpdateAI first
    1031           0 :         CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity());
    1032           0 :         if (cmpTerritoryManager && (cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID) || m_JustDeserialized))
    1033             :         {
    1034           0 :             const Grid<u8>& territoryMap = cmpTerritoryManager->GetTerritoryGrid();
    1035           0 :             m_Worker.UpdateTerritoryMap(territoryMap);
    1036             :         }
    1037             : 
    1038           0 :         m_Worker.StartComputation();
    1039             : 
    1040           0 :         m_JustDeserialized = false;
    1041             :     }
    1042             : 
    1043           0 :     void PushCommands() override
    1044             :     {
    1045           0 :         std::vector<CAIWorker::SCommandSets> commands;
    1046           0 :         m_Worker.GetCommands(commands);
    1047             : 
    1048           0 :         CmpPtr<ICmpCommandQueue> cmpCommandQueue(GetSystemEntity());
    1049           0 :         if (!cmpCommandQueue)
    1050           0 :             return;
    1051             : 
    1052           0 :         const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
    1053           0 :         ScriptRequest rq(scriptInterface);
    1054           0 :         JS::RootedValue clonedCommandVal(rq.cx);
    1055             : 
    1056           0 :         for (size_t i = 0; i < commands.size(); ++i)
    1057             :         {
    1058           0 :             for (size_t j = 0; j < commands[i].commands.size(); ++j)
    1059             :             {
    1060           0 :                 Script::ReadStructuredClone(rq, commands[i].commands[j], &clonedCommandVal);
    1061           0 :                 cmpCommandQueue->PushLocalCommand(commands[i].player, clonedCommandVal);
    1062             :             }
    1063             :         }
    1064             :     }
    1065             : 
    1066             : private:
    1067             :     size_t m_TerritoriesDirtyID;
    1068             :     size_t m_TerritoriesDirtyBlinkingID;
    1069             : 
    1070             :     bool m_JustDeserialized;
    1071             : 
    1072             :     /**
    1073             :      * Load the templates of all entities on the map (called when adding a new AI player for a new game
    1074             :      * or when deserializing)
    1075             :      */
    1076           0 :     void LoadUsedEntityTemplates()
    1077             :     {
    1078           0 :         if (m_Worker.HasLoadedEntityTemplates())
    1079           0 :             return;
    1080             : 
    1081           0 :         CmpPtr<ICmpTemplateManager> cmpTemplateManager(GetSystemEntity());
    1082           0 :         ENSURE(cmpTemplateManager);
    1083             : 
    1084           0 :         std::vector<std::string> templateNames = cmpTemplateManager->FindUsedTemplates();
    1085           0 :         std::vector<std::pair<std::string, const CParamNode*> > usedTemplates;
    1086           0 :         usedTemplates.reserve(templateNames.size());
    1087           0 :         for (const std::string& name : templateNames)
    1088             :         {
    1089           0 :             const CParamNode* node = cmpTemplateManager->GetTemplateWithoutValidation(name);
    1090           0 :             if (node)
    1091           0 :                 usedTemplates.emplace_back(name, node);
    1092             :         }
    1093             :         // Send the data to the worker
    1094           0 :         m_Worker.LoadEntityTemplates(usedTemplates);
    1095             :     }
    1096             : 
    1097           0 :     void LoadPathfinderClasses(JS::HandleValue state)
    1098             :     {
    1099           0 :         CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    1100           0 :         if (!cmpPathfinder)
    1101           0 :             return;
    1102             : 
    1103           0 :         const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
    1104           0 :         ScriptRequest rq(scriptInterface);
    1105             : 
    1106           0 :         JS::RootedValue classesVal(rq.cx);
    1107           0 :         Script::CreateObject(rq, &classesVal);
    1108             : 
    1109           0 :         std::map<std::string, pass_class_t> classes;
    1110           0 :         cmpPathfinder->GetPassabilityClasses(classes);
    1111           0 :         for (std::map<std::string, pass_class_t>::iterator it = classes.begin(); it != classes.end(); ++it)
    1112           0 :             Script::SetProperty(rq, classesVal, it->first.c_str(), it->second, true);
    1113             : 
    1114           0 :         Script::SetProperty(rq, state, "passabilityClasses", classesVal, true);
    1115             :     }
    1116             : 
    1117             :     CAIWorker m_Worker;
    1118             : };
    1119             : 
    1120         119 : REGISTER_COMPONENT_TYPE(AIManager)

Generated by: LCOV version 1.13