Fix the removal of implicit conversions in libfmt 10 by using explicit casts.
[0ad.git] / source / simulation2 / Simulation2.cpp
blobf81dc67b1f2483e1e4c5cccd65c42f594d58406e
1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "Simulation2.h"
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"
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"
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"
50 #include <fstream>
51 #include <iomanip>
52 #include <memory>
54 class CSimulation2Impl
56 public:
57 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 m_MapSettings(cx->GetGeneralJSContext()), m_InitAttributes(cx->GetGeneralJSContext())
62 m_SimContext.m_UnitManager = unitManager;
63 m_SimContext.m_Terrain = terrain;
64 m_ComponentManager.LoadComponentTypes();
66 RegisterFileReloadFunc(ReloadChangedFileCB, this);
68 // Tests won't have config initialised
69 if (CConfigDB::IsInitialised())
71 CFG_GET_VAL("ooslog", m_EnableOOSLog);
72 CFG_GET_VAL("serializationtest", m_EnableSerializationTest);
73 CFG_GET_VAL("rejointest", m_RejoinTestTurn);
74 if (m_RejoinTestTurn < 0) // Handle bogus values of the arg
75 m_RejoinTestTurn = -1;
78 if (m_EnableOOSLog)
80 m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs");
81 debug_printf("Writing ooslogs to %s\n", m_OOSLogPath.string8().c_str());
85 ~CSimulation2Impl()
87 UnregisterFileReloadFunc(ReloadChangedFileCB, this);
90 void ResetState(bool skipScriptedComponents, bool skipAI)
92 m_DeltaTime = 0.0;
93 m_LastFrameOffset = 0.0f;
94 m_TurnNumber = 0;
95 ResetComponentState(m_ComponentManager, skipScriptedComponents, skipAI);
98 static void ResetComponentState(CComponentManager& componentManager, bool skipScriptedComponents, bool skipAI)
100 componentManager.ResetState();
101 componentManager.InitSystemEntity();
102 componentManager.AddSystemComponents(skipScriptedComponents, skipAI);
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);
110 static Status ReloadChangedFileCB(void* param, const VfsPath& path)
112 return static_cast<CSimulation2Impl*>(param)->ReloadChangedFile(path);
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);
120 void DumpState();
122 CSimContext m_SimContext;
123 CComponentManager m_ComponentManager;
124 double m_DeltaTime;
125 float m_LastFrameOffset;
127 std::string m_StartupScript;
128 JS::PersistentRootedValue m_InitAttributes;
129 JS::PersistentRootedValue m_MapSettings;
131 std::set<VfsPath> m_LoadedScripts;
133 uint32_t m_TurnNumber;
135 bool m_EnableOOSLog;
136 OsPath m_OOSLogPath;
138 // Functions and data for the serialization test mode: (see Update() for relevant comments)
140 bool m_EnableSerializationTest;
141 int m_RejoinTestTurn;
142 bool m_TestingRejoin;
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;
150 struct SerializationTestState
152 std::stringstream state;
153 std::stringstream debug;
154 std::string hash;
157 void DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix);
159 void ReportSerializationFailure(
160 SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
161 SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter);
163 void InitRNGSeedSimulation();
164 void InitRNGSeedAI();
166 static std::vector<SimulationCommand> CloneCommandsFromOtherCompartment(const ScriptInterface& newScript, const ScriptInterface& oldScript,
167 const std::vector<SimulationCommand>& commands)
169 std::vector<SimulationCommand> newCommands;
170 newCommands.reserve(commands.size());
172 ScriptRequest rqNew(newScript);
173 for (const SimulationCommand& command : commands)
175 JS::RootedValue tmpCommand(rqNew.cx, Script::CloneValueFromOtherCompartment(newScript, oldScript, command.data));
176 Script::FreezeObject(rqNew, tmpCommand, true);
177 SimulationCommand cmd(command.player, rqNew.cx, tmpCommand);
178 newCommands.emplace_back(std::move(cmd));
180 return newCommands;
184 bool CSimulation2Impl::LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts)
186 return (
187 LoadScripts(componentManager, loadedScripts, L"simulation/components/interfaces/") &&
188 LoadScripts(componentManager, loadedScripts, L"simulation/helpers/") &&
189 LoadScripts(componentManager, loadedScripts, L"simulation/components/")
193 bool CSimulation2Impl::LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path)
195 VfsPaths pathnames;
196 if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
197 return false;
199 bool ok = true;
200 for (const VfsPath& scriptPath : pathnames)
202 if (loadedScripts)
203 loadedScripts->insert(scriptPath);
204 LOGMESSAGE("Loading simulation script '%s'", scriptPath.string8());
205 if (!componentManager.LoadScript(scriptPath))
206 ok = false;
208 return ok;
211 bool CSimulation2Impl::LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set<VfsPath>* loadedScripts)
213 bool ok = true;
214 ScriptRequest rq(componentManager.GetScriptInterface());
215 if (Script::HasProperty(rq, mapSettings, "TriggerScripts"))
217 std::vector<std::string> scriptNames;
218 Script::GetProperty(rq, mapSettings, "TriggerScripts", scriptNames);
219 for (const std::string& triggerScript : scriptNames)
221 std::string scriptName = "maps/" + triggerScript;
222 if (loadedScripts)
224 if (loadedScripts->find(scriptName) != loadedScripts->end())
225 continue;
226 loadedScripts->insert(scriptName);
228 LOGMESSAGE("Loading trigger script '%s'", scriptName.c_str());
229 if (!componentManager.LoadScript(scriptName.data()))
230 ok = false;
233 return ok;
236 Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
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 if (m_LoadedScripts.find(path) == m_LoadedScripts.end())
241 return INFO::OK;
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 if (!VfsFileExists(path))
246 return INFO::OK;
248 LOGMESSAGE("Reloading simulation script '%s'", path.string8());
249 if (!m_ComponentManager.LoadScript(path, true))
250 return ERR::FAIL;
252 return INFO::OK;
255 int CSimulation2Impl::ProgressiveLoad()
257 // yield after this time is reached. balances increased progress bar
258 // smoothness vs. slowing down loading.
259 const double end_time = timer_Time() + 200e-3;
261 int ret;
265 bool progressed = false;
266 int total = 0;
267 int progress = 0;
269 CMessageProgressiveLoad msg(&progressed, &total, &progress);
271 m_ComponentManager.BroadcastMessage(msg);
273 if (!progressed || total == 0)
274 return 0; // we have nothing left to load
276 ret = Clamp(100*progress / total, 1, 100);
278 while (timer_Time() < end_time);
280 return ret;
283 void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix)
285 if (!state.hash.empty())
287 std::ofstream file (OsString(path / (L"hash." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
288 file << Hexify(state.hash);
291 if (!state.debug.str().empty())
293 std::ofstream file (OsString(path / (L"debug." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
294 file << state.debug.str();
297 if (!state.state.str().empty())
299 std::ofstream file (OsString(path / (L"state." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
300 file << state.state.str();
304 void CSimulation2Impl::ReportSerializationFailure(
305 SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
306 SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter)
308 const OsPath path = createDateIndexSubdirectory(psLogDir() / "serializationtest");
309 debug_printf("Writing serializationtest-data to %s\n", path.string8().c_str());
311 // Clean up obsolete files from previous runs
312 wunlink(path / "hash.before.a");
313 wunlink(path / "hash.before.b");
314 wunlink(path / "debug.before.a");
315 wunlink(path / "debug.before.b");
316 wunlink(path / "state.before.a");
317 wunlink(path / "state.before.b");
318 wunlink(path / "hash.after.a");
319 wunlink(path / "hash.after.b");
320 wunlink(path / "debug.after.a");
321 wunlink(path / "debug.after.b");
322 wunlink(path / "state.after.a");
323 wunlink(path / "state.after.b");
325 if (primaryStateBefore)
326 DumpSerializationTestState(*primaryStateBefore, path, L"before.a");
327 if (primaryStateAfter)
328 DumpSerializationTestState(*primaryStateAfter, path, L"after.a");
329 if (secondaryStateBefore)
330 DumpSerializationTestState(*secondaryStateBefore, path, L"before.b");
331 if (secondaryStateAfter)
332 DumpSerializationTestState(*secondaryStateAfter, path, L"after.b");
334 debug_warn(L"Serialization test failure");
337 void CSimulation2Impl::InitRNGSeedSimulation()
339 u32 seed = 0;
340 ScriptRequest rq(m_ComponentManager.GetScriptInterface());
341 if (!Script::HasProperty(rq, m_MapSettings, "Seed") ||
342 !Script::GetProperty(rq, m_MapSettings, "Seed", seed))
343 LOGWARNING("CSimulation2Impl::InitRNGSeedSimulation: No seed value specified - using %d", seed);
345 m_ComponentManager.SetRNGSeed(seed);
348 void CSimulation2Impl::InitRNGSeedAI()
350 u32 seed = 0;
351 ScriptRequest rq(m_ComponentManager.GetScriptInterface());
352 if (!Script::HasProperty(rq, m_MapSettings, "AISeed") ||
353 !Script::GetProperty(rq, m_MapSettings, "AISeed", seed))
354 LOGWARNING("CSimulation2Impl::InitRNGSeedAI: No seed value specified - using %d", seed);
356 CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY);
357 if (cmpAIManager)
358 cmpAIManager->SetRNGSeed(seed);
361 void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
363 PROFILE3("sim update");
364 PROFILE2_ATTR("turn %d", (int)m_TurnNumber);
366 fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
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).
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.
382 const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow)
383 const bool serializationTestHash = true; // set true to save and compare hash of state
385 SerializationTestState primaryStateBefore;
386 const ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface();
388 const bool startRejoinTest = (int64_t) m_RejoinTestTurn == m_TurnNumber;
389 if (startRejoinTest)
390 m_TestingRejoin = true;
392 if (m_EnableSerializationTest || m_TestingRejoin)
394 ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state));
395 if (serializationTestDebugDump)
396 ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false));
397 if (serializationTestHash)
398 ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false));
401 UpdateComponents(m_SimContext, turnLengthFixed, commands);
403 if (m_EnableSerializationTest || startRejoinTest)
405 if (startRejoinTest)
406 debug_printf("Initializing the secondary simulation\n");
408 m_SecondaryTerrain = std::make_unique<CTerrain>();
410 m_SecondaryContext = std::make_unique<CSimContext>();
411 m_SecondaryContext->m_Terrain = m_SecondaryTerrain.get();
413 m_SecondaryComponentManager = std::make_unique<CComponentManager>(*m_SecondaryContext, scriptInterface.GetContext());
414 m_SecondaryComponentManager->LoadComponentTypes();
416 m_SecondaryLoadedScripts = std::make_unique<std::set<VfsPath>>();
417 ENSURE(LoadDefaultScripts(*m_SecondaryComponentManager, m_SecondaryLoadedScripts.get()));
418 ResetComponentState(*m_SecondaryComponentManager, false, false);
420 ScriptRequest rq(scriptInterface);
422 // Load the trigger scripts after we have loaded the simulation.
424 ScriptRequest rq2(m_SecondaryComponentManager->GetScriptInterface());
425 JS::RootedValue mapSettingsCloned(rq2.cx, Script::CloneValueFromOtherCompartment(m_SecondaryComponentManager->GetScriptInterface(), scriptInterface, m_MapSettings));
426 ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts.get()));
429 // Load the map into the secondary simulation
431 LDR_BeginRegistering();
432 std::unique_ptr<CMapReader> mapReader = std::make_unique<CMapReader>();
434 std::string mapType;
435 Script::GetProperty(rq, m_InitAttributes, "mapType", mapType);
436 if (mapType == "random")
438 // TODO: support random map scripts
439 debug_warn(L"Serialization test mode does not support random maps");
441 else
443 std::wstring mapFile;
444 Script::GetProperty(rq, m_InitAttributes, "map", mapFile);
446 VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp");
447 mapReader->LoadMap(mapfilename, *scriptInterface.GetContext(), JS::UndefinedHandleValue,
448 m_SecondaryTerrain.get(), NULL, NULL, NULL, NULL, NULL, NULL,
449 NULL, NULL, m_SecondaryContext.get(), INVALID_PLAYER, true); // throws exception on failure
452 LDR_EndRegistering();
453 ENSURE(LDR_NonprogressiveLoad() == INFO::OK);
454 ENSURE(m_SecondaryComponentManager->DeserializeState(primaryStateBefore.state));
457 if (m_EnableSerializationTest || m_TestingRejoin)
459 SerializationTestState secondaryStateBefore;
460 ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateBefore.state));
461 if (serializationTestDebugDump)
462 ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateBefore.debug, false));
463 if (serializationTestHash)
464 ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateBefore.hash, false));
466 if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() ||
467 primaryStateBefore.hash != secondaryStateBefore.hash)
469 ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL);
472 SerializationTestState primaryStateAfter;
473 ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state));
474 if (serializationTestHash)
475 ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false));
477 UpdateComponents(*m_SecondaryContext, turnLengthFixed,
478 CloneCommandsFromOtherCompartment(m_SecondaryComponentManager->GetScriptInterface(), scriptInterface, commands));
479 SerializationTestState secondaryStateAfter;
480 ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateAfter.state));
481 if (serializationTestHash)
482 ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateAfter.hash, false));
484 if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() ||
485 primaryStateAfter.hash != secondaryStateAfter.hash)
487 // Only do the (slow) dumping now we know we're going to need to report it
488 ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false));
489 ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateAfter.debug, false));
491 ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter);
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
503 // (TODO: we ought to schedule this for a frame where we're not
504 // running the sim update, to spread the load)
505 if (m_TurnNumber % 500 == 0)
506 scriptInterface.GetContext()->ShrinkingGC();
507 else
508 scriptInterface.GetContext()->MaybeIncrementalGC(0.0f);
510 if (m_EnableOOSLog)
511 DumpState();
513 ++m_TurnNumber;
516 void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands)
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.
521 CComponentManager& componentManager = simContext.GetComponentManager();
523 CmpPtr<ICmpPathfinder> cmpPathfinder(simContext, SYSTEM_ENTITY);
524 if (cmpPathfinder)
525 cmpPathfinder->SendRequestedPaths();
528 PROFILE2("Sim - Update Start");
529 CMessageTurnStart msgTurnStart;
530 componentManager.BroadcastMessage(msgTurnStart);
534 CmpPtr<ICmpCommandQueue> cmpCommandQueue(simContext, SYSTEM_ENTITY);
535 if (cmpCommandQueue)
536 cmpCommandQueue->FlushTurn(commands);
538 // Process newly generated move commands so the UI feels snappy
539 if (cmpPathfinder)
541 cmpPathfinder->StartProcessingMoves(true);
542 cmpPathfinder->SendRequestedPaths();
544 // Send all the update phases
546 PROFILE2("Sim - Update");
547 CMessageUpdate msgUpdate(turnLengthFixed);
548 componentManager.BroadcastMessage(msgUpdate);
552 CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
553 componentManager.BroadcastMessage(msgUpdate);
556 // Process move commands for formations (group proxy)
557 if (cmpPathfinder)
559 cmpPathfinder->StartProcessingMoves(true);
560 cmpPathfinder->SendRequestedPaths();
564 PROFILE2("Sim - Motion Unit");
565 CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
566 componentManager.BroadcastMessage(msgUpdate);
569 PROFILE2("Sim - Update Final");
570 CMessageUpdate_Final msgUpdate(turnLengthFixed);
571 componentManager.BroadcastMessage(msgUpdate);
574 // Clean up any entities destroyed during the simulation update
575 componentManager.FlushDestroyedComponents();
577 // Compute AI immediately at turn's end.
578 CmpPtr<ICmpAIManager> cmpAIManager(simContext, SYSTEM_ENTITY);
579 if (cmpAIManager)
581 cmpAIManager->StartComputation();
582 cmpAIManager->PushCommands();
585 // Process all remaining moves
586 if (cmpPathfinder)
588 cmpPathfinder->UpdateGrid();
589 cmpPathfinder->StartProcessingMoves(false);
593 void CSimulation2Impl::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
595 PROFILE3("sim interpolate");
597 m_LastFrameOffset = frameOffset;
599 CMessageInterpolate msg(simFrameLength, frameOffset, realFrameLength);
600 m_ComponentManager.BroadcastMessage(msg);
602 // Clean up any entities destroyed during interpolate (e.g. local corpses)
603 m_ComponentManager.FlushDestroyedComponents();
606 void CSimulation2Impl::DumpState()
608 PROFILE("DumpState");
610 std::stringstream name;\
611 name << std::setw(5) << std::setfill('0') << m_TurnNumber << ".txt";
612 const OsPath path = m_OOSLogPath / name.str();
613 std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
615 if (!DirectoryExists(m_OOSLogPath))
617 LOGWARNING("OOS-log directory %s was deleted, creating it again.", m_OOSLogPath.string8().c_str());
618 CreateDirectories(m_OOSLogPath, 0700);
621 file << "State hash: " << std::hex;
622 std::string hashRaw;
623 m_ComponentManager.ComputeStateHash(hashRaw, false);
624 for (size_t i = 0; i < hashRaw.size(); ++i)
625 file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i];
626 file << std::dec << "\n";
628 file << "\n";
630 m_ComponentManager.DumpDebugState(file, true);
632 std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
633 m_ComponentManager.SerializeState(binfile);
636 ////////////////////////////////////////////////////////////////
638 CSimulation2::CSimulation2(CUnitManager* unitManager, std::shared_ptr<ScriptContext> cx, CTerrain* terrain) :
639 m(new CSimulation2Impl(unitManager, cx, terrain))
643 CSimulation2::~CSimulation2()
645 delete m;
648 // Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
650 void CSimulation2::EnableSerializationTest()
652 m->m_EnableSerializationTest = true;
655 void CSimulation2::EnableRejoinTest(int rejoinTestTurn)
657 m->m_RejoinTestTurn = rejoinTestTurn;
660 void CSimulation2::EnableOOSLog()
662 if (m->m_EnableOOSLog)
663 return;
665 m->m_EnableOOSLog = true;
666 m->m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs");
668 debug_printf("Writing ooslogs to %s\n", m->m_OOSLogPath.string8().c_str());
671 entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
673 return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
676 entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
678 return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
681 entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
683 return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
686 void CSimulation2::DestroyEntity(entity_id_t ent)
688 m->m_ComponentManager.DestroyComponentsSoon(ent);
691 void CSimulation2::FlushDestroyedEntities()
693 m->m_ComponentManager.FlushDestroyedComponents();
696 IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const
698 return m->m_ComponentManager.QueryInterface(ent, iid);
701 void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const
703 m->m_ComponentManager.PostMessage(ent, msg);
706 void CSimulation2::BroadcastMessage(const CMessage& msg) const
708 m->m_ComponentManager.BroadcastMessage(msg);
711 CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid)
713 return m->m_ComponentManager.GetEntitiesWithInterface(iid);
716 const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid)
718 return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid);
721 const CSimContext& CSimulation2::GetSimContext() const
723 return m->m_SimContext;
726 ScriptInterface& CSimulation2::GetScriptInterface() const
728 return m->m_ComponentManager.GetScriptInterface();
731 void CSimulation2::PreInitGame()
733 ScriptRequest rq(GetScriptInterface());
734 JS::RootedValue global(rq.cx, rq.globalValue());
735 ScriptFunction::CallVoid(rq, global, "PreInitGame");
738 void CSimulation2::InitGame()
740 ScriptRequest rq(GetScriptInterface());
741 JS::RootedValue global(rq.cx, rq.globalValue());
743 JS::RootedValue settings(rq.cx);
744 JS::RootedValue tmpInitAttributes(rq.cx, GetInitAttributes());
745 Script::GetProperty(rq, tmpInitAttributes, "settings", &settings);
747 ScriptFunction::CallVoid(rq, global, "InitGame", settings);
750 void CSimulation2::Update(int turnLength)
752 std::vector<SimulationCommand> commands;
753 m->Update(turnLength, commands);
756 void CSimulation2::Update(int turnLength, const std::vector<SimulationCommand>& commands)
758 m->Update(turnLength, commands);
761 void CSimulation2::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
763 m->Interpolate(simFrameLength, frameOffset, realFrameLength);
766 void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
768 PROFILE3("sim submit");
770 CMessageRenderSubmit msg(collector, frustum, culling);
771 m->m_ComponentManager.BroadcastMessage(msg);
774 float CSimulation2::GetLastFrameOffset() const
776 return m->m_LastFrameOffset;
779 bool CSimulation2::LoadScripts(const VfsPath& path)
781 return m->LoadScripts(m->m_ComponentManager, &m->m_LoadedScripts, path);
784 bool CSimulation2::LoadDefaultScripts()
786 return m->LoadDefaultScripts(m->m_ComponentManager, &m->m_LoadedScripts);
789 void CSimulation2::SetStartupScript(const std::string& code)
791 m->m_StartupScript = code;
794 const std::string& CSimulation2::GetStartupScript()
796 return m->m_StartupScript;
799 void CSimulation2::SetInitAttributes(JS::HandleValue attribs)
801 m->m_InitAttributes = attribs;
804 JS::Value CSimulation2::GetInitAttributes()
806 return m->m_InitAttributes.get();
809 void CSimulation2::GetInitAttributes(JS::MutableHandleValue ret)
811 ret.set(m->m_InitAttributes);
814 void CSimulation2::SetMapSettings(const std::string& settings)
816 Script::ParseJSON(ScriptRequest(m->m_ComponentManager.GetScriptInterface()), settings, &m->m_MapSettings);
819 void CSimulation2::SetMapSettings(JS::HandleValue settings)
821 m->m_MapSettings = settings;
823 m->InitRNGSeedSimulation();
824 m->InitRNGSeedAI();
827 std::string CSimulation2::GetMapSettingsString()
829 return Script::StringifyJSON(ScriptRequest(m->m_ComponentManager.GetScriptInterface()), &m->m_MapSettings);
832 void CSimulation2::GetMapSettings(JS::MutableHandleValue ret)
834 ret.set(m->m_MapSettings);
837 void CSimulation2::LoadPlayerSettings(bool newPlayers)
839 ScriptRequest rq(GetScriptInterface());
840 JS::RootedValue global(rq.cx, rq.globalValue());
841 ScriptFunction::CallVoid(rq, global, "LoadPlayerSettings", m->m_MapSettings, newPlayers);
844 void CSimulation2::LoadMapSettings()
846 ScriptRequest rq(GetScriptInterface());
848 JS::RootedValue global(rq.cx, rq.globalValue());
850 // Initialize here instead of in Update()
851 ScriptFunction::CallVoid(rq, global, "LoadMapSettings", m->m_MapSettings);
853 Script::FreezeObject(rq, m->m_InitAttributes, true);
854 GetScriptInterface().SetGlobal("InitAttributes", m->m_InitAttributes, true, true, true);
856 if (!m->m_StartupScript.empty())
857 GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript);
859 // Load the trigger scripts after we have loaded the simulation and the map.
860 m->LoadTriggerScripts(m->m_ComponentManager, m->m_MapSettings, &m->m_LoadedScripts);
863 int CSimulation2::ProgressiveLoad()
865 return m->ProgressiveLoad();
868 Status CSimulation2::ReloadChangedFile(const VfsPath& path)
870 return m->ReloadChangedFile(path);
873 void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI)
875 m->ResetState(skipScriptedComponents, skipAI);
878 bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick)
880 return m->m_ComponentManager.ComputeStateHash(outHash, quick);
883 bool CSimulation2::DumpDebugState(std::ostream& stream)
885 stream << "sim turn: " << m->m_TurnNumber << std::endl;
886 return m->m_ComponentManager.DumpDebugState(stream, true);
889 bool CSimulation2::SerializeState(std::ostream& stream)
891 return m->m_ComponentManager.SerializeState(stream);
894 bool CSimulation2::DeserializeState(std::istream& stream)
896 // TODO: need to make sure the required SYSTEM_ENTITY components get constructed
897 return m->m_ComponentManager.DeserializeState(stream);
900 void CSimulation2::ActivateRejoinTest(int turn)
902 if (m->m_RejoinTestTurn != -1)
903 return;
904 LOGMESSAGERENDER("Rejoin test will activate in %i turns", turn - m->m_TurnNumber);
905 m->m_RejoinTestTurn = turn;
908 std::string CSimulation2::GenerateSchema()
910 return m->m_ComponentManager.GenerateSchema();
913 static std::vector<std::string> GetJSONData(const VfsPath& path)
915 VfsPaths pathnames;
916 Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
917 if (ret != INFO::OK)
919 // Some error reading directory
920 wchar_t error[200];
921 LOGERROR("Error reading directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
922 return std::vector<std::string>();
925 std::vector<std::string> data;
926 for (const VfsPath& p : pathnames)
928 // Load JSON file
929 CVFSFile file;
930 PSRETURN loadStatus = file.Load(g_VFS, p);
931 if (loadStatus != PSRETURN_OK)
933 LOGERROR("GetJSONData: Failed to load file '%s': %s", p.string8(), GetErrorString(loadStatus));
934 continue;
937 data.push_back(file.DecodeUTF8()); // assume it's UTF-8
940 return data;
943 std::vector<std::string> CSimulation2::GetRMSData()
945 return GetJSONData(L"maps/random/");
948 std::vector<std::string> CSimulation2::GetVictoryConditiondData()
950 return GetJSONData(L"simulation/data/settings/victory_conditions/");
953 static std::string ReadJSON(const VfsPath& path)
955 if (!VfsFileExists(path))
957 LOGERROR("File '%s' does not exist", path.string8());
958 return std::string();
961 // Load JSON file
962 CVFSFile file;
963 PSRETURN ret = file.Load(g_VFS, path);
964 if (ret != PSRETURN_OK)
966 LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret));
967 return std::string();
970 return file.DecodeUTF8(); // assume it's UTF-8
973 std::string CSimulation2::GetPlayerDefaults()
975 return ReadJSON(L"simulation/data/settings/player_defaults.json");
978 std::string CSimulation2::GetMapSizes()
980 return ReadJSON(L"simulation/data/settings/map_sizes.json");
983 std::string CSimulation2::GetAIData()
985 const ScriptInterface& scriptInterface = GetScriptInterface();
986 ScriptRequest rq(scriptInterface);
987 JS::RootedValue aiData(rq.cx, ICmpAIManager::GetAIs(scriptInterface));
989 // Build single JSON string with array of AI data
990 JS::RootedValue ais(rq.cx);
992 if (!Script::CreateObject(rq, &ais, "AIData", aiData))
993 return std::string();
995 return Script::StringifyJSON(rq, &ais);