Add initial serialization test mode.

Reduce some dependencies on CSimulation2 to provide more flexibility.

This was SVN commit r10426.
This commit is contained in:
Ykkrosh 2011-10-24 14:31:05 +00:00
parent 0f3119e36a
commit 29e4f633f1
14 changed files with 368 additions and 114 deletions

View File

@ -62,7 +62,7 @@ CMapReader::CMapReader()
void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_,
CSimulation2 *pSimulation2_, int playerID_)
CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
{
// latch parameters (held until DelayedLoadFinished)
pTerrain = pTerrain_;
@ -73,7 +73,9 @@ void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pSimulation2 = pSimulation2_;
pSimContext = pSimContext_;
m_PlayerID = playerID_;
m_SkipEntities = skipEntities;
m_StartingCameraTarget = INVALID_ENTITY;
filename_xml = pathname.ChangeExtension(L".xml");
@ -144,7 +146,9 @@ void CMapReader::LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted&
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pSimulation2 = pSimulation2_;
pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
m_PlayerID = playerID_;
m_SkipEntities = false;
m_StartingCameraTarget = INVALID_ENTITY;
// delete all existing entities
@ -282,7 +286,7 @@ int CMapReader::ApplyData()
if (pLightEnv)
*pLightEnv = m_LightEnv;
CmpPtr<ICmpPlayerManager> cmpPlayerManager(*pSimulation2, SYSTEM_ENTITY);
CmpPtr<ICmpPlayerManager> cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
if (pGameView && !cmpPlayerManager.null())
{
@ -290,7 +294,7 @@ int CMapReader::ApplyData()
pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
// TODO: Starting rotation?
CmpPtr<ICmpPlayer> cmpPlayer(*pSimulation2, cmpPlayerManager->GetPlayerByID(m_PlayerID));
CmpPtr<ICmpPlayer> cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
if (!cmpPlayer.null() && cmpPlayer->HasStartingCamera())
{
// Use player starting camera
@ -300,7 +304,7 @@ int CMapReader::ApplyData()
else if (m_StartingCameraTarget != INVALID_ENTITY)
{
// Point camera at entity
CmpPtr<ICmpPosition> cmpPosition(*pSimulation2, m_StartingCameraTarget);
CmpPtr<ICmpPosition> cmpPosition(*pSimContext, m_StartingCameraTarget);
if (!cmpPosition.null())
{
CFixedVector3D pos = cmpPosition->GetPosition();
@ -309,7 +313,7 @@ int CMapReader::ApplyData()
}
}
CmpPtr<ICmpTerrain> cmpTerrain(*pSimulation2, SYSTEM_ENTITY);
CmpPtr<ICmpTerrain> cmpTerrain(*pSimContext, SYSTEM_ENTITY);
if (!cmpTerrain.null())
cmpTerrain->ReloadTerrain();
@ -630,7 +634,7 @@ void CXMLReader::ReadEnvironment(XMBElement parent)
int element_name = waterelement.GetNodeName();
if (element_name == el_height)
{
CmpPtr<ICmpWaterManager> cmpWaterMan(*m_MapReader.pSimulation2, SYSTEM_ENTITY);
CmpPtr<ICmpWaterManager> cmpWaterMan(*m_MapReader.pSimContext, SYSTEM_ENTITY);
ENSURE(!cmpWaterMan.null());
cmpWaterMan->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText()));
continue;
@ -859,6 +863,7 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
XMBElementList entities = parent.GetChildNodes();
ENSURE(m_MapReader.pSimulation2);
CSimulation2& sim = *m_MapReader.pSimulation2;
CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
@ -981,9 +986,12 @@ int CXMLReader::ProgressiveRead()
}
else if (name == "Entities")
{
ret = ReadEntities(node, end_time);
if (ret != 0) // error or timed out
return ret;
if (!m_MapReader.m_SkipEntities)
{
ret = ReadEntities(node, end_time);
if (ret != 0) // error or timed out
return ret;
}
}
else if (name == "Paths")
{
@ -995,7 +1003,8 @@ int CXMLReader::ProgressiveRead()
}
else if (name == "Script")
{
m_MapReader.pSimulation2->SetStartupScript(node.GetText().FromUTF8());
if (m_MapReader.pSimulation2)
m_MapReader.pSimulation2->SetStartupScript(node.GetText().FromUTF8());
}
else
{
@ -1021,7 +1030,8 @@ int CMapReader::LoadScriptSettings()
xml_reader = new CXMLReader(filename_xml, *this);
// parse the script settings
pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
if (pSimulation2)
pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
return 0;
}
@ -1029,14 +1039,16 @@ int CMapReader::LoadScriptSettings()
// load player settings script
int CMapReader::LoadPlayerSettings()
{
pSimulation2->LoadPlayerSettings(true);
if (pSimulation2)
pSimulation2->LoadPlayerSettings(true);
return 0;
}
// load map settings script
int CMapReader::LoadMapSettings()
{
pSimulation2->LoadMapSettings();
if (pSimulation2)
pSimulation2->LoadMapSettings();
return 0;
}

View File

@ -34,6 +34,7 @@ class CLightEnv;
class CCinemaManager;
class CTriggerManager;
class CSimulation2;
class CSimContext;
class CTerrainTextureEntry;
class CScriptValRooted;
class ScriptInterface;
@ -52,7 +53,7 @@ public:
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void LoadMap(const VfsPath& pathname, CTerrain*, WaterManager*, SkyManager*, CLightEnv*, CGameView*,
CCinemaManager*, CTriggerManager*, CSimulation2*, int playerID);
CCinemaManager*, CTriggerManager*, CSimulation2*, const CSimContext*, int playerID, bool skipEntities);
void LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted& settings, CTerrain*, WaterManager*, SkyManager*, CLightEnv*, CGameView*,
CCinemaManager*, CTriggerManager*, CSimulation2*, int playerID);
@ -133,7 +134,9 @@ private:
CCinemaManager* pCinema;
CTriggerManager* pTrigMan;
CSimulation2* pSimulation2;
const CSimContext* pSimContext;
int m_PlayerID;
bool m_SkipEntities;
VfsPath filename_xml;
bool only_xml;
u32 file_format_version;

