From 2bf1dbfd1315f327ed0ef85da37a2fa75c4c12ef Mon Sep 17 00:00:00 2001 From: elexis Date: Tue, 15 Nov 2016 13:26:58 +0000 Subject: [PATCH] An awesome Rejoin-test tool by wraitii and Itms, fixes #4242, refs #3460. 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. --- binaries/system/readme.txt | 3 + source/main.cpp | 5 +- source/ps/GameSetup/Config.cpp | 3 + source/ps/Replay.cpp | 4 +- source/ps/Replay.h | 4 +- source/simulation2/Simulation2.cpp | 106 ++++++++++++++++++++--------- source/simulation2/Simulation2.h | 3 +- 7 files changed, 90 insertions(+), 38 deletions(-) diff --git a/binaries/system/readme.txt b/binaries/system/readme.txt index 2d32479ce0..a208a902cb 100644 --- a/binaries/system/readme.txt +++ b/binaries/system/readme.txt @@ -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 diff --git a/source/main.cpp b/source/main.cpp index 86961a6f41..fb330c4222 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -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(); diff --git a/source/ps/GameSetup/Config.cpp b/source/ps/GameSetup/Config.cpp index 8df64c7615..ab177a128b 100644 --- a/source/ps/GameSetup/Config.cpp +++ b/source/ps/GameSetup/Config.cpp @@ -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")); } diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index f380bd3ab7..17c139fe2a 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -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(); diff --git a/source/ps/Replay.h b/source/ps/Replay.h index be5b042864..6bdda6e5ca 100644 --- a/source/ps/Replay.h +++ b/source/ps/Replay.h @@ -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; diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index 8be12e1025..fef97bf016 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -51,7 +51,8 @@ class CSimulation2Impl public: CSimulation2Impl(CUnitManager* unitManager, shared_ptr 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* m_SecondaryLoadedScripts; struct SerializationTestState { @@ -332,6 +349,10 @@ void CSimulation2Impl::Update(int turnLength, const std::vector 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(); + 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::vectorLoadMap(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::vectorGetScriptInterface(), 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()); diff --git a/source/simulation2/Simulation2.h b/source/simulation2/Simulation2.h index ee341b8f8e..cdec350a1d 100644 --- a/source/simulation2/Simulation2.h +++ b/source/simulation2/Simulation2.h @@ -54,8 +54,9 @@ public: CSimulation2(CUnitManager* unitManager, shared_ptr rt, CTerrain* terrain); ~CSimulation2(); - void EnableOOSLog(); void EnableSerializationTest(); + void EnableRejoinTest(int rejoinTestTurn); + void EnableOOSLog(); /** * Load all scripts in the specified directory (non-recursively),