mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Contrary to the serializationtest, initializes the secondary simstate only once before progressively applying updates. Thus reproducing actual multiplayer rejoining, enabling developers to analyze OOS reports solely from the replay file of the rejoined and a non-rejoined client. This was SVN commit r18940.
This commit is contained in:
parent
9a4fcd55a8
commit
2bf1dbfd13
7 changed files with 90 additions and 38 deletions
|
|
@ -58,6 +58,9 @@ Advanced / diagnostic:
|
|||
-serializationtest checks simulation state each turn for serialization errors; on test
|
||||
failure, error is displayed and logs created in oos_log within the
|
||||
game's log folder. NOTE: game will run much slower with this option!
|
||||
-rejointest=N simulates a rejoin and checks simulation state each turn for serialization
|
||||
errors; this is similar to a serialization test but much faster and
|
||||
less complete. It should be enough for debugging most rejoin OOSes.
|
||||
|
||||
Windows-specific:
|
||||
-wQpcTscSafe allow timing via QueryPerformanceCounter despite the fact
|
||||
|
|
|
|||
|
|
@ -472,7 +472,10 @@ static void RunGameOrAtlas(int argc, const char* argv[])
|
|||
{
|
||||
CReplayPlayer replay;
|
||||
replay.Load(replayFile);
|
||||
replay.Replay(args.Has("serializationtest"), args.Has("ooslog"));
|
||||
replay.Replay(
|
||||
args.Has("serializationtest"),
|
||||
args.Has("rejointest") ? args.Get("rejointest").ToInt() : -1,
|
||||
args.Has("ooslog"));
|
||||
}
|
||||
|
||||
g_VFS.reset();
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@ static void ProcessCommandLineArgs(const CmdLineArgs& args)
|
|||
|
||||
if (args.Has("serializationtest"))
|
||||
g_ConfigDB.SetValueString(CFG_COMMAND, "serializationtest", "true");
|
||||
|
||||
if (args.Has("rejointest"))
|
||||
g_ConfigDB.SetValueString(CFG_COMMAND, "rejointest", args.Get("rejointest"));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ void CReplayPlayer::Load(const std::string& path)
|
|||
ENSURE(m_Stream->good());
|
||||
}
|
||||
|
||||
void CReplayPlayer::Replay(bool serializationtest, bool ooslog)
|
||||
void CReplayPlayer::Replay(bool serializationtest, int rejointestturn, bool ooslog)
|
||||
{
|
||||
ENSURE(m_Stream);
|
||||
|
||||
|
|
@ -130,6 +130,8 @@ void CReplayPlayer::Replay(bool serializationtest, bool ooslog)
|
|||
g_Game = new CGame(true, false);
|
||||
if (serializationtest)
|
||||
g_Game->GetSimulation2()->EnableSerializationTest();
|
||||
if (rejointestturn > 0)
|
||||
g_Game->GetSimulation2()->EnableRejoinTest(rejointestturn);
|
||||
if (ooslog)
|
||||
g_Game->GetSimulation2()->EnableOOSLog();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2015 Wildfire Games.
|
||||
/* Copyright (C) 2016 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -97,7 +97,7 @@ public:
|
|||
~CReplayPlayer();
|
||||
|
||||
void Load(const std::string& path);
|
||||
void Replay(bool serializationtest, bool ooslog);
|
||||
void Replay(bool serializationtest, int rejointestturn, bool ooslog);
|
||||
|
||||
private:
|
||||
std::istream* m_Stream;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ class CSimulation2Impl
|
|||
public:
|
||||
CSimulation2Impl(CUnitManager* unitManager, shared_ptr<ScriptRuntime> rt, CTerrain* terrain) :
|
||||
m_SimContext(), m_ComponentManager(m_SimContext, rt),
|
||||
m_EnableOOSLog(false), m_EnableSerializationTest(false),
|
||||
m_EnableOOSLog(false), m_EnableSerializationTest(false), m_RejoinTestTurn(-1), m_TestingRejoin(false),
|
||||
m_SecondaryTerrain(nullptr), m_SecondaryContext(nullptr), m_SecondaryComponentManager(nullptr), m_SecondaryLoadedScripts(nullptr),
|
||||
m_MapSettings(rt->m_rt), m_InitAttributes(rt->m_rt)
|
||||
{
|
||||
m_SimContext.m_UnitManager = unitManager;
|
||||
|
|
@ -65,6 +66,9 @@ public:
|
|||
{
|
||||
CFG_GET_VAL("ooslog", m_EnableOOSLog);
|
||||
CFG_GET_VAL("serializationtest", m_EnableSerializationTest);
|
||||
CFG_GET_VAL("rejointest", m_RejoinTestTurn);
|
||||
if (m_RejoinTestTurn <= 0) // Handle bogus values of the arg
|
||||
m_RejoinTestTurn = -1;
|
||||
}
|
||||
|
||||
if (m_EnableOOSLog)
|
||||
|
|
@ -76,6 +80,11 @@ public:
|
|||
|
||||
~CSimulation2Impl()
|
||||
{
|
||||
delete m_SecondaryTerrain;
|
||||
delete m_SecondaryContext;
|
||||
delete m_SecondaryComponentManager;
|
||||
delete m_SecondaryLoadedScripts;
|
||||
|
||||
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +139,14 @@ public:
|
|||
// Functions and data for the serialization test mode: (see Update() for relevant comments)
|
||||
|
||||
bool m_EnableSerializationTest;
|
||||
int m_RejoinTestTurn;
|
||||
bool m_TestingRejoin;
|
||||
|
||||
// Secondary simulation
|
||||
CTerrain* m_SecondaryTerrain;
|
||||
CSimContext* m_SecondaryContext;
|
||||
CComponentManager* m_SecondaryComponentManager;
|
||||
std::set<VfsPath>* m_SecondaryLoadedScripts;
|
||||
|
||||
struct SerializationTestState
|
||||
{
|
||||
|
|
@ -332,6 +349,10 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
|
|||
* 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).
|
||||
*
|
||||
* In rejoin test mode, the secondary simulation is initialized from serialized data at turn N, then both
|
||||
* simulations run independantly while comparing their states each turn. This is way faster than a
|
||||
* complete serialization test and allows us to reproduce OOSes on rejoin.
|
||||
*/
|
||||
|
||||
const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow)
|
||||
|
|
@ -340,7 +361,11 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
|
|||
SerializationTestState primaryStateBefore;
|
||||
ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface();
|
||||
|
||||
if (m_EnableSerializationTest)
|
||||
const bool startRejoinTest = (int64_t) m_RejoinTestTurn == m_TurnNumber;
|
||||
if (startRejoinTest)
|
||||
m_TestingRejoin = true;
|
||||
|
||||
if (m_EnableSerializationTest || m_TestingRejoin)
|
||||
{
|
||||
ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state));
|
||||
if (serializationTestDebugDump)
|
||||
|
|
@ -351,27 +376,35 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
|
|||
|
||||
UpdateComponents(m_SimContext, turnLengthFixed, commands);
|
||||
|
||||
|
||||
if (m_EnableSerializationTest)
|
||||
if (m_EnableSerializationTest || startRejoinTest)
|
||||
{
|
||||
// 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);
|
||||
if (startRejoinTest)
|
||||
debug_printf("Initializing the secondary simulation\n");
|
||||
|
||||
delete m_SecondaryTerrain;
|
||||
m_SecondaryTerrain = new CTerrain();
|
||||
|
||||
delete m_SecondaryContext;
|
||||
m_SecondaryContext = new CSimContext();
|
||||
m_SecondaryContext->m_Terrain = m_SecondaryTerrain;
|
||||
|
||||
delete m_SecondaryComponentManager;
|
||||
m_SecondaryComponentManager = new CComponentManager(*m_SecondaryContext, scriptInterface.GetRuntime());
|
||||
m_SecondaryComponentManager->LoadComponentTypes();
|
||||
|
||||
delete m_SecondaryLoadedScripts;
|
||||
m_SecondaryLoadedScripts = new std::set<VfsPath>();
|
||||
ENSURE(LoadDefaultScripts(*m_SecondaryComponentManager, m_SecondaryLoadedScripts));
|
||||
ResetComponentState(*m_SecondaryComponentManager, false, false);
|
||||
|
||||
// Load the trigger scripts after we have loaded the simulation.
|
||||
{
|
||||
JSContext* cx2 = secondaryComponentManager.GetScriptInterface().GetContext();
|
||||
JSContext* cx2 = m_SecondaryComponentManager->GetScriptInterface().GetContext();
|
||||
JSAutoRequest rq2(cx2);
|
||||
JS::RootedValue mapSettingsCloned(cx2,
|
||||
secondaryComponentManager.GetScriptInterface().CloneValueFromOtherContext(
|
||||
m_SecondaryComponentManager->GetScriptInterface().CloneValueFromOtherContext(
|
||||
scriptInterface, m_MapSettings));
|
||||
ENSURE(LoadTriggerScripts(secondaryComponentManager, mapSettingsCloned, &secondaryLoadedScripts));
|
||||
ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts));
|
||||
}
|
||||
|
||||
// Load the map into the secondary simulation
|
||||
|
|
@ -393,21 +426,23 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
|
|||
|
||||
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
|
||||
m_SecondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, m_SecondaryContext, INVALID_PLAYER, true); // throws exception on failure
|
||||
}
|
||||
|
||||
LDR_EndRegistering();
|
||||
ENSURE(LDR_NonprogressiveLoad() == INFO::OK);
|
||||
ENSURE(m_SecondaryComponentManager->DeserializeState(primaryStateBefore.state));
|
||||
}
|
||||
|
||||
ENSURE(secondaryComponentManager.DeserializeState(primaryStateBefore.state));
|
||||
|
||||
if (m_EnableSerializationTest || m_TestingRejoin)
|
||||
{
|
||||
SerializationTestState secondaryStateBefore;
|
||||
ENSURE(secondaryComponentManager.SerializeState(secondaryStateBefore.state));
|
||||
ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateBefore.state));
|
||||
if (serializationTestDebugDump)
|
||||
ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateBefore.debug, false));
|
||||
ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateBefore.debug, false));
|
||||
if (serializationTestHash)
|
||||
ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateBefore.hash, false));
|
||||
ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateBefore.hash, false));
|
||||
|
||||
if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() ||
|
||||
primaryStateBefore.hash != secondaryStateBefore.hash)
|
||||
|
|
@ -420,19 +455,19 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
|
|||
if (serializationTestHash)
|
||||
ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false));
|
||||
|
||||
UpdateComponents(secondaryContext, turnLengthFixed,
|
||||
CloneCommandsFromOtherContext(scriptInterface, secondaryComponentManager.GetScriptInterface(), commands));
|
||||
UpdateComponents(*m_SecondaryContext, turnLengthFixed,
|
||||
CloneCommandsFromOtherContext(scriptInterface, m_SecondaryComponentManager->GetScriptInterface(), commands));
|
||||
SerializationTestState secondaryStateAfter;
|
||||
ENSURE(secondaryComponentManager.SerializeState(secondaryStateAfter.state));
|
||||
ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateAfter.state));
|
||||
if (serializationTestHash)
|
||||
ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false));
|
||||
ENSURE(m_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));
|
||||
ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateAfter.debug, false));
|
||||
|
||||
ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter);
|
||||
}
|
||||
|
|
@ -588,6 +623,16 @@ CSimulation2::~CSimulation2()
|
|||
|
||||
// Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
|
||||
|
||||
void CSimulation2::EnableSerializationTest()
|
||||
{
|
||||
m->m_EnableSerializationTest = true;
|
||||
}
|
||||
|
||||
void CSimulation2::EnableRejoinTest(int rejoinTestTurn)
|
||||
{
|
||||
m->m_RejoinTestTurn = rejoinTestTurn;
|
||||
}
|
||||
|
||||
void CSimulation2::EnableOOSLog()
|
||||
{
|
||||
if (m->m_EnableOOSLog)
|
||||
|
|
@ -599,11 +644,6 @@ void CSimulation2::EnableOOSLog()
|
|||
debug_printf("Writing ooslogs to %s\n", m->m_OOSLogPath.string8().c_str());
|
||||
}
|
||||
|
||||
void CSimulation2::EnableSerializationTest()
|
||||
{
|
||||
m->m_EnableSerializationTest = true;
|
||||
}
|
||||
|
||||
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
|
||||
{
|
||||
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ public:
|
|||
CSimulation2(CUnitManager* unitManager, shared_ptr<ScriptRuntime> rt, CTerrain* terrain);
|
||||
~CSimulation2();
|
||||
|
||||
void EnableOOSLog();
|
||||
void EnableSerializationTest();
|
||||
void EnableRejoinTest(int rejoinTestTurn);
|
||||
void EnableOOSLog();
|
||||
|
||||
/**
|
||||
* Load all scripts in the specified directory (non-recursively),
|
||||
|
|
|
|||
Loading…
Reference in a new issue