View File

@ -84,9 +84,6 @@ CGame::CGame(bool disableGraphics):
**/
CGame::~CGame()
{
// Clear rooted value before destroying its context
m_RegisteredAttribs = CScriptValRooted();
// Again, the in-game call tree is going to be different to the main menu one.
if (CProfileManager::IsInitialised())
g_Profiler.StructuralReset();
@ -117,7 +114,7 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager)
**/
void CGame::RegisterInit(const CScriptValRooted& attribs)
{
m_RegisteredAttribs = attribs; // save the attributes for ReallyStartGame
m_Simulation2->SetInitAttributes(attribs);
std::string mapType;
m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "mapType", mapType);
@ -165,7 +162,7 @@ void CGame::RegisterInit(const CScriptValRooted& attribs)
PSRETURN CGame::ReallyStartGame()
{
CScriptVal settings;
m_Simulation2->GetScriptInterface().GetProperty(m_RegisteredAttribs.get(), "settings", settings);
m_Simulation2->GetScriptInterface().GetProperty(m_Simulation2->GetInitAttributes().get(), "settings", settings);
m_Simulation2->InitGame(settings);
// Call the reallyStartGame GUI function, but only if it exists

View File

@ -162,7 +162,6 @@ public:
private:
void RegisterInit(const CScriptValRooted& attribs);
IReplayLogger* m_ReplayLogger;
CScriptValRooted m_RegisteredAttribs;
std::vector<CColor> m_PlayerColours;
};

View File

