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"
48 #include "ps/XML/Xeromyces.h"
54 class CSimulation2Impl
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;
80 m_OOSLogPath
= createDateIndexSubdirectory(psLogDir() / "oos_logs");
81 debug_printf("Writing ooslogs to %s\n", m_OOSLogPath
.string8().c_str());
87 UnregisterFileReloadFunc(ReloadChangedFileCB
, this);
90 void ResetState(bool skipScriptedComponents
, bool skipAI
)
93 m_LastFrameOffset
= 0.0f
;
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
);
122 CSimContext m_SimContext
;
123 CComponentManager m_ComponentManager
;
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
;
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
;
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
));
184 bool CSimulation2Impl::LoadDefaultScripts(CComponentManager
& componentManager
, std::set
<VfsPath
>* loadedScripts
)
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
)
196 if (vfs::GetPathnames(g_VFS
, path
, L
"*.js", pathnames
) < 0)
200 for (const VfsPath
& scriptPath
: pathnames
)
203 loadedScripts
->insert(scriptPath
);
204 LOGMESSAGE("Loading simulation script '%s'", scriptPath
.string8());
205 if (!componentManager
.LoadScript(scriptPath
))
211 bool CSimulation2Impl::LoadTriggerScripts(CComponentManager
& componentManager
, JS::HandleValue mapSettings
, std::set
<VfsPath
>* loadedScripts
)
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
;
224 if (loadedScripts
->find(scriptName
) != loadedScripts
->end())
226 loadedScripts
->insert(scriptName
);
228 LOGMESSAGE("Loading trigger script '%s'", scriptName
.c_str());
229 if (!componentManager
.LoadScript(scriptName
.data()))
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())
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
))
248 LOGMESSAGE("Reloading simulation script '%s'", path
.string8());
249 if (!m_ComponentManager
.LoadScript(path
, true))
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;
265 bool progressed
= false;
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
);
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()
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()
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
);
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
;
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
)
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
>();
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");
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();
508 scriptInterface
.GetContext()->MaybeIncrementalGC(0.0f
);
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
);
525 cmpPathfinder
->SendRequestedPaths();
528 PROFILE2("Sim - Update Start");
529 CMessageTurnStart msgTurnStart
;
530 componentManager
.BroadcastMessage(msgTurnStart
);
534 CmpPtr
<ICmpCommandQueue
> cmpCommandQueue(simContext
, SYSTEM_ENTITY
);
536 cmpCommandQueue
->FlushTurn(commands
);
538 // Process newly generated move commands so the UI feels snappy
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)
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
);
581 cmpAIManager
->StartComputation();
582 cmpAIManager
->PushCommands();
585 // Process all remaining moves
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
;
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";
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()
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
)
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();
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)
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
)
916 Status ret
= vfs::GetPathnames(g_VFS
, path
, L
"*.json", pathnames
);
919 // Some error reading directory
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
)
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
));
937 data
.push_back(file
.DecodeUTF8()); // assume it's UTF-8
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();
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
);