Exemplo n.º 1
0
void CReplayPlayer::Replay()
{
	ENSURE(m_Stream);

	new CProfileViewer;
	new CProfileManager;
	g_ScriptStatsTable = new CScriptStatsTable;
	g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
	g_ScriptRuntime = ScriptInterface::CreateRuntime(128 * 1024 * 1024);

	CGame game(true);
	g_Game = &game;

	// Need some stuff for terrain movement costs:
	// (TODO: this ought to be independent of any graphics code)
	tex_codec_register_all();
	new CTerrainTextureManager;
	g_TexMan.LoadTerrainTextures();

	// Initialise h_mgr so it doesn't crash when emitting sounds
	h_mgr_init();

	std::vector<SimulationCommand> commands;
	u32 turn = 0;
	u32 turnLength = 0;

	std::string type;
	while ((*m_Stream >> type).good())
	{
//		if (turn >= 1400) break;

		if (type == "start")
		{
			std::string line;
			std::getline(*m_Stream, line);
			CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);

			game.StartGame(attribs, "");

			// TODO: Non progressive load can fail - need a decent way to handle this
			LDR_NonprogressiveLoad();

			PSRETURN ret = game.ReallyStartGame();
			ENSURE(ret == PSRETURN_OK);
		}
		else if (type == "turn")
		{
			*m_Stream >> turn >> turnLength;
			debug_printf(L"Turn %u (%u)... ", turn, turnLength);
		}
		else if (type == "cmd")
Exemplo n.º 2
0
/*
 * Command line options for autostart (keep synchronized with binaries/system/readme.txt):
 *
 * -autostart="TYPEDIR/MAPNAME"         enables autostart and sets MAPNAME; TYPEDIR is skirmishes, scenarios, or random
 * -autostart-ai=PLAYER:AI              sets the AI for PLAYER (e.g. 2:petra)
 * -autostart-aidiff=PLAYER:DIFF        sets the DIFFiculty of PLAYER's AI (0: sandbox, 5: very hard)
 * -autostart-civ=PLAYER:CIV            sets PLAYER's civilisation to CIV (skirmish and random maps only)
 * -autostart-aiseed=AISEED             sets the seed used for the AI random generator (default 0, use -1 for random)
 * Multiplayer:
 * -autostart-playername=NAME		sets local player NAME (default 'anonymous')
 * -autostart-host                      sets multiplayer host mode
 * -autostart-host-players=NUMBER       sets NUMBER of human players for multiplayer game (default 2)
 * -autostart-client=IP                 sets multiplayer client to join host at given IP address
 * Random maps only:
 * -autostart-seed=SEED                 sets random map SEED value (default 0, use -1 for random)
 * -autostart-size=TILES                sets random map size in TILES (default 192)
 * -autostart-players=NUMBER            sets NUMBER of players on random map (default 2)
 *
 * Examples:
 * 1) "Bob" will host a 2 player game on the Arcadia map:
 * -autostart="scenarios/Arcadia 02" -autostart-host -autostart-host-players=2 -autostart-playername="Bob"
 * 2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot:
 * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra
*/
bool Autostart(const CmdLineArgs& args)
{
	CStr autoStartName = args.Get("autostart");

	if (autoStartName.empty())
		return false;

	g_Game = new CGame();

	ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
	JSContext* cx = scriptInterface.GetContext();
	JSAutoRequest rq(cx);

	JS::RootedValue attrs(cx);
	scriptInterface.Eval("({})", &attrs);
	JS::RootedValue settings(cx);
	scriptInterface.Eval("({})", &settings);
	JS::RootedValue playerData(cx);
	scriptInterface.Eval("([])", &playerData);

	// The directory in front of the actual map name indicates which type
	// of map is being loaded. Drawback of this approach is the association
	// of map types and folders is hard-coded, but benefits are:
	// - No need to pass the map type via command line separately
	// - Prevents mixing up of scenarios and skirmish maps to some degree
	Path mapPath = Path(autoStartName);
	std::wstring mapDirectory = mapPath.Parent().Filename().string();
	std::string mapType;

	if (mapDirectory == L"random")
	{
		// Default seed is 0
		u32 seed = 0;
		CStr seedArg = args.Get("autostart-seed");

		if (!seedArg.empty())
		{
			// Random seed value
			if (seedArg == "-1")
				seed = rand();
			else
				seed = seedArg.ToULong();
		}
		
		// Random map definition will be loaded from JSON file, so we need to parse it
		std::wstring scriptPath = L"maps/" + autoStartName.FromUTF8() + L".json";
		JS::RootedValue scriptData(cx);
		scriptInterface.ReadJSONFile(scriptPath, &scriptData);
		if (!scriptData.isUndefined() && scriptInterface.GetProperty(scriptData, "settings", &settings))
		{
			// JSON loaded ok - copy script name over to game attributes
			std::wstring scriptFile;
			scriptInterface.GetProperty(settings, "Script", scriptFile);
			scriptInterface.SetProperty(attrs, "script", scriptFile);				// RMS filename
		}
		else
		{
			// Problem with JSON file
			LOGERROR("Autostart: Error reading random map script '%s'", utf8_from_wstring(scriptPath));
			throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
		}

		// Get optional map size argument (default 192)
		uint mapSize = 192;
		if (args.Has("autostart-size"))
		{
			CStr size = args.Get("autostart-size");
			mapSize = size.ToUInt();
		}

		scriptInterface.SetProperty(settings, "Seed", seed);		// Random seed
		scriptInterface.SetProperty(settings, "Size", mapSize);		// Random map size (in patches)

		// Get optional number of players (default 2)
		size_t numPlayers = 2;
		if (args.Has("autostart-players"))
		{
			CStr num = args.Get("autostart-players");
			numPlayers = num.ToUInt();
		}

		// Set up player data
		for (size_t i = 0; i < numPlayers; ++i)
		{
			JS::RootedValue player(cx);
			scriptInterface.Eval("({})", &player);

			// We could load player_defaults.json here, but that would complicate the logic
			// even more and autostart is only intended for developers anyway
			scriptInterface.SetProperty(player, "Civ", std::string("athen"));
			scriptInterface.SetPropertyInt(playerData, i, player);
		}
		mapType = "random";
	}
	else if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
	{
		// Initialize general settings from the map data so some values
		// (e.g. name of map) are always present, even when autostart is
		// partially configured
		CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml");
		scriptInterface.ParseJSON(mapSettingsJSON, &settings);
		
		// Initialize the playerData array being modified by autostart
		// with the real map data, so sensible values are present:
		scriptInterface.GetProperty(settings, "PlayerData", &playerData);

		if (mapDirectory == L"scenarios")
			mapType = "scenario";
		else
			mapType = "skirmish";
	}
	else
	{
		LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory));
		throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types.");
	}
	scriptInterface.SetProperty(attrs, "mapType", mapType);
	scriptInterface.SetProperty(attrs, "map", std::string("maps/" + autoStartName));
	scriptInterface.SetProperty(settings, "mapType", mapType);

	// Set seed for AIs
	u32 aiseed = 0;
	if (args.Has("autostart-aiseed"))
	{
		CStr seedArg = args.Get("autostart-aiseed");
		if (seedArg == "-1")
			aiseed = rand();
		else
			aiseed = seedArg.ToULong();
	}
	scriptInterface.SetProperty(settings, "AISeed", aiseed);

	// Set player data for AIs
	//		attrs.settings = { PlayerData: [ { AI: ... }, ... ] }:
	if (args.Has("autostart-ai"))
	{
		std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai");
		for (size_t i = 0; i < aiArgs.size(); ++i)
		{
			int playerID = aiArgs[i].BeforeFirst(":").ToInt();

			// Instead of overwriting existing player data, modify the array
			JS::RootedValue player(cx);
			if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined())
			{
				if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
				{
					// playerID is certainly bigger than this map player number
					LOGWARNING("Autostart: Invalid player %d in autostart-ai option", playerID);
					continue;
				}
				scriptInterface.Eval("({})", &player);
			}

			CStr name = aiArgs[i].AfterFirst(":");
			scriptInterface.SetProperty(player, "AI", std::string(name));
			scriptInterface.SetProperty(player, "AIDiff", 3);
			scriptInterface.SetPropertyInt(playerData, playerID-1, player);
		}
	}
	// Set AI difficulty
	if (args.Has("autostart-aidiff"))
	{
		std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff");
		for (size_t i = 0; i < civArgs.size(); ++i)
		{
			int playerID = civArgs[i].BeforeFirst(":").ToInt();

			// Instead of overwriting existing player data, modify the array
			JS::RootedValue player(cx);
			if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined())
			{
				if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes")
				{
					// playerID is certainly bigger than this map player number
					LOGWARNING("Autostart: Invalid player %d in autostart-aidiff option", playerID);
					continue;
				}
				scriptInterface.Eval("({})", &player);
			}

			int difficulty = civArgs[i].AfterFirst(":").ToInt();			
			scriptInterface.SetProperty(player, "AIDiff", difficulty);
			scriptInterface.SetPropertyInt(playerData, playerID-1, player);
		}
	}
	// Set player data for Civs
	if (args.Has("autostart-civ"))
	{
		if (mapDirectory != L"scenarios")
		{
			std::vector<CStr> civArgs = args.GetMultiple("autostart-civ");
			for (size_t i = 0; i < civArgs.size(); ++i)
			{
				int playerID = civArgs[i].BeforeFirst(":").ToInt();

				// Instead of overwriting existing player data, modify the array
				JS::RootedValue player(cx);
				if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined())
				{
					if (mapDirectory == L"skirmishes")
					{
						// playerID is certainly bigger than this map player number
						LOGWARNING("Autostart: Invalid player %d in autostart-civ option", playerID);
						continue;
					}
					scriptInterface.Eval("({})", &player);
				}
			
				CStr name = civArgs[i].AfterFirst(":");			
				scriptInterface.SetProperty(player, "Civ", std::string(name));
				scriptInterface.SetPropertyInt(playerData, playerID-1, player);
			}
		}
		else
			LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios");
	}

	// Add player data to map settings
	scriptInterface.SetProperty(settings, "PlayerData", playerData);

	// Add map settings to game attributes
	scriptInterface.SetProperty(attrs, "settings", settings);

	JS::RootedValue mpInitData(cx);
	scriptInterface.Eval("({isNetworked:true, playerAssignments:{}})", &mpInitData);
	scriptInterface.SetProperty(mpInitData, "attribs", attrs);

	// Get optional playername
	CStrW userName = L"anonymous";
	if (args.Has("autostart-playername"))
	{
		userName = args.Get("autostart-playername").FromUTF8();
	}

	if (args.Has("autostart-host"))
	{
		InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData);

		size_t maxPlayers = 2;
		if (args.Has("autostart-host-players"))
			maxPlayers = args.Get("autostart-host-players").ToUInt();

		g_NetServer = new CNetServer(maxPlayers);

		g_NetServer->UpdateGameAttributes(&attrs, scriptInterface);

		bool ok = g_NetServer->SetupConnection();
		ENSURE(ok);

		g_NetClient = new CNetClient(g_Game);
		g_NetClient->SetUserName(userName);
		g_NetClient->SetupConnection("127.0.0.1");
	}
	else if (args.Has("autostart-client"))
	{
		InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData);

		g_NetClient = new CNetClient(g_Game);
		g_NetClient->SetUserName(userName);

		CStr ip = args.Get("autostart-client");
		if (ip.empty())
			ip = "127.0.0.1";

		bool ok = g_NetClient->SetupConnection(ip);
		ENSURE(ok);
	}
	else
	{
		g_Game->SetPlayerID(1);
		g_Game->StartGame(&attrs, "");

		LDR_NonprogressiveLoad();

		PSRETURN ret = g_Game->ReallyStartGame();
		ENSURE(ret == PSRETURN_OK);

		InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue);
	}

	return true;
}
Exemplo n.º 3
0
void CReplayPlayer::Replay(bool serializationtest, bool ooslog)
{
	ENSURE(m_Stream);

	new CProfileViewer;
	new CProfileManager;
	g_ScriptStatsTable = new CScriptStatsTable;
	g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
	
	const int runtimeSize = 384 * 1024 * 1024;
	const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024;
	g_ScriptRuntime = ScriptInterface::CreateRuntime(shared_ptr<ScriptRuntime>(), runtimeSize, heapGrowthBytesGCTrigger);

	g_Game = new CGame(true);
	if (serializationtest)
		g_Game->GetSimulation2()->EnableSerializationTest();
	if (ooslog)
		g_Game->GetSimulation2()->EnableOOSLog();

	// Need some stuff for terrain movement costs:
	// (TODO: this ought to be independent of any graphics code)
	new CTerrainTextureManager;
	g_TexMan.LoadTerrainTextures();

	// Initialise h_mgr so it doesn't crash when emitting sounds
	h_mgr_init();

	std::vector<SimulationCommand> commands;
	u32 turn = 0;
	u32 turnLength = 0;

	{
	JSContext* cx = g_Game->GetSimulation2()->GetScriptInterface().GetContext();
	JSAutoRequest rq(cx);
	std::string type;
	while ((*m_Stream >> type).good())
	{
		if (type == "start")
		{
			std::string line;
			std::getline(*m_Stream, line);
			JS::RootedValue attribs(cx);
			ENSURE(g_Game->GetSimulation2()->GetScriptInterface().ParseJSON(line, &attribs));

			g_Game->StartGame(&attribs, "");

			// TODO: Non progressive load can fail - need a decent way to handle this
			LDR_NonprogressiveLoad();

			PSRETURN ret = g_Game->ReallyStartGame();
			ENSURE(ret == PSRETURN_OK);
		}
		else if (type == "turn")
		{
			*m_Stream >> turn >> turnLength;
			debug_printf("Turn %u (%u)...\n", turn, turnLength);
		}
		else if (type == "cmd")
		{
			player_id_t player;
			*m_Stream >> player;

			std::string line;
			std::getline(*m_Stream, line);
			JS::RootedValue data(cx);
			g_Game->GetSimulation2()->GetScriptInterface().ParseJSON(line, &data);

			commands.emplace_back(SimulationCommand(player, cx, data));
		}
Exemplo n.º 4
0
bool Autostart(const CmdLineArgs& args)
{
	/*
	 * Handle various command-line options, for quick testing of various features:
	 * -autostart=name					-- map name for scenario, or rms name for random map
	 * -autostart-ai=1:dummybot			-- adds the dummybot AI to player 1
	 * -autostart-playername=name		-- multiplayer player name
	 * -autostart-host					-- multiplayer host mode
	 * -autostart-players=2				-- number of players
	 * -autostart-client				-- multiplayer client mode
	 * -autostart-ip=127.0.0.1			-- multiplayer connect to 127.0.0.1
	 * -autostart-random=104			-- random map, optional seed value = 104 (default is 0, random is -1)
	 * -autostart-size=192				-- random map size in tiles = 192 (default is 192)
	 *
	 * Examples:
	 * -autostart=Acropolis -autostart-host -autostart-players=2		-- Host game on Acropolis map, 2 players
	 * -autostart=latium -autostart-random=-1							-- Start single player game on latium random map, random rng seed
	 */

	CStr autoStartName = args.Get("autostart");

#if OS_ANDROID
	// HACK: currently the most convenient way to test maps on Android;
	// should find a better solution
	autoStartName = "Oasis";
#endif

	if (autoStartName.empty())
	{
		return false;
	}

	g_Game = new CGame();

	ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();

	CScriptValRooted attrs;
	scriptInterface.Eval("({})", attrs);
	CScriptVal settings;
	scriptInterface.Eval("({})", settings);
	CScriptVal playerData;
	scriptInterface.Eval("([])", playerData);

	// Set different attributes for random or scenario game
	if (args.Has("autostart-random"))
	{
		CStr seedArg = args.Get("autostart-random");

		// Default seed is 0
		uint32 seed = 0;
		if (!seedArg.empty())
		{
			if (seedArg.compare("-1") == 0)
			{	// Random seed value
				seed = rand();
			}
			else
			{
				seed = seedArg.ToULong();
			}
		}
		
		// Random map definition will be loaded from JSON file, so we need to parse it
		std::wstring mapPath = L"maps/random/";
		std::wstring scriptPath = mapPath + autoStartName.FromUTF8() + L".json";
		CScriptValRooted scriptData = scriptInterface.ReadJSONFile(scriptPath);
		if (!scriptData.undefined() && scriptInterface.GetProperty(scriptData.get(), "settings", settings))
		{
			// JSON loaded ok - copy script name over to game attributes
			std::wstring scriptFile;
			scriptInterface.GetProperty(settings.get(), "Script", scriptFile);
			scriptInterface.SetProperty(attrs.get(), "script", scriptFile);				// RMS filename
		}
		else
		{
			// Problem with JSON file
			LOGERROR(L"Error reading random map script '%ls'", scriptPath.c_str());
			throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details.");
		}

		// Get optional map size argument (default 192)
		uint mapSize = 192;
		if (args.Has("autostart-size"))
		{
			CStr size = args.Get("autostart-size");
			mapSize = size.ToUInt();
		}

		scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName));
		scriptInterface.SetProperty(attrs.get(), "mapPath", mapPath);
		scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random"));
		scriptInterface.SetProperty(settings.get(), "Seed", seed);									// Random seed
		scriptInterface.SetProperty(settings.get(), "Size", mapSize);								// Random map size (in patches)

		// Get optional number of players (default 2)
		size_t numPlayers = 2;
		if (args.Has("autostart-players"))
		{
			CStr num = args.Get("autostart-players");
			numPlayers = num.ToUInt();
		}

		// Set up player data
		for (size_t i = 0; i < numPlayers; ++i)
		{
			CScriptVal player;
			scriptInterface.Eval("({})", player);

			// We could load player_defaults.json here, but that would complicate the logic
			//	even more and autostart is only intended for developers anyway
			scriptInterface.SetProperty(player.get(), "Civ", std::string("athen"));
			scriptInterface.SetPropertyInt(playerData.get(), i, player);
		}
	}
	else
	{
		scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName));
		scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"));
	}

	// Set player data for AIs
	//		attrs.settings = { PlayerData: [ { AI: ... }, ... ] }:
	if (args.Has("autostart-ai"))
	{
		std::vector<CStr> aiArgs = args.GetMultiple("autostart-ai");
		for (size_t i = 0; i < aiArgs.size(); ++i)
		{
			// Instead of overwriting existing player data, modify the array
			CScriptVal player;
			if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined())
			{
				scriptInterface.Eval("({})", player);
			}

			int playerID = aiArgs[i].BeforeFirst(":").ToInt();
			CStr name = aiArgs[i].AfterFirst(":");

			scriptInterface.SetProperty(player.get(), "AI", std::string(name));
			scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player);
		}
	}

	// Add player data to map settings
	scriptInterface.SetProperty(settings.get(), "PlayerData", playerData);

	// Add map settings to game attributes
	scriptInterface.SetProperty(attrs.get(), "settings", settings);

	CScriptVal mpInitData;
	g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData);
	g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs",
			CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())));

	// Get optional playername
	CStrW userName = L"anonymous";
	if (args.Has("autostart-playername"))
	{
		userName = args.Get("autostart-playername").FromUTF8();
	}

	if (args.Has("autostart-host"))
	{
		InitPs(true, L"page_loading.xml", mpInitData.get());

		size_t maxPlayers = 2;
		if (args.Has("autostart-players"))
		{
			maxPlayers = args.Get("autostart-players").ToUInt();
		}

		g_NetServer = new CNetServer(maxPlayers);

		g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface);

		bool ok = g_NetServer->SetupConnection();
		ENSURE(ok);

		g_NetClient = new CNetClient(g_Game);
		g_NetClient->SetUserName(userName);
		g_NetClient->SetupConnection("127.0.0.1");
	}
	else if (args.Has("autostart-client"))
	{
		InitPs(true, L"page_loading.xml", mpInitData.get());

		g_NetClient = new CNetClient(g_Game);
		g_NetClient->SetUserName(userName);

		CStr ip = "127.0.0.1";
		if (args.Has("autostart-ip"))
		{
			ip = args.Get("autostart-ip");
		}

		bool ok = g_NetClient->SetupConnection(ip);
		ENSURE(ok);
	}
	else
	{
		g_Game->SetPlayerID(1);
		g_Game->StartGame(attrs, "");

		LDR_NonprogressiveLoad();

		PSRETURN ret = g_Game->ReallyStartGame();
		ENSURE(ret == PSRETURN_OK);

		InitPs(true, L"page_session.xml", JSVAL_VOID);
	}

	return true;
}
Exemplo n.º 5
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;
}
Exemplo n.º 6
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;
}