void CSimulation2Impl::DumpState() { PROFILE("DumpState"); std::stringstream pid; pid << getpid(); std::stringstream name;\ name << std::setw(5) << std::setfill('0') << m_TurnNumber << ".txt"; OsPath path = psLogDir() / "sim_log" / pid.str() / name.str(); CreateDirectories(path.Parent(), 0700); std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); file << "State hash: " << std::hex; std::string hashRaw; m_ComponentManager.ComputeStateHash(hashRaw, false); for (size_t i = 0; i < hashRaw.size(); ++i) file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i]; file << std::dec << "\n"; file << "\n"; m_ComponentManager.DumpDebugState(file, true); std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); m_ComponentManager.SerializeState(binfile); }
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands) { PROFILE3("sim update"); PROFILE2_ATTR("turn %d", (int)m_TurnNumber); fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; /* * In serialization test mode, we save the original (primary) simulation state before each turn update. * We run the update, then load the saved state into a secondary context. * We serialize that again and compare to the original serialization (to check that * serialize->deserialize->serialize is equivalent to serialize). * Then we run the update on the secondary context, and check that its new serialized * state matches the primary context after the update (to check that the simulation doesn't depend * on anything that's not serialized). */ const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow) const bool serializationTestHash = true; // set true to save and compare hash of state SerializationTestState primaryStateBefore; if (m_EnableSerializationTest) { ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false)); } UpdateComponents(m_SimContext, turnLengthFixed, commands); if (m_EnableSerializationTest) { // Initialise the secondary simulation CTerrain secondaryTerrain; CSimContext secondaryContext; secondaryContext.m_Terrain = &secondaryTerrain; CComponentManager secondaryComponentManager(secondaryContext, m_ComponentManager.GetScriptInterface().GetRuntime()); secondaryComponentManager.LoadComponentTypes(); ENSURE(LoadDefaultScripts(secondaryComponentManager, NULL)); ResetComponentState(secondaryComponentManager, false, false); // Load the map into the secondary simulation LDR_BeginRegistering(); CMapReader* mapReader = new CMapReader; // automatically deletes itself // TODO: this duplicates CWorld::RegisterInit and could probably be cleaned up a bit std::string mapType; m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "mapType", mapType); if (mapType == "random") { // TODO: support random map scripts debug_warn(L"Serialization test mode only supports scenarios"); } else { std::wstring mapFile; m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "map", mapFile); VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp"); mapReader->LoadMap(mapfilename, CScriptValRooted(), &secondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &secondaryContext, INVALID_PLAYER, true); // throws exception on failure } LDR_EndRegistering(); ENSURE(LDR_NonprogressiveLoad() == INFO::OK); ENSURE(secondaryComponentManager.DeserializeState(primaryStateBefore.state)); SerializationTestState secondaryStateBefore; ENSURE(secondaryComponentManager.SerializeState(secondaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateBefore.hash, false)); if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() || primaryStateBefore.hash != secondaryStateBefore.hash) { ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL); } SerializationTestState primaryStateAfter; ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false)); UpdateComponents(secondaryContext, turnLengthFixed, CloneCommandsFromOtherContext(m_ComponentManager.GetScriptInterface(), secondaryComponentManager.GetScriptInterface(), commands)); SerializationTestState secondaryStateAfter; ENSURE(secondaryComponentManager.SerializeState(secondaryStateAfter.state)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false)); if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() || primaryStateAfter.hash != secondaryStateAfter.hash) { // Only do the (slow) dumping now we know we're going to need to report it ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false)); ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateAfter.debug, false)); ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter); } } // if (m_TurnNumber == 0) // m_ComponentManager.GetScriptInterface().DumpHeap(); // Run the GC occasionally // (TODO: we ought to schedule this for a frame where we're not // running the sim update, to spread the load) if (m_TurnNumber % 1 == 0) m_ComponentManager.GetScriptInterface().MaybeIncrementalRuntimeGC(); if (m_EnableOOSLog) DumpState(); // Start computing AI for the next turn CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->StartComputation(); ++m_TurnNumber; }
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands) { PROFILE3("sim update"); PROFILE2_ATTR("turn %d", (int)m_TurnNumber); fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; /* * In serialization test mode, we save the original (primary) simulation state before each turn update. * We run the update, then load the saved state into a secondary context. * We serialize that again and compare to the original serialization (to check that * serialize->deserialize->serialize is equivalent to serialize). * Then we run the update on the secondary context, and check that its new serialized * state matches the primary context after the update (to check that the simulation doesn't depend * on anything that's not serialized). */ const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow) const bool serializationTestHash = true; // set true to save and compare hash of state SerializationTestState primaryStateBefore; ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface(); if (m_EnableSerializationTest) { ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false)); } UpdateComponents(m_SimContext, turnLengthFixed, commands); if (m_EnableSerializationTest) { // Initialise the secondary simulation CTerrain secondaryTerrain; CSimContext secondaryContext; secondaryContext.m_Terrain = &secondaryTerrain; CComponentManager secondaryComponentManager(secondaryContext, scriptInterface.GetRuntime()); secondaryComponentManager.LoadComponentTypes(); std::set<VfsPath> secondaryLoadedScripts; ENSURE(LoadDefaultScripts(secondaryComponentManager, &secondaryLoadedScripts)); ResetComponentState(secondaryComponentManager, false, false); // Load the trigger scripts after we have loaded the simulation. { JSContext* cx2 = secondaryComponentManager.GetScriptInterface().GetContext(); JSAutoRequest rq2(cx2); JS::RootedValue mapSettingsCloned(cx2, secondaryComponentManager.GetScriptInterface().CloneValueFromOtherContext( scriptInterface, m_MapSettings)); ENSURE(LoadTriggerScripts(secondaryComponentManager, mapSettingsCloned, &secondaryLoadedScripts)); } // Load the map into the secondary simulation LDR_BeginRegistering(); CMapReader* mapReader = new CMapReader; // automatically deletes itself std::string mapType; scriptInterface.GetProperty(m_InitAttributes, "mapType", mapType); if (mapType == "random") { // TODO: support random map scripts debug_warn(L"Serialization test mode does not support random maps"); } else { std::wstring mapFile; scriptInterface.GetProperty(m_InitAttributes, "map", mapFile); VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp"); mapReader->LoadMap(mapfilename, scriptInterface.GetJSRuntime(), JS::UndefinedHandleValue, &secondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &secondaryContext, INVALID_PLAYER, true); // throws exception on failure } LDR_EndRegistering(); ENSURE(LDR_NonprogressiveLoad() == INFO::OK); ENSURE(secondaryComponentManager.DeserializeState(primaryStateBefore.state)); SerializationTestState secondaryStateBefore; ENSURE(secondaryComponentManager.SerializeState(secondaryStateBefore.state)); if (serializationTestDebugDump) ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateBefore.debug, false)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateBefore.hash, false)); if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() || primaryStateBefore.hash != secondaryStateBefore.hash) { ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL); } SerializationTestState primaryStateAfter; ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state)); if (serializationTestHash) ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false)); UpdateComponents(secondaryContext, turnLengthFixed, CloneCommandsFromOtherContext(scriptInterface, secondaryComponentManager.GetScriptInterface(), commands)); SerializationTestState secondaryStateAfter; ENSURE(secondaryComponentManager.SerializeState(secondaryStateAfter.state)); if (serializationTestHash) ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false)); if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() || primaryStateAfter.hash != secondaryStateAfter.hash) { // Only do the (slow) dumping now we know we're going to need to report it ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false)); ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateAfter.debug, false)); ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter); } } // if (m_TurnNumber == 0) // m_ComponentManager.GetScriptInterface().DumpHeap(); // Run the GC occasionally // No delay because a lot of garbage accumulates in one turn and in non-visual replays there are // much more turns in the same time than in normal games. // Every 500 turns we run a shrinking GC, which decommits unused memory and frees all JIT code. // Based on testing, this seems to be a good compromise between memory usage and performance. // Also check the comment about gcPreserveCode in the ScriptInterface code and this forum topic: // http://www.wildfiregames.com/forum/index.php?showtopic=18466&p=300323 // // (TODO: we ought to schedule this for a frame where we're not // running the sim update, to spread the load) if (m_TurnNumber % 500 == 0) scriptInterface.GetRuntime()->ShrinkingGC(); else scriptInterface.GetRuntime()->MaybeIncrementalGC(0.0f); if (m_EnableOOSLog) DumpState(); // Start computing AI for the next turn CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY); if (cmpAIManager) cmpAIManager->StartComputation(); ++m_TurnNumber; }