@ -82,7 +82,7 @@ void CWorld::RegisterInit(const CStrW& mapFile, int playerID)
CRenderer::IsInitialised() ? g_Renderer.GetSkyManager() : NULL,
&g_LightEnv, m_pGame->GetView(),
m_pGame->GetView() ? m_pGame->GetView()->GetCinema() : NULL,
pTriggerManager, m_pGame->GetSimulation2(), playerID);
pTriggerManager, m_pGame->GetSimulation2(), &m_pGame->GetSimulation2()->GetSimContext(), playerID, false);
// fails immediately, or registers for delay loading
}
catch (PSERROR_File& err)

View File

@ -27,11 +27,14 @@
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "graphics/MapReader.h"
#include "graphics/Terrain.h"
#include "lib/timer.h"
#include "lib/file/vfs/vfs_util.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/XML/Xeromyces.h"
@ -43,11 +46,21 @@
#define getpid _getpid // use the non-deprecated function name
#endif
static std::string Hexify(const std::string& s) // TODO: shouldn't duplicate this function in so many places
{
std::stringstream str;
str << std::hex;
for (size_t i = 0; i < s.size(); ++i)
str << std::setfill('0') << std::setw(2) << (int)(unsigned char)s[i];
return str.str();
}
class CSimulation2Impl
{
public:
CSimulation2Impl(CUnitManager* unitManager, CTerrain* terrain) :
m_SimContext(), m_ComponentManager(m_SimContext), m_EnableOOSLog(false)
m_SimContext(), m_ComponentManager(m_SimContext),
m_EnableOOSLog(false), m_EnableSerializationTest(false)
{
m_SimContext.m_UnitManager = unitManager;
m_SimContext.m_Terrain = terrain;
@ -56,6 +69,7 @@ public:
RegisterFileReloadFunc(ReloadChangedFileCB, this);
// m_EnableOOSLog = true; // TODO: this should be a command-line flag or similar
// m_EnableSerializationTest = true; // TODO: this should too
}
~CSimulation2Impl()
@ -65,41 +79,45 @@ public:
void ResetState(bool skipScriptedComponents, bool skipAI)
{
m_ComponentManager.ResetState();
m_DeltaTime = 0.0;
m_LastFrameOffset = 0.0f;
m_TurnNumber = 0;
ResetComponentState(m_ComponentManager, skipScriptedComponents, skipAI);
}
static void ResetComponentState(CComponentManager& componentManager, bool skipScriptedComponents, bool skipAI)
{
componentManager.ResetState();
CParamNode noParam;
CComponentManager::ComponentTypeId cid;
// Add native system components:
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_TemplateManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_TemplateManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_RangeManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_TerritoryManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_RangeManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_TerritoryManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
if (!skipAI)
{
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_AIManager, noParam);
componentManager.AddComponent(SYSTEM_ENTITY, CID_AIManager, noParam);
}
// Add scripted system components:
if (!skipScriptedComponents)
{
#define LOAD_SCRIPTED_COMPONENT(name) \
cid = m_ComponentManager.LookupCID(name); \
cid = componentManager.LookupCID(name); \
if (cid == CID__Invalid) \
LOGERROR(L"Can't find component type " L##name); \
m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
componentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
LOAD_SCRIPTED_COMPONENT("AIInterface");
LOAD_SCRIPTED_COMPONENT("EndGameManager");
@ -111,7 +129,8 @@ public:
}
}
bool LoadScripts(const VfsPath& path);
static bool LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts);
static bool LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path);
Status ReloadChangedFile(const VfsPath& path);
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
@ -120,7 +139,8 @@ public:
}
int ProgressiveLoad();
bool Update(int turnLength, const std::vector<SimulationCommand>& commands);
void Update(int turnLength, const std::vector<SimulationCommand>& commands);
static void UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands);
void Interpolate(float frameLength, float frameOffset);
void DumpState();
@ -131,6 +151,7 @@ public:
float m_LastFrameOffset;
std::wstring m_StartupScript;
CScriptValRooted m_InitAttributes;
CScriptValRooted m_MapSettings;
std::set<VfsPath> m_LoadedScripts;
@ -138,9 +159,48 @@ public:
uint32_t m_TurnNumber;
bool m_EnableOOSLog;
// Functions and data for the serialization test mode: (see Update() for relevant comments)
bool m_EnableSerializationTest;
struct SerializationTestState
{
std::stringstream state;
std::stringstream debug;
std::string hash;
};
void DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix);
void ReportSerializationFailure(
SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter);
static std::vector<SimulationCommand> CloneCommandsFromOtherContext(ScriptInterface& oldScript, ScriptInterface& newScript,
const std::vector<SimulationCommand>& commands)
{
std::vector<SimulationCommand> newCommands = commands;
for (size_t i = 0; i < newCommands.size(); ++i)
{
newCommands[i].data = CScriptValRooted(newScript.GetContext(),
newScript.CloneValueFromOtherContext(oldScript, newCommands[i].data.get()));
}
return newCommands;
}
};
bool CSimulation2Impl::LoadScripts(const VfsPath& path)
bool CSimulation2Impl::LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts)
{
return (
LoadScripts(componentManager, loadedScripts, "simulation/components/interfaces/") &&
LoadScripts(componentManager, loadedScripts, L"simulation/helpers/") &&
LoadScripts(componentManager, loadedScripts, L"simulation/components/")
);
}
bool CSimulation2Impl::LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path)
{
VfsPaths pathnames;
if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
@ -150,9 +210,10 @@ bool CSimulation2Impl::LoadScripts(const VfsPath& path)
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
VfsPath filename = *it;
m_LoadedScripts.insert(filename);
if (loadedScripts)
loadedScripts->insert(filename);
LOGMESSAGE(L"Loading simulation script '%ls'", filename.string().c_str());
if (! m_ComponentManager.LoadScript(filename))
if (! componentManager.LoadScript(filename))
ok = false;
}
return ok;
@ -207,63 +268,167 @@ int CSimulation2Impl::ProgressiveLoad()
return ret;
}
bool CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix)
{
if (!state.hash.empty())
{
std::ofstream file (OsString(path / (L"hash." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
file << Hexify(state.hash);
}
if (!state.debug.str().empty())
{
std::ofstream file (OsString(path / (L"debug." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
file << state.debug.str();
}
if (!state.state.str().empty())
{
std::ofstream file (OsString(path / (L"state." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
file << state.state.str();
}
}
void CSimulation2Impl::ReportSerializationFailure(
SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter)
{
OsPath path = psLogDir() / "oos_log";
CreateDirectories(path, 0700);
// Clean up obsolete files from previous runs
wunlink(path / "hash.before.a");
wunlink(path / "hash.before.b");
wunlink(path / "debug.before.a");
wunlink(path / "debug.before.b");
wunlink(path / "state.before.a");
wunlink(path / "state.before.b");
wunlink(path / "hash.after.a");
wunlink(path / "hash.after.b");
wunlink(path / "debug.after.a");
wunlink(path / "debug.after.b");
wunlink(path / "state.after.a");
wunlink(path / "state.after.b");
if (primaryStateBefore)
DumpSerializationTestState(*primaryStateBefore, path, L"before.a");
if (primaryStateAfter)
DumpSerializationTestState(*primaryStateAfter, path, L"after.a");
if (secondaryStateBefore)
DumpSerializationTestState(*secondaryStateBefore, path, L"before.b");
if (secondaryStateAfter)
DumpSerializationTestState(*secondaryStateAfter, path, L"after.b");
debug_warn(L"Serialization test failure");
}
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
{
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
// TODO: the update process is pretty ugly, with lots of messages and dependencies
// between different components. Ought to work out a nicer way to do this.
/*
* 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).
*/
CMessageTurnStart msgTurnStart;
m_ComponentManager.BroadcastMessage(msgTurnStart);
const bool serializationTestDebugDump = false; // set true to save human-readable state dumps, for debugging (but slow)
const bool serializationTestHash = true; // set true to save and compare hash of state
CmpPtr<ICmpPathfinder> cmpPathfinder(m_SimContext, SYSTEM_ENTITY);
if (!cmpPathfinder.null())
cmpPathfinder->FinishAsyncRequests();
// Push AI commands onto the queue before we use them
CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY);
if (!cmpAIManager.null())
cmpAIManager->PushCommands();
CmpPtr<ICmpCommandQueue> cmpCommandQueue(m_SimContext, SYSTEM_ENTITY);
if (!cmpCommandQueue.null())
cmpCommandQueue->FlushTurn(commands);
// Process newly generated move commands so the UI feels snappy
if (!cmpPathfinder.null())
cmpPathfinder->ProcessSameTurnMoves();
// Send all the update phases
SerializationTestState primaryStateBefore;
if (m_EnableSerializationTest)
{
CMessageUpdate msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state));
if (serializationTestDebugDump)
ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false));
if (serializationTestHash)
ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false));
}
// Process move commands for formations (group proxy)
if (!cmpPathfinder.null())
cmpPathfinder->ProcessSameTurnMoves();
UpdateComponents(m_SimContext, turnLengthFixed, commands);
if (m_EnableSerializationTest)
{
CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
// Initialise the secondary simulation
CTerrain secondaryTerrain;
CSimContext secondaryContext;
secondaryContext.m_Terrain = &secondaryTerrain;
CComponentManager secondaryComponentManager(secondaryContext);
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 == "scenario")
{
// Load scenario attributes
std::wstring mapFile;
m_ComponentManager.GetScriptInterface().GetProperty(m_InitAttributes.get(), "map", mapFile);
VfsPath mapfilename(VfsPath("maps/scenarios") / (mapFile + L".pmp"));
mapReader->LoadMap(mapfilename, &secondaryTerrain, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, &secondaryContext, INVALID_PLAYER, true); // throws exception on failure
}
else
{
// TODO: support random map scripts
debug_warn(L"Serialization test mode only supports scenarios");
}
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 (serializationTestDebugDump)
ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false));
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 (serializationTestDebugDump)
ENSURE(secondaryComponentManager.DumpDebugState(secondaryStateAfter.debug, false));
if (serializationTestHash)
ENSURE(secondaryComponentManager.ComputeStateHash(secondaryStateAfter.hash, false));
if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() ||
primaryStateAfter.hash != secondaryStateAfter.hash)
{
ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter);
}
}
{
CMessageUpdate_Final msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
}
// Process moves resulting from group proxy movement (unit needs to catch up or realign) and any others
if (!cmpPathfinder.null())
cmpPathfinder->ProcessSameTurnMoves();
// Clean up any entities destroyed during the simulation update
m_ComponentManager.FlushDestroyedComponents();
// if (m_TurnNumber == 0)
// m_ComponentManager.GetScriptInterface().DumpHeap();
@ -278,12 +443,69 @@ bool CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
DumpState();
// Start computing AI for the next turn
CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY);
if (!cmpAIManager.null())
cmpAIManager->StartComputation();
++m_TurnNumber;
}
return true; // TODO: don't bother with bool return
void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands)
{
// TODO: the update process is pretty ugly, with lots of messages and dependencies
// between different components. Ought to work out a nicer way to do this.
CComponentManager& componentManager = simContext.GetComponentManager();
CMessageTurnStart msgTurnStart;
componentManager.BroadcastMessage(msgTurnStart);
CmpPtr<ICmpPathfinder> cmpPathfinder(simContext, SYSTEM_ENTITY);
if (!cmpPathfinder.null())
cmpPathfinder->FinishAsyncRequests();
// Push AI commands onto the queue before we use them
CmpPtr<ICmpAIManager> cmpAIManager(simContext, SYSTEM_ENTITY);
if (!cmpAIManager.null())
cmpAIManager->PushCommands();
CmpPtr<ICmpCommandQueue> cmpCommandQueue(simContext, SYSTEM_ENTITY);
if (!cmpCommandQueue.null())
cmpCommandQueue->FlushTurn(commands);
// Process newly generated move commands so the UI feels snappy
if (!cmpPathfinder.null())
cmpPathfinder->ProcessSameTurnMoves();
// Send all the update phases
{
CMessageUpdate msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
// Process move commands for formations (group proxy)
if (!cmpPathfinder.null())
cmpPathfinder->ProcessSameTurnMoves();
{
CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_Final msgUpdate(turnLengthFixed);
componentManager.BroadcastMessage(msgUpdate);
}
// Process moves resulting from group proxy movement (unit needs to catch up or realign) and any others
if (!cmpPathfinder.null())
cmpPathfinder->ProcessSameTurnMoves();
// Clean up any entities destroyed during the simulation update
componentManager.FlushDestroyedComponents();
}
void CSimulation2Impl::Interpolate(float frameLength, float frameOffset)
@ -316,7 +538,7 @@ void CSimulation2Impl::DumpState()
file << "\n";
m_ComponentManager.DumpDebugState(file);
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);
@ -407,15 +629,15 @@ void CSimulation2::InitGame(const CScriptVal& data)
GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret);
}
bool CSimulation2::Update(int turnLength)
void CSimulation2::Update(int turnLength)
{
std::vector<SimulationCommand> commands;
return m->Update(turnLength, commands);
m->Update(turnLength, commands);
}
bool CSimulation2::Update(int turnLength, const std::vector<SimulationCommand>& commands)
void CSimulation2::Update(int turnLength, const std::vector<SimulationCommand>& commands)
{
return m->Update(turnLength, commands);
m->Update(turnLength, commands);
}
void CSimulation2::Interpolate(float frameLength, float frameOffset)
@ -436,16 +658,12 @@ float CSimulation2::GetLastFrameOffset() const
bool CSimulation2::LoadScripts(const VfsPath& path)
{
return m->LoadScripts(path);
return m->LoadScripts(m->m_ComponentManager, &m->m_LoadedScripts, path);
}
bool CSimulation2::LoadDefaultScripts()
{
return (
m->LoadScripts(L"simulation/components/interfaces/") &&
m->LoadScripts(L"simulation/helpers/") &&
m->LoadScripts(L"simulation/components/")
);
return m->LoadDefaultScripts(m->m_ComponentManager, &m->m_LoadedScripts);
}
void CSimulation2::SetStartupScript(const std::wstring& code)
@ -458,6 +676,16 @@ const std::wstring& CSimulation2::GetStartupScript()
return m->m_StartupScript;
}
void CSimulation2::SetInitAttributes(const CScriptValRooted& attribs)
{
m->m_InitAttributes = attribs;
}
CScriptValRooted CSimulation2::GetInitAttributes()
{
return m->m_InitAttributes;
}
void CSimulation2::SetMapSettings(const std::string& settings)
{
m->m_MapSettings = m->m_ComponentManager.GetScriptInterface().ParseJSON(settings);
@ -514,7 +742,7 @@ bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick)
bool CSimulation2::DumpDebugState(std::ostream& stream)
{
return m->m_ComponentManager.DumpDebugState(stream);
return m->m_ComponentManager.DumpDebugState(stream, true);
}
bool CSimulation2::SerializeState(std::ostream& stream)

