Example #1
0
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;
}
Example #2
0
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;
}