LCOV - code coverage report
Current view: top level - source/simulation2 - Simulation2.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 68 474 14.3 %
Date: 2023-01-19 00:18:29 Functions: 18 76 23.7 %

          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.h"
      21             : 
      22             : #include "scriptinterface/FunctionWrapper.h"
      23             : #include "scriptinterface/ScriptContext.h"
      24             : #include "scriptinterface/ScriptInterface.h"
      25             : #include "scriptinterface/JSON.h"
      26             : #include "scriptinterface/StructuredClone.h"
      27             : 
      28             : #include "simulation2/MessageTypes.h"
      29             : #include "simulation2/system/ComponentManager.h"
      30             : #include "simulation2/system/ParamNode.h"
      31             : #include "simulation2/system/SimContext.h"
      32             : #include "simulation2/components/ICmpAIManager.h"
      33             : #include "simulation2/components/ICmpCommandQueue.h"
      34             : #include "simulation2/components/ICmpTemplateManager.h"
      35             : 
      36             : #include "graphics/MapReader.h"
      37             : #include "graphics/Terrain.h"
      38             : #include "lib/timer.h"
      39             : #include "lib/file/vfs/vfs_util.h"
      40             : #include "maths/MathUtil.h"
      41             : #include "ps/CLogger.h"
      42             : #include "ps/ConfigDB.h"
      43             : #include "ps/Filesystem.h"
      44             : #include "ps/Loader.h"
      45             : #include "ps/Profile.h"
      46             : #include "ps/Pyrogenesis.h"
      47             : #include "ps/Util.h"
      48             : #include "ps/XML/Xeromyces.h"
      49             : 
      50             : #include <fstream>
      51             : #include <iomanip>
      52             : #include <memory>
      53             : 
      54             : class CSimulation2Impl
      55             : {
      56             : public:
      57           4 :     CSimulation2Impl(CUnitManager* unitManager, std::shared_ptr<ScriptContext> cx, CTerrain* terrain) :
      58             :         m_SimContext(), m_ComponentManager(m_SimContext, cx),
      59             :         m_EnableOOSLog(false), m_EnableSerializationTest(false), m_RejoinTestTurn(-1), m_TestingRejoin(false),
      60           4 :         m_MapSettings(cx->GetGeneralJSContext()), m_InitAttributes(cx->GetGeneralJSContext())
      61             :     {
      62           4 :         m_SimContext.m_UnitManager = unitManager;
      63           4 :         m_SimContext.m_Terrain = terrain;
      64           4 :         m_ComponentManager.LoadComponentTypes();
      65             : 
      66           4 :         RegisterFileReloadFunc(ReloadChangedFileCB, this);
      67             : 
      68             :         // Tests won't have config initialised
      69           4 :         if (CConfigDB::IsInitialised())
      70             :         {
      71           0 :             CFG_GET_VAL("ooslog", m_EnableOOSLog);
      72           0 :             CFG_GET_VAL("serializationtest", m_EnableSerializationTest);
      73           0 :             CFG_GET_VAL("rejointest", m_RejoinTestTurn);
      74           0 :             if (m_RejoinTestTurn < 0) // Handle bogus values of the arg
      75           0 :                 m_RejoinTestTurn = -1;
      76             :         }
      77             : 
      78           4 :         if (m_EnableOOSLog)
      79             :         {
      80           0 :             m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs");
      81           0 :             debug_printf("Writing ooslogs to %s\n", m_OOSLogPath.string8().c_str());
      82             :         }
      83           4 :     }
      84             : 
      85           4 :     ~CSimulation2Impl()
      86           4 :     {
      87           4 :         UnregisterFileReloadFunc(ReloadChangedFileCB, this);
      88           4 :     }
      89             : 
      90           3 :     void ResetState(bool skipScriptedComponents, bool skipAI)
      91             :     {
      92           3 :         m_DeltaTime = 0.0;
      93           3 :         m_LastFrameOffset = 0.0f;
      94           3 :         m_TurnNumber = 0;
      95           3 :         ResetComponentState(m_ComponentManager, skipScriptedComponents, skipAI);
      96           3 :     }
      97             : 
      98           3 :     static void ResetComponentState(CComponentManager& componentManager, bool skipScriptedComponents, bool skipAI)
      99             :     {
     100           3 :         componentManager.ResetState();
     101           3 :         componentManager.InitSystemEntity();
     102           3 :         componentManager.AddSystemComponents(skipScriptedComponents, skipAI);
     103           3 :     }
     104             : 
     105             :     static bool LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts);
     106             :     static bool LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path);
     107             :     static bool LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set<VfsPath>* loadedScripts);
     108             :     Status ReloadChangedFile(const VfsPath& path);
     109             : 
     110           0 :     static Status ReloadChangedFileCB(void* param, const VfsPath& path)
     111             :     {
     112           0 :         return static_cast<CSimulation2Impl*>(param)->ReloadChangedFile(path);
     113             :     }
     114             : 
     115             :     int ProgressiveLoad();
     116             :     void Update(int turnLength, const std::vector<SimulationCommand>& commands);
     117             :     static void UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands);
     118             :     void Interpolate(float simFrameLength, float frameOffset, float realFrameLength);
     119             : 
     120             :     void DumpState();
     121             : 
     122             :     CSimContext m_SimContext;
     123             :     CComponentManager m_ComponentManager;
     124             :     double m_DeltaTime;
     125             :     float m_LastFrameOffset;
     126             : 
     127             :     std::string m_StartupScript;
     128             :     JS::PersistentRootedValue m_InitAttributes;
     129             :     JS::PersistentRootedValue m_MapSettings;
     130             : 
     131             :     std::set<VfsPath> m_LoadedScripts;
     132             : 
     133             :     uint32_t m_TurnNumber;
     134             : 
     135             :     bool m_EnableOOSLog;
     136             :     OsPath m_OOSLogPath;
     137             : 
     138             :     // Functions and data for the serialization test mode: (see Update() for relevant comments)
     139             : 
     140             :     bool m_EnableSerializationTest;
     141             :     int m_RejoinTestTurn;
     142             :     bool m_TestingRejoin;
     143             : 
     144             :     // Secondary simulation (NB: order matters for destruction).
     145             :     std::unique_ptr<CComponentManager> m_SecondaryComponentManager;
     146             :     std::unique_ptr<CTerrain> m_SecondaryTerrain;
     147             :     std::unique_ptr<CSimContext> m_SecondaryContext;
     148             :     std::unique_ptr<std::set<VfsPath>> m_SecondaryLoadedScripts;
     149             : 
     150           0 :     struct SerializationTestState
     151             :     {
     152             :         std::stringstream state;
     153             :         std::stringstream debug;
     154             :         std::string hash;
     155             :     };
     156             : 
     157             :     void DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix);
     158             : 
     159             :     void ReportSerializationFailure(
     160             :         SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
     161             :         SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter);
     162             : 
     163             :     void InitRNGSeedSimulation();
     164             :     void InitRNGSeedAI();
     165             : 
     166           0 :     static std::vector<SimulationCommand> CloneCommandsFromOtherCompartment(const ScriptInterface& newScript, const ScriptInterface& oldScript,
     167             :         const std::vector<SimulationCommand>& commands)
     168             :     {
     169           0 :         std::vector<SimulationCommand> newCommands;
     170           0 :         newCommands.reserve(commands.size());
     171             : 
     172           0 :         ScriptRequest rqNew(newScript);
     173           0 :         for (const SimulationCommand& command : commands)
     174             :         {
     175           0 :             JS::RootedValue tmpCommand(rqNew.cx, Script::CloneValueFromOtherCompartment(newScript, oldScript, command.data));
     176           0 :             Script::FreezeObject(rqNew, tmpCommand, true);
     177           0 :             SimulationCommand cmd(command.player, rqNew.cx, tmpCommand);
     178           0 :             newCommands.emplace_back(std::move(cmd));
     179             :         }
     180           0 :         return newCommands;
     181             :     }
     182             : };
     183             : 
     184           0 : bool CSimulation2Impl::LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts)
     185             : {
     186             :     return (
     187           0 :         LoadScripts(componentManager, loadedScripts, L"simulation/components/interfaces/") &&
     188           0 :         LoadScripts(componentManager, loadedScripts, L"simulation/helpers/") &&
     189           0 :         LoadScripts(componentManager, loadedScripts, L"simulation/components/")
     190           0 :     );
     191             : }
     192             : 
     193           3 : bool CSimulation2Impl::LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path)
     194             : {
     195           6 :     VfsPaths pathnames;
     196           3 :     if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
     197           0 :         return false;
     198             : 
     199           3 :     bool ok = true;
     200           6 :     for (const VfsPath& scriptPath : pathnames)
     201             :     {
     202           3 :         if (loadedScripts)
     203           3 :             loadedScripts->insert(scriptPath);
     204           3 :         LOGMESSAGE("Loading simulation script '%s'", scriptPath.string8());
     205           3 :         if (!componentManager.LoadScript(scriptPath))
     206           0 :             ok = false;
     207             :     }
     208           3 :     return ok;
     209             : }
     210             : 
     211           0 : bool CSimulation2Impl::LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set<VfsPath>* loadedScripts)
     212             : {
     213           0 :     bool ok = true;
     214           0 :     ScriptRequest rq(componentManager.GetScriptInterface());
     215           0 :     if (Script::HasProperty(rq, mapSettings, "TriggerScripts"))
     216             :     {
     217           0 :         std::vector<std::string> scriptNames;
     218           0 :         Script::GetProperty(rq, mapSettings, "TriggerScripts", scriptNames);
     219           0 :         for (const std::string& triggerScript : scriptNames)
     220             :         {
     221           0 :             std::string scriptName = "maps/" + triggerScript;
     222           0 :             if (loadedScripts)
     223             :             {
     224           0 :                 if (loadedScripts->find(scriptName) != loadedScripts->end())
     225           0 :                     continue;
     226           0 :                 loadedScripts->insert(scriptName);
     227             :             }
     228           0 :             LOGMESSAGE("Loading trigger script '%s'", scriptName.c_str());
     229           0 :             if (!componentManager.LoadScript(scriptName.data()))
     230           0 :                 ok = false;
     231             :         }
     232             :     }
     233           0 :     return ok;
     234             : }
     235             : 
     236           5 : Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
     237             : {
     238             :     // Ignore if this file wasn't loaded as a script
     239             :     // (TODO: Maybe we ought to load in any new .js files that are created in the right directories)
     240           5 :     if (m_LoadedScripts.find(path) == m_LoadedScripts.end())
     241           3 :         return INFO::OK;
     242             : 
     243             :     // If the file doesn't exist (e.g. it was deleted), don't bother loading it since that'll give an error message.
     244             :     // (Also don't bother trying to 'unload' it from the component manager, because that's not possible)
     245           2 :     if (!VfsFileExists(path))
     246           0 :         return INFO::OK;
     247             : 
     248           2 :     LOGMESSAGE("Reloading simulation script '%s'", path.string8());
     249           2 :     if (!m_ComponentManager.LoadScript(path, true))
     250           0 :         return ERR::FAIL;
     251             : 
     252           2 :     return INFO::OK;
     253             : }
     254             : 
     255           0 : int CSimulation2Impl::ProgressiveLoad()
     256             : {
     257             :     // yield after this time is reached. balances increased progress bar
     258             :     // smoothness vs. slowing down loading.
     259           0 :     const double end_time = timer_Time() + 200e-3;
     260             : 
     261             :     int ret;
     262             : 
     263           0 :     do
     264             :     {
     265           0 :         bool progressed = false;
     266           0 :         int total = 0;
     267           0 :         int progress = 0;
     268             : 
     269           0 :         CMessageProgressiveLoad msg(&progressed, &total, &progress);
     270             : 
     271           0 :         m_ComponentManager.BroadcastMessage(msg);
     272             : 
     273           0 :         if (!progressed || total == 0)
     274           0 :             return 0; // we have nothing left to load
     275             : 
     276           0 :         ret = Clamp(100*progress / total, 1, 100);
     277             :     }
     278           0 :     while (timer_Time() < end_time);
     279             : 
     280           0 :     return ret;
     281             : }
     282             : 
     283           0 : void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix)
     284             : {
     285           0 :     if (!state.hash.empty())
     286             :     {
     287           0 :         std::ofstream file (OsString(path / (L"hash." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
     288           0 :         file << Hexify(state.hash);
     289             :     }
     290             : 
     291           0 :     if (!state.debug.str().empty())
     292             :     {
     293           0 :         std::ofstream file (OsString(path / (L"debug." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
     294           0 :         file << state.debug.str();
     295             :     }
     296             : 
     297           0 :     if (!state.state.str().empty())
     298             :     {
     299           0 :         std::ofstream file (OsString(path / (L"state." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
     300           0 :         file << state.state.str();
     301             :     }
     302           0 : }
     303             : 
     304           0 : void CSimulation2Impl::ReportSerializationFailure(
     305             :     SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
     306             :     SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter)
     307             : {
     308           0 :     const OsPath path = createDateIndexSubdirectory(psLogDir() / "serializationtest");
     309           0 :     debug_printf("Writing serializationtest-data to %s\n", path.string8().c_str());
     310             : 
     311             :     // Clean up obsolete files from previous runs
     312           0 :     wunlink(path / "hash.before.a");
     313           0 :     wunlink(path / "hash.before.b");
     314           0 :     wunlink(path / "debug.before.a");
     315           0 :     wunlink(path / "debug.before.b");
     316           0 :     wunlink(path / "state.before.a");
     317           0 :     wunlink(path / "state.before.b");
     318           0 :     wunlink(path / "hash.after.a");
     319           0 :     wunlink(path / "hash.after.b");
     320           0 :     wunlink(path / "debug.after.a");
     321           0 :     wunlink(path / "debug.after.b");
     322           0 :     wunlink(path / "state.after.a");
     323           0 :     wunlink(path / "state.after.b");
     324             : 
     325           0 :     if (primaryStateBefore)
     326           0 :         DumpSerializationTestState(*primaryStateBefore, path, L"before.a");
     327           0 :     if (primaryStateAfter)
     328           0 :         DumpSerializationTestState(*primaryStateAfter, path, L"after.a");
     329           0 :     if (secondaryStateBefore)
     330           0 :         DumpSerializationTestState(*secondaryStateBefore, path, L"before.b");
     331           0 :     if (secondaryStateAfter)
     332           0 :         DumpSerializationTestState(*secondaryStateAfter, path, L"after.b");
     333             : 
     334           0 :     debug_warn(L"Serialization test failure");
     335           0 : }
     336             : 
     337           0 : void CSimulation2Impl::InitRNGSeedSimulation()
     338             : {
     339           0 :     u32 seed = 0;
     340           0 :     ScriptRequest rq(m_ComponentManager.GetScriptInterface());
     341           0 :     if (!Script::HasProperty(rq, m_MapSettings, "Seed") ||
     342           0 :         !Script::GetProperty(rq, m_MapSettings, "Seed", seed))
     343           0 :         LOGWARNING("CSimulation2Impl::InitRNGSeedSimulation: No seed value specified - using %d", seed);
     344             : 
     345           0 :     m_ComponentManager.SetRNGSeed(seed);
     346           0 : }
     347             : 
     348           0 : void CSimulation2Impl::InitRNGSeedAI()
     349             : {
     350           0 :     u32 seed = 0;
     351           0 :     ScriptRequest rq(m_ComponentManager.GetScriptInterface());
     352           0 :     if (!Script::HasProperty(rq, m_MapSettings, "AISeed") ||
     353           0 :         !Script::GetProperty(rq, m_MapSettings, "AISeed", seed))
     354           0 :         LOGWARNING("CSimulation2Impl::InitRNGSeedAI: No seed value specified - using %d", seed);
     355             : 
     356           0 :     CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY);
     357           0 :     if (cmpAIManager)
     358           0 :         cmpAIManager->SetRNGSeed(seed);
     359           0 : }
     360             : 
     361           0 : void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
     362             : {
     363           0 :     PROFILE3("sim update");
     364           0 :     PROFILE2_ATTR("turn %d", (int)m_TurnNumber);
     365             : 
     366           0 :     fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
     367             : 
     368             :     /*
     369             :      * In serialization test mode, we save the original (primary) simulation state before each turn update.
     370             :      * We run the update, then load the saved state into a secondary context.
     371             :      * We serialize that again and compare to the original serialization (to check that
     372             :      * serialize->deserialize->serialize is equivalent to serialize).
     373             :      * Then we run the update on the secondary context, and check that its new serialized
     374             :      * state matches the primary context after the update (to check that the simulation doesn't depend
     375             :      * on anything that's not serialized).
     376             :      *
     377             :      * In rejoin test mode, the secondary simulation is initialized from serialized data at turn N, then both
     378             :      * simulations run independantly while comparing their states each turn. This is way faster than a
     379             :      * complete serialization test and allows us to reproduce OOSes on rejoin.
     380             :      */
     381             : 
     382           0 :     const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow)
     383           0 :     const bool serializationTestHash = true; // set true to save and compare hash of state
     384             : 
     385           0 :     SerializationTestState primaryStateBefore;
     386           0 :     const ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface();
     387             : 
     388           0 :     const bool startRejoinTest = (int64_t) m_RejoinTestTurn == m_TurnNumber;
     389           0 :     if (startRejoinTest)
     390           0 :         m_TestingRejoin = true;
     391             : 
     392           0 :     if (m_EnableSerializationTest || m_TestingRejoin)
     393             :     {
     394           0 :         ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state));
     395             :         if (serializationTestDebugDump)
     396             :             ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false));
     397             :         if (serializationTestHash)
     398           0 :             ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false));
     399             :     }
     400             : 
     401           0 :     UpdateComponents(m_SimContext, turnLengthFixed, commands);
     402             : 
     403           0 :     if (m_EnableSerializationTest || startRejoinTest)
     404             :     {
     405           0 :         if (startRejoinTest)
     406           0 :             debug_printf("Initializing the secondary simulation\n");
     407             : 
     408           0 :         m_SecondaryTerrain = std::make_unique<CTerrain>();
     409             : 
     410           0 :         m_SecondaryContext = std::make_unique<CSimContext>();
     411           0 :         m_SecondaryContext->m_Terrain = m_SecondaryTerrain.get();
     412             : 
     413           0 :         m_SecondaryComponentManager = std::make_unique<CComponentManager>(*m_SecondaryContext, scriptInterface.GetContext());
     414           0 :         m_SecondaryComponentManager->LoadComponentTypes();
     415             : 
     416           0 :         m_SecondaryLoadedScripts = std::make_unique<std::set<VfsPath>>();
     417           0 :         ENSURE(LoadDefaultScripts(*m_SecondaryComponentManager, m_SecondaryLoadedScripts.get()));
     418           0 :         ResetComponentState(*m_SecondaryComponentManager, false, false);
     419             : 
     420           0 :         ScriptRequest rq(scriptInterface);
     421             : 
     422             :         // Load the trigger scripts after we have loaded the simulation.
     423             :         {
     424           0 :             ScriptRequest rq2(m_SecondaryComponentManager->GetScriptInterface());
     425           0 :             JS::RootedValue mapSettingsCloned(rq2.cx, Script::CloneValueFromOtherCompartment(m_SecondaryComponentManager->GetScriptInterface(), scriptInterface, m_MapSettings));
     426           0 :             ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts.get()));
     427             :         }
     428             : 
     429             :         // Load the map into the secondary simulation
     430             : 
     431           0 :         LDR_BeginRegistering();
     432           0 :         std::unique_ptr<CMapReader> mapReader = std::make_unique<CMapReader>();
     433             : 
     434           0 :         std::string mapType;
     435           0 :         Script::GetProperty(rq, m_InitAttributes, "mapType", mapType);
     436           0 :         if (mapType == "random")
     437             :         {
     438             :             // TODO: support random map scripts
     439           0 :             debug_warn(L"Serialization test mode does not support random maps");
     440             :         }
     441             :         else
     442             :         {
     443           0 :             std::wstring mapFile;
     444           0 :             Script::GetProperty(rq, m_InitAttributes, "map", mapFile);
     445             : 
     446           0 :             VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp");
     447           0 :             mapReader->LoadMap(mapfilename, *scriptInterface.GetContext(), JS::UndefinedHandleValue,
     448             :                 m_SecondaryTerrain.get(), NULL, NULL, NULL, NULL, NULL, NULL,
     449           0 :                 NULL, NULL, m_SecondaryContext.get(), INVALID_PLAYER, true); // throws exception on failure
     450             :         }
     451             : 
     452           0 :         LDR_EndRegistering();
     453           0 :         ENSURE(LDR_NonprogressiveLoad() == INFO::OK);
     454           0 :         ENSURE(m_SecondaryComponentManager->DeserializeState(primaryStateBefore.state));
     455             :     }
     456             : 
     457           0 :     if (m_EnableSerializationTest || m_TestingRejoin)
     458             :     {
     459           0 :         SerializationTestState secondaryStateBefore;
     460           0 :         ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateBefore.state));
     461             :         if (serializationTestDebugDump)
     462             :             ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateBefore.debug, false));
     463             :         if (serializationTestHash)
     464           0 :             ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateBefore.hash, false));
     465             : 
     466           0 :         if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() ||
     467           0 :             primaryStateBefore.hash != secondaryStateBefore.hash)
     468             :         {
     469           0 :             ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL);
     470             :         }
     471             : 
     472           0 :         SerializationTestState primaryStateAfter;
     473           0 :         ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state));
     474             :         if (serializationTestHash)
     475           0 :             ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false));
     476             : 
     477           0 :         UpdateComponents(*m_SecondaryContext, turnLengthFixed,
     478           0 :             CloneCommandsFromOtherCompartment(m_SecondaryComponentManager->GetScriptInterface(), scriptInterface, commands));
     479           0 :         SerializationTestState secondaryStateAfter;
     480           0 :         ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateAfter.state));
     481             :         if (serializationTestHash)
     482           0 :             ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateAfter.hash, false));
     483             : 
     484           0 :         if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() ||
     485           0 :             primaryStateAfter.hash != secondaryStateAfter.hash)
     486             :         {
     487             :             // Only do the (slow) dumping now we know we're going to need to report it
     488           0 :             ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false));
     489           0 :             ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateAfter.debug, false));
     490             : 
     491           0 :             ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter);
     492             :         }
     493             :     }
     494             : 
     495             :     // Run the GC occasionally
     496             :     // No delay because a lot of garbage accumulates in one turn and in non-visual replays there are
     497             :     // much more turns in the same time than in normal games.
     498             :     // Every 500 turns we run a shrinking GC, which decommits unused memory and frees all JIT code.
     499             :     // Based on testing, this seems to be a good compromise between memory usage and performance.
     500             :     // Also check the comment about gcPreserveCode in the ScriptInterface code and this forum topic:
     501             :     // http://www.wildfiregames.com/forum/index.php?showtopic=18466&p=300323
     502             :     //
     503             :     // (TODO: we ought to schedule this for a frame where we're not
     504             :     // running the sim update, to spread the load)
     505           0 :     if (m_TurnNumber % 500 == 0)
     506           0 :         scriptInterface.GetContext()->ShrinkingGC();
     507             :     else
     508           0 :         scriptInterface.GetContext()->MaybeIncrementalGC(0.0f);
     509             : 
     510           0 :     if (m_EnableOOSLog)
     511           0 :         DumpState();
     512             : 
     513           0 :     ++m_TurnNumber;
     514           0 : }
     515             : 
     516           0 : void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands)
     517             : {
     518             :     // TODO: the update process is pretty ugly, with lots of messages and dependencies
     519             :     // between different components. Ought to work out a nicer way to do this.
     520             : 
     521           0 :     CComponentManager& componentManager = simContext.GetComponentManager();
     522             : 
     523           0 :     CmpPtr<ICmpPathfinder> cmpPathfinder(simContext, SYSTEM_ENTITY);
     524           0 :     if (cmpPathfinder)
     525           0 :         cmpPathfinder->SendRequestedPaths();
     526             : 
     527             :     {
     528           0 :         PROFILE2("Sim - Update Start");
     529           0 :         CMessageTurnStart msgTurnStart;
     530           0 :         componentManager.BroadcastMessage(msgTurnStart);
     531             :     }
     532             : 
     533             : 
     534           0 :     CmpPtr<ICmpCommandQueue> cmpCommandQueue(simContext, SYSTEM_ENTITY);
     535           0 :     if (cmpCommandQueue)
     536           0 :         cmpCommandQueue->FlushTurn(commands);
     537             : 
     538             :     // Process newly generated move commands so the UI feels snappy
     539           0 :     if (cmpPathfinder)
     540             :     {
     541           0 :         cmpPathfinder->StartProcessingMoves(true);
     542           0 :         cmpPathfinder->SendRequestedPaths();
     543             :     }
     544             :     // Send all the update phases
     545             :     {
     546           0 :         PROFILE2("Sim - Update");
     547           0 :         CMessageUpdate msgUpdate(turnLengthFixed);
     548           0 :         componentManager.BroadcastMessage(msgUpdate);
     549             :     }
     550             : 
     551             :     {
     552           0 :         CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
     553           0 :         componentManager.BroadcastMessage(msgUpdate);
     554             :     }
     555             : 
     556             :     // Process move commands for formations (group proxy)
     557           0 :     if (cmpPathfinder)
     558             :     {
     559           0 :         cmpPathfinder->StartProcessingMoves(true);
     560           0 :         cmpPathfinder->SendRequestedPaths();
     561             :     }
     562             : 
     563             :     {
     564           0 :         PROFILE2("Sim - Motion Unit");
     565           0 :         CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
     566           0 :         componentManager.BroadcastMessage(msgUpdate);
     567             :     }
     568             :     {
     569           0 :         PROFILE2("Sim - Update Final");
     570           0 :         CMessageUpdate_Final msgUpdate(turnLengthFixed);
     571           0 :         componentManager.BroadcastMessage(msgUpdate);
     572             :     }
     573             : 
     574             :     // Clean up any entities destroyed during the simulation update
     575           0 :     componentManager.FlushDestroyedComponents();
     576             : 
     577             :     // Compute AI immediately at turn's end.
     578           0 :     CmpPtr<ICmpAIManager> cmpAIManager(simContext, SYSTEM_ENTITY);
     579           0 :     if (cmpAIManager)
     580             :     {
     581           0 :         cmpAIManager->StartComputation();
     582           0 :         cmpAIManager->PushCommands();
     583             :     }
     584             : 
     585             :     // Process all remaining moves
     586           0 :     if (cmpPathfinder)
     587             :     {
     588           0 :         cmpPathfinder->UpdateGrid();
     589           0 :         cmpPathfinder->StartProcessingMoves(false);
     590             :     }
     591           0 : }
     592             : 
     593           0 : void CSimulation2Impl::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
     594             : {
     595           0 :     PROFILE3("sim interpolate");
     596             : 
     597           0 :     m_LastFrameOffset = frameOffset;
     598             : 
     599           0 :     CMessageInterpolate msg(simFrameLength, frameOffset, realFrameLength);
     600           0 :     m_ComponentManager.BroadcastMessage(msg);
     601             : 
     602             :     // Clean up any entities destroyed during interpolate (e.g. local corpses)
     603           0 :     m_ComponentManager.FlushDestroyedComponents();
     604           0 : }
     605             : 
     606           0 : void CSimulation2Impl::DumpState()
     607             : {
     608           0 :     PROFILE("DumpState");
     609             : 
     610           0 :     std::stringstream name;\
     611           0 :     name << std::setw(5) << std::setfill('0') << m_TurnNumber << ".txt";
     612           0 :     const OsPath path = m_OOSLogPath / name.str();
     613           0 :     std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
     614             : 
     615           0 :     if (!DirectoryExists(m_OOSLogPath))
     616             :     {
     617           0 :         LOGWARNING("OOS-log directory %s was deleted, creating it again.", m_OOSLogPath.string8().c_str());
     618           0 :         CreateDirectories(m_OOSLogPath, 0700);
     619             :     }
     620             : 
     621           0 :     file << "State hash: " << std::hex;
     622           0 :     std::string hashRaw;
     623           0 :     m_ComponentManager.ComputeStateHash(hashRaw, false);
     624           0 :     for (size_t i = 0; i < hashRaw.size(); ++i)
     625           0 :         file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i];
     626           0 :     file << std::dec << "\n";
     627             : 
     628           0 :     file << "\n";
     629             : 
     630           0 :     m_ComponentManager.DumpDebugState(file, true);
     631             : 
     632           0 :     std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
     633           0 :     m_ComponentManager.SerializeState(binfile);
     634           0 : }
     635             : 
     636             : ////////////////////////////////////////////////////////////////
     637             : 
     638           4 : CSimulation2::CSimulation2(CUnitManager* unitManager, std::shared_ptr<ScriptContext> cx, CTerrain* terrain) :
     639           4 :     m(new CSimulation2Impl(unitManager, cx, terrain))
     640             : {
     641           4 : }
     642             : 
     643           8 : CSimulation2::~CSimulation2()
     644             : {
     645           4 :     delete m;
     646           4 : }
     647             : 
     648             : // Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
     649             : 
     650           0 : void CSimulation2::EnableSerializationTest()
     651             : {
     652           0 :     m->m_EnableSerializationTest = true;
     653           0 : }
     654             : 
     655           0 : void CSimulation2::EnableRejoinTest(int rejoinTestTurn)
     656             : {
     657           0 :     m->m_RejoinTestTurn = rejoinTestTurn;
     658           0 : }
     659             : 
     660           0 : void CSimulation2::EnableOOSLog()
     661             : {
     662           0 :     if (m->m_EnableOOSLog)
     663           0 :         return;
     664             : 
     665           0 :     m->m_EnableOOSLog = true;
     666           0 :     m->m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs");
     667             : 
     668           0 :     debug_printf("Writing ooslogs to %s\n", m->m_OOSLogPath.string8().c_str());
     669             : }
     670             : 
     671           6 : entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
     672             : {
     673           6 :     return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
     674             : }
     675             : 
     676           0 : entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
     677             : {
     678           0 :     return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
     679             : }
     680             : 
     681           0 : entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
     682             : {
     683           0 :     return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
     684             : }
     685             : 
     686           5 : void CSimulation2::DestroyEntity(entity_id_t ent)
     687             : {
     688           5 :     m->m_ComponentManager.DestroyComponentsSoon(ent);
     689           5 : }
     690             : 
     691           5 : void CSimulation2::FlushDestroyedEntities()
     692             : {
     693           5 :     m->m_ComponentManager.FlushDestroyedComponents();
     694           5 : }
     695             : 
     696          23 : IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const
     697             : {
     698          23 :     return m->m_ComponentManager.QueryInterface(ent, iid);
     699             : }
     700             : 
     701           0 : void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const
     702             : {
     703           0 :     m->m_ComponentManager.PostMessage(ent, msg);
     704           0 : }
     705             : 
     706           1 : void CSimulation2::BroadcastMessage(const CMessage& msg) const
     707             : {
     708           1 :     m->m_ComponentManager.BroadcastMessage(msg);
     709           1 : }
     710             : 
     711           0 : CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid)
     712             : {
     713           0 :     return m->m_ComponentManager.GetEntitiesWithInterface(iid);
     714             : }
     715             : 
     716           0 : const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid)
     717             : {
     718           0 :     return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid);
     719             : }
     720             : 
     721           0 : const CSimContext& CSimulation2::GetSimContext() const
     722             : {
     723           0 :     return m->m_SimContext;
     724             : }
     725             : 
     726           0 : ScriptInterface& CSimulation2::GetScriptInterface() const
     727             : {
     728           0 :     return m->m_ComponentManager.GetScriptInterface();
     729             : }
     730             : 
     731           0 : void CSimulation2::PreInitGame()
     732             : {
     733           0 :     ScriptRequest rq(GetScriptInterface());
     734           0 :     JS::RootedValue global(rq.cx, rq.globalValue());
     735           0 :     ScriptFunction::CallVoid(rq, global, "PreInitGame");
     736           0 : }
     737             : 
     738           0 : void CSimulation2::InitGame()
     739             : {
     740           0 :     ScriptRequest rq(GetScriptInterface());
     741           0 :     JS::RootedValue global(rq.cx, rq.globalValue());
     742             : 
     743           0 :     JS::RootedValue settings(rq.cx);
     744           0 :     JS::RootedValue tmpInitAttributes(rq.cx, GetInitAttributes());
     745           0 :     Script::GetProperty(rq, tmpInitAttributes, "settings", &settings);
     746             : 
     747           0 :     ScriptFunction::CallVoid(rq, global, "InitGame", settings);
     748           0 : }
     749             : 
     750           0 : void CSimulation2::Update(int turnLength)
     751             : {
     752           0 :     std::vector<SimulationCommand> commands;
     753           0 :     m->Update(turnLength, commands);
     754           0 : }
     755             : 
     756           0 : void CSimulation2::Update(int turnLength, const std::vector<SimulationCommand>& commands)
     757             : {
     758           0 :     m->Update(turnLength, commands);
     759           0 : }
     760             : 
     761           0 : void CSimulation2::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
     762             : {
     763           0 :     m->Interpolate(simFrameLength, frameOffset, realFrameLength);
     764           0 : }
     765             : 
     766           0 : void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
     767             : {
     768           0 :     PROFILE3("sim submit");
     769             : 
     770           0 :     CMessageRenderSubmit msg(collector, frustum, culling);
     771           0 :     m->m_ComponentManager.BroadcastMessage(msg);
     772           0 : }
     773             : 
     774           0 : float CSimulation2::GetLastFrameOffset() const
     775             : {
     776           0 :     return m->m_LastFrameOffset;
     777             : }
     778             : 
     779           3 : bool CSimulation2::LoadScripts(const VfsPath& path)
     780             : {
     781           3 :     return m->LoadScripts(m->m_ComponentManager, &m->m_LoadedScripts, path);
     782             : }
     783             : 
     784           0 : bool CSimulation2::LoadDefaultScripts()
     785             : {
     786           0 :     return m->LoadDefaultScripts(m->m_ComponentManager, &m->m_LoadedScripts);
     787             : }
     788             : 
     789           0 : void CSimulation2::SetStartupScript(const std::string& code)
     790             : {
     791           0 :     m->m_StartupScript = code;
     792           0 : }
     793             : 
     794           0 : const std::string& CSimulation2::GetStartupScript()
     795             : {
     796           0 :     return m->m_StartupScript;
     797             : }
     798             : 
     799           0 : void CSimulation2::SetInitAttributes(JS::HandleValue attribs)
     800             : {
     801           0 :     m->m_InitAttributes = attribs;
     802           0 : }
     803             : 
     804           0 : JS::Value CSimulation2::GetInitAttributes()
     805             : {
     806           0 :     return m->m_InitAttributes.get();
     807             : }
     808             : 
     809           0 : void CSimulation2::GetInitAttributes(JS::MutableHandleValue ret)
     810             : {
     811           0 :     ret.set(m->m_InitAttributes);
     812           0 : }
     813             : 
     814           0 : void CSimulation2::SetMapSettings(const std::string& settings)
     815             : {
     816           0 :     Script::ParseJSON(ScriptRequest(m->m_ComponentManager.GetScriptInterface()), settings, &m->m_MapSettings);
     817           0 : }
     818             : 
     819           0 : void CSimulation2::SetMapSettings(JS::HandleValue settings)
     820             : {
     821           0 :     m->m_MapSettings = settings;
     822             : 
     823           0 :     m->InitRNGSeedSimulation();
     824           0 :     m->InitRNGSeedAI();
     825           0 : }
     826             : 
     827           0 : std::string CSimulation2::GetMapSettingsString()
     828             : {
     829           0 :     return Script::StringifyJSON(ScriptRequest(m->m_ComponentManager.GetScriptInterface()), &m->m_MapSettings);
     830             : }
     831             : 
     832           0 : void CSimulation2::GetMapSettings(JS::MutableHandleValue ret)
     833             : {
     834           0 :     ret.set(m->m_MapSettings);
     835           0 : }
     836             : 
     837           0 : void CSimulation2::LoadPlayerSettings(bool newPlayers)
     838             : {
     839           0 :     ScriptRequest rq(GetScriptInterface());
     840           0 :     JS::RootedValue global(rq.cx, rq.globalValue());
     841           0 :     ScriptFunction::CallVoid(rq, global, "LoadPlayerSettings", m->m_MapSettings, newPlayers);
     842           0 : }
     843             : 
     844           0 : void CSimulation2::LoadMapSettings()
     845             : {
     846           0 :     ScriptRequest rq(GetScriptInterface());
     847             : 
     848           0 :     JS::RootedValue global(rq.cx, rq.globalValue());
     849             : 
     850             :     // Initialize here instead of in Update()
     851           0 :     ScriptFunction::CallVoid(rq, global, "LoadMapSettings", m->m_MapSettings);
     852             : 
     853           0 :     Script::FreezeObject(rq, m->m_InitAttributes, true);
     854           0 :     GetScriptInterface().SetGlobal("InitAttributes", m->m_InitAttributes, true, true, true);
     855             : 
     856           0 :     if (!m->m_StartupScript.empty())
     857           0 :         GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript);
     858             : 
     859             :     // Load the trigger scripts after we have loaded the simulation and the map.
     860           0 :     m->LoadTriggerScripts(m->m_ComponentManager, m->m_MapSettings, &m->m_LoadedScripts);
     861           0 : }
     862             : 
     863           0 : int CSimulation2::ProgressiveLoad()
     864             : {
     865           0 :     return m->ProgressiveLoad();
     866             : }
     867             : 
     868           5 : Status CSimulation2::ReloadChangedFile(const VfsPath& path)
     869             : {
     870           5 :     return m->ReloadChangedFile(path);
     871             : }
     872             : 
     873           3 : void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI)
     874             : {
     875           3 :     m->ResetState(skipScriptedComponents, skipAI);
     876           3 : }
     877             : 
     878           0 : bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick)
     879             : {
     880           0 :     return m->m_ComponentManager.ComputeStateHash(outHash, quick);
     881             : }
     882             : 
     883           0 : bool CSimulation2::DumpDebugState(std::ostream& stream)
     884             : {
     885           0 :     stream << "sim turn: " << m->m_TurnNumber << std::endl;
     886           0 :     return m->m_ComponentManager.DumpDebugState(stream, true);
     887             : }
     888             : 
     889           0 : bool CSimulation2::SerializeState(std::ostream& stream)
     890             : {
     891           0 :     return m->m_ComponentManager.SerializeState(stream);
     892             : }
     893             : 
     894           0 : bool CSimulation2::DeserializeState(std::istream& stream)
     895             : {
     896             :     // TODO: need to make sure the required SYSTEM_ENTITY components get constructed
     897           0 :     return m->m_ComponentManager.DeserializeState(stream);
     898             : }
     899             : 
     900           0 : void CSimulation2::ActivateRejoinTest(int turn)
     901             : {
     902           0 :     if (m->m_RejoinTestTurn != -1)
     903           0 :         return;
     904           0 :     LOGMESSAGERENDER("Rejoin test will activate in %i turns", turn - m->m_TurnNumber);
     905           0 :     m->m_RejoinTestTurn = turn;
     906             : }
     907             : 
     908           0 : std::string CSimulation2::GenerateSchema()
     909             : {
     910           0 :     return m->m_ComponentManager.GenerateSchema();
     911             : }
     912             : 
     913           0 : static std::vector<std::string> GetJSONData(const VfsPath& path)
     914             : {
     915           0 :     VfsPaths pathnames;
     916           0 :     Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
     917           0 :     if (ret != INFO::OK)
     918             :     {
     919             :         // Some error reading directory
     920             :         wchar_t error[200];
     921           0 :         LOGERROR("Error reading directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
     922           0 :         return std::vector<std::string>();
     923             :     }
     924             : 
     925           0 :     std::vector<std::string> data;
     926           0 :     for (const VfsPath& p : pathnames)
     927             :     {
     928             :         // Load JSON file
     929           0 :         CVFSFile file;
     930           0 :         PSRETURN loadStatus = file.Load(g_VFS, p);
     931           0 :         if (loadStatus != PSRETURN_OK)
     932             :         {
     933           0 :             LOGERROR("GetJSONData: Failed to load file '%s': %s", p.string8(), GetErrorString(loadStatus));
     934           0 :             continue;
     935             :         }
     936             : 
     937           0 :         data.push_back(file.DecodeUTF8()); // assume it's UTF-8
     938             :     }
     939             : 
     940           0 :     return data;
     941             : }
     942             : 
     943           0 : std::vector<std::string> CSimulation2::GetRMSData()
     944             : {
     945           0 :     return GetJSONData(L"maps/random/");
     946             : }
     947             : 
     948           0 : std::vector<std::string> CSimulation2::GetVictoryConditiondData()
     949             : {
     950           0 :     return GetJSONData(L"simulation/data/settings/victory_conditions/");
     951             : }
     952             : 
     953           0 : static std::string ReadJSON(const VfsPath& path)
     954             : {
     955           0 :     if (!VfsFileExists(path))
     956             :     {
     957           0 :         LOGERROR("File '%s' does not exist", path.string8());
     958           0 :         return std::string();
     959             :     }
     960             : 
     961             :     // Load JSON file
     962           0 :     CVFSFile file;
     963           0 :     PSRETURN ret = file.Load(g_VFS, path);
     964           0 :     if (ret != PSRETURN_OK)
     965             :     {
     966           0 :         LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret));
     967           0 :         return std::string();
     968             :     }
     969             : 
     970           0 :     return file.DecodeUTF8(); // assume it's UTF-8
     971             : }
     972             : 
     973           0 : std::string CSimulation2::GetPlayerDefaults()
     974             : {
     975           0 :     return ReadJSON(L"simulation/data/settings/player_defaults.json");
     976             : }
     977             : 
     978           0 : std::string CSimulation2::GetMapSizes()
     979             : {
     980           0 :     return ReadJSON(L"simulation/data/settings/map_sizes.json");
     981             : }
     982             : 
     983           0 : std::string CSimulation2::GetAIData()
     984             : {
     985           0 :     const ScriptInterface& scriptInterface = GetScriptInterface();
     986           0 :     ScriptRequest rq(scriptInterface);
     987           0 :     JS::RootedValue aiData(rq.cx, ICmpAIManager::GetAIs(scriptInterface));
     988             : 
     989             :     // Build single JSON string with array of AI data
     990           0 :     JS::RootedValue ais(rq.cx);
     991             : 
     992           0 :     if (!Script::CreateObject(rq, &ais, "AIData", aiData))
     993           0 :         return std::string();
     994             : 
     995           0 :     return Script::StringifyJSON(rq, &ais);
     996           3 : }

Generated by: LCOV version 1.13