View File

@ -89,6 +89,17 @@ public:
*/
const std::wstring& GetStartupScript();
/**
* Set the attributes identifying the scenario/RMS used to initialise this
* simulation.
*/
void SetInitAttributes(const CScriptValRooted& settings);
/**
* Get the data passed to SetInitAttributes.
*/
CScriptValRooted GetInitAttributes();
/**
* Set the initial map settings (as a UTF-8-encoded JSON string),
* which will be used to set up the simulation state.
@ -141,8 +152,8 @@ public:
*/
void InitGame(const CScriptVal& data);
bool Update(int turnLength);
bool Update(int turnLength, const std::vector<SimulationCommand>& commands);
void Update(int turnLength);
void Update(int turnLength, const std::vector<SimulationCommand>& commands);
void Interpolate(float frameLength, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);

View File

@ -68,7 +68,8 @@ public:
CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself
LDR_BeginRegistering();
mapReader->LoadMap(L"maps/scenarios/Median Oasis.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL, &sim2, -1);
mapReader->LoadMap(L"maps/scenarios/Median Oasis.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL,
&sim2, &sim2.GetSimContext(), -1, false);
LDR_EndRegistering();
TS_ASSERT_OK(LDR_NonprogressiveLoad());

View File

@ -52,8 +52,8 @@ std::string canonfloat(T value, int prec)
return r;
}
CDebugSerializer::CDebugSerializer(ScriptInterface& scriptInterface, std::ostream& stream) :
m_ScriptInterface(scriptInterface), m_Stream(stream), m_Indent(0)
CDebugSerializer::CDebugSerializer(ScriptInterface& scriptInterface, std::ostream& stream, bool includeDebugInfo) :
m_ScriptInterface(scriptInterface), m_Stream(stream), m_IsDebug(includeDebugInfo), m_Indent(0)
{
}
@ -170,7 +170,7 @@ void CDebugSerializer::PutRaw(const char* name, const u8* data, size_t len)
bool CDebugSerializer::IsDebug() const
{
return true;
return m_IsDebug;
}
std::ostream& CDebugSerializer::GetStream()

View File

@ -30,8 +30,9 @@ public:
/**
* @param scriptInterface Script interface corresponding to any jsvals passed to ScriptVal()
* @param stream Stream to receive UTF-8 encoded output
* @param includeDebugInfo If true then additional non-deterministic data will be included in the output
*/
CDebugSerializer(ScriptInterface& scriptInterface, std::ostream& stream);
CDebugSerializer(ScriptInterface& scriptInterface, std::ostream& stream, bool includeDebugInfo = true);
void Comment(const std::string& comment);
void TextLine(const std::string& text);
@ -59,6 +60,7 @@ protected:
private:
ScriptInterface& m_ScriptInterface;
std::ostream& m_Stream;
bool m_IsDebug;
int m_Indent;
};

View File

@ -208,7 +208,7 @@ public:
// Various state serialization functions:
bool ComputeStateHash(std::string& outHash, bool quick);
bool DumpDebugState(std::ostream& stream);
bool DumpDebugState(std::ostream& stream, bool includeDebugInfo);
// FlushDestroyedComponents must be called before SerializeState (since the destruction queue
// won't get serialized)
bool SerializeState(std::ostream& stream);

View File

@ -51,9 +51,9 @@ void DeserializeRNG(const std::string& str, boost::rand48& rng)
s >> rng;
}
bool CComponentManager::DumpDebugState(std::ostream& stream)
bool CComponentManager::DumpDebugState(std::ostream& stream, bool includeDebugInfo)
{
CDebugSerializer serializer(m_ScriptInterface, stream);
CDebugSerializer serializer(m_ScriptInterface, stream, includeDebugInfo);
serializer.StringASCII("rng", SerializeRNG(m_RNG), 0, 32);

View File

@ -564,7 +564,7 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent3, IID_Test2))->GetX(), 21000);
std::stringstream debugStream;
TS_ASSERT(man.DumpDebugState(debugStream));
TS_ASSERT(man.DumpDebugState(debugStream, true));
TS_ASSERT_STR_EQUALS(debugStream.str(),
"rng: \"78606\"\n"
"entities:\n"
@ -654,7 +654,7 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent3, IID_Test1))->GetX(), 8);
std::stringstream debugStream;
TS_ASSERT(man.DumpDebugState(debugStream));
TS_ASSERT(man.DumpDebugState(debugStream, true));
TS_ASSERT_STR_EQUALS(debugStream.str(),
"rng: \"78606\"\n"
"entities:\n"

View File

@ -499,7 +499,8 @@ public:
CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself
LDR_BeginRegistering();
mapReader->LoadMap(L"maps/scenarios/Latium.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL, &sim2, -1);
mapReader->LoadMap(L"maps/scenarios/Latium.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL,
&sim2, &sim2.GetSimContext(), -1, false);
LDR_EndRegistering();
TS_ASSERT_OK(LDR_NonprogressiveLoad());