mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
# Partial support for saved games with AI.
Support cancelling loads while inside a loader callback. Fix use of ArchiveReader/Writer since their API changed. Improve error-detection in deserializer to avoid crashes. Report deserializer errors to users. Expand load-error message box to fit message about invalid saved games. This was SVN commit r10787.
This commit is contained in:
parent
5f0d5e4137
commit
6399ec0cd2
14 changed files with 202 additions and 117 deletions
|
|
@ -16,7 +16,7 @@ function cancelOnError(msg)
|
|||
if (msg)
|
||||
{
|
||||
Engine.PushGuiPage("page_msgbox.xml", {
|
||||
width: 400,
|
||||
width: 500,
|
||||
height: 200,
|
||||
message: '[font="serif-bold-18"]' + msg + '[/font]',
|
||||
title: "Loading Aborted",
|
||||
|
|
|
|||
|
|
@ -3,16 +3,41 @@ function BaseAI(settings)
|
|||
if (!settings)
|
||||
return;
|
||||
|
||||
// Make some properties non-enumerable, so they won't be serialised
|
||||
Object.defineProperty(this, "_player", {value: settings.player, enumerable: false});
|
||||
Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false});
|
||||
Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false});
|
||||
// Copies of static engine data (not serialized)
|
||||
this._player = settings.player;
|
||||
this._templates = settings.templates;
|
||||
this._derivedTemplates = {};
|
||||
|
||||
// Representation of the current world state (requires serialization)
|
||||
this._rawEntities = null;
|
||||
this._ownEntities = {};
|
||||
|
||||
this._entityMetadata = {};
|
||||
}
|
||||
|
||||
// Return a simple object (using no classes etc) that will be serialized
|
||||
// into saved games
|
||||
BaseAI.prototype.Serialize = function()
|
||||
{
|
||||
return {
|
||||
_rawEntities: this._rawEntities,
|
||||
_ownEntities: this._ownEntities,
|
||||
_entityMetadata: this._entityMetadata,
|
||||
};
|
||||
|
||||
// TODO: ought to get the AI script subclass to serialize its own state
|
||||
};
|
||||
|
||||
// Called after the constructor when loading a saved game, with 'data' being
|
||||
// whatever Serialize() returned
|
||||
BaseAI.prototype.Deserialize = function(data)
|
||||
{
|
||||
this._rawEntities = data._rawEntities;
|
||||
this._ownEntities = data._ownEntities;
|
||||
this._entityMetadata = data._entityMetadata;
|
||||
|
||||
// TODO: ought to get the AI script subclass to deserialize its own state
|
||||
};
|
||||
|
||||
// Components that will be disabled in foundation entity templates.
|
||||
// (This is a bit yucky and fragile since it's the inverse of
|
||||
// CCmpTemplateManager::CopyFoundationSubset and only includes components
|
||||
|
|
@ -160,7 +185,8 @@ BaseAI.prototype.ApplyEntitiesDelta = function(state)
|
|||
};
|
||||
|
||||
BaseAI.prototype.OnUpdate = function()
|
||||
{ // AIs override this function
|
||||
{
|
||||
// AIs override this function
|
||||
};
|
||||
|
||||
BaseAI.prototype.chat = function(message)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ function InitGame(settings)
|
|||
{
|
||||
// This will be called after the map settings have been loaded,
|
||||
// before the simulation has started.
|
||||
// This is only called at the start of a new game, not when loading
|
||||
// a saved game.
|
||||
|
||||
// No settings when loading a map in Atlas, so do nothing
|
||||
if (!settings)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include "ps/Profile.h"
|
||||
#include "ps/Replay.h"
|
||||
#include "ps/World.h"
|
||||
#include "ps/GameSetup/GameSetup.h"
|
||||
#include "renderer/Renderer.h"
|
||||
#include "scripting/ScriptingHost.h"
|
||||
#include "scriptinterface/ScriptInterface.h"
|
||||
|
|
@ -63,7 +64,8 @@ CGame::CGame(bool disableGraphics):
|
|||
m_GameStarted(false),
|
||||
m_Paused(false),
|
||||
m_SimRate(1.0f),
|
||||
m_PlayerID(-1)
|
||||
m_PlayerID(-1),
|
||||
m_IsSavedGame(false)
|
||||
{
|
||||
m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
|
||||
// TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps?
|
||||
|
|
@ -115,6 +117,7 @@ void CGame::SetTurnManager(CNetTurnManager* turnManager)
|
|||
void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& savedState)
|
||||
{
|
||||
m_InitialSavedState = savedState;
|
||||
m_IsSavedGame = !savedState.empty();
|
||||
|
||||
m_Simulation2->SetInitAttributes(attribs);
|
||||
|
||||
|
|
@ -153,7 +156,7 @@ void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& sav
|
|||
m_World->RegisterInitRMS(scriptFile, settings, m_PlayerID);
|
||||
}
|
||||
|
||||
if (!m_InitialSavedState.empty())
|
||||
if (m_IsSavedGame)
|
||||
RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
|
||||
|
||||
LDR_EndRegistering();
|
||||
|
|
@ -161,6 +164,7 @@ void CGame::RegisterInit(const CScriptValRooted& attribs, const std::string& sav
|
|||
|
||||
int CGame::LoadInitialState()
|
||||
{
|
||||
ENSURE(m_IsSavedGame);
|
||||
ENSURE(!m_InitialSavedState.empty());
|
||||
|
||||
std::string state;
|
||||
|
|
@ -169,7 +173,11 @@ int CGame::LoadInitialState()
|
|||
std::stringstream stream(state);
|
||||
|
||||
bool ok = m_Simulation2->DeserializeState(stream);
|
||||
ENSURE(ok);
|
||||
if (!ok)
|
||||
{
|
||||
CancelLoad(L"Failed to load saved game state. It might have been\nsaved with an incompatible version of the game.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -181,9 +189,13 @@ int CGame::LoadInitialState()
|
|||
**/
|
||||
PSRETURN CGame::ReallyStartGame()
|
||||
{
|
||||
CScriptVal settings;
|
||||
m_Simulation2->GetScriptInterface().GetProperty(m_Simulation2->GetInitAttributes().get(), "settings", settings);
|
||||
m_Simulation2->InitGame(settings);
|
||||
// Call the script function InitGame only for new games, not saved games
|
||||
if (!m_IsSavedGame)
|
||||
{
|
||||
CScriptVal 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
|
||||
if (g_GUI && g_GUI->HasPages())
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ private:
|
|||
|
||||
int LoadInitialState();
|
||||
std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState
|
||||
bool m_IsSavedGame; // true if loading a saved game; false for a new game
|
||||
};
|
||||
|
||||
extern CGame *g_Game;
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_
|
|||
{
|
||||
state = LOADING;
|
||||
|
||||
ret = ERR::TIMED_OUT; // make caller think we did something
|
||||
ret = ERR::TIMED_OUT; // make caller think we did something
|
||||
// progress already set to 0.0; that'll be passed back.
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_
|
|||
const double estimated_duration = lr.estimated_duration_ms*1e-3;
|
||||
if(!HaveTimeForNextTask(time_left, time_budget, lr.estimated_duration_ms))
|
||||
{
|
||||
ret = ERR::TIMED_OUT;
|
||||
ret = ERR::TIMED_OUT;
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_
|
|||
// .. function interrupted itself, i.e. timed out; abort.
|
||||
if(timed_out)
|
||||
{
|
||||
ret = ERR::TIMED_OUT;
|
||||
ret = ERR::TIMED_OUT;
|
||||
goto done;
|
||||
}
|
||||
// .. failed; abort. loading will continue when we're called in
|
||||
|
|
@ -270,6 +270,13 @@ Status LDR_ProgressiveLoad(double time_budget, wchar_t* description, size_t max_
|
|||
ret = (Status)status;
|
||||
goto done;
|
||||
}
|
||||
// .. function called LDR_Cancel; abort. return OK since this is an
|
||||
// intentional cancellation, not an error.
|
||||
else if(state != LOADING)
|
||||
{
|
||||
ret = INFO::OK;
|
||||
goto done;
|
||||
}
|
||||
// .. succeeded; continue and process next queued task.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,15 +75,9 @@ Status SavedGames::Save(const std::wstring& prefix, CSimulation2& simulation, CG
|
|||
std::string metadataString = simulation.GetScriptInterface().StringifyJSON(metadata.get(), true);
|
||||
|
||||
// Write the saved game as zip file containing the various components
|
||||
PIArchiveWriter archiveWriter;
|
||||
try
|
||||
{
|
||||
archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false);
|
||||
}
|
||||
catch (Status err)
|
||||
{
|
||||
WARN_RETURN(err);
|
||||
}
|
||||
PIArchiveWriter archiveWriter = CreateArchiveWriter_Zip(tempSaveFileRealPath, false);
|
||||
if (!archiveWriter)
|
||||
WARN_RETURN(ERR::FAIL);
|
||||
|
||||
WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)metadataString.c_str(), metadataString.length(), now, "metadata.json"));
|
||||
WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)simStateStream.str().c_str(), simStateStream.str().length(), now, "simulation.dat"));
|
||||
|
|
@ -147,15 +141,9 @@ Status SavedGames::Load(const std::wstring& name, ScriptInterface& scriptInterfa
|
|||
OsPath realPath;
|
||||
WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
|
||||
|
||||
PIArchiveReader archiveReader;
|
||||
try
|
||||
{
|
||||
archiveReader = CreateArchiveReader_Zip(realPath);
|
||||
}
|
||||
catch (Status err)
|
||||
{
|
||||
WARN_RETURN(err);
|
||||
}
|
||||
PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath);
|
||||
if (!archiveReader)
|
||||
WARN_RETURN(ERR::FAIL);
|
||||
|
||||
CGameLoader loader(scriptInterface, &metadata, &savedState);
|
||||
WARN_RETURN_STATUS_IF_ERR(archiveReader->ReadEntries(CGameLoader::ReadEntryCallback, (uintptr_t)&loader));
|
||||
|
|
@ -185,14 +173,11 @@ std::vector<CScriptValRooted> SavedGames::GetSavedGames(ScriptInterface& scriptI
|
|||
continue; // skip this file
|
||||
}
|
||||
|
||||
PIArchiveReader archiveReader;
|
||||
try
|
||||
PIArchiveReader archiveReader = CreateArchiveReader_Zip(realPath);
|
||||
if (!archiveReader)
|
||||
{
|
||||
archiveReader = CreateArchiveReader_Zip(realPath);
|
||||
}
|
||||
catch (Status err)
|
||||
{
|
||||
DEBUG_WARN_ERR(err);
|
||||
// Triggered by e.g. the file being open in another program
|
||||
LOGWARNING(L"Failed to read saved game '%ls'", realPath.string().c_str());
|
||||
continue; // skip this file
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -638,8 +638,7 @@ ScriptInterface& CSimulation2::GetScriptInterface() const
|
|||
|
||||
void CSimulation2::InitGame(const CScriptVal& data)
|
||||
{
|
||||
CScriptVal ret; // ignored
|
||||
GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret);
|
||||
GetScriptInterface().CallFunctionVoid(GetScriptInterface().GetGlobalObject(), "InitGame", data);
|
||||
}
|
||||
|
||||
void CSimulation2::Update(int turnLength)
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ private:
|
|||
CScriptVal settings;
|
||||
m_ScriptInterface.Eval(L"({})", settings);
|
||||
m_ScriptInterface.SetProperty(settings.get(), "player", m_Player, false);
|
||||
ENSURE(m_Worker.m_HasLoadedEntityTemplates);
|
||||
m_ScriptInterface.SetProperty(settings.get(), "templates", m_Worker.m_EntityTemplates, false);
|
||||
|
||||
obj = m_ScriptInterface.CallConstructor(ctor.get(), settings.get());
|
||||
|
|
@ -218,6 +219,7 @@ private:
|
|||
{
|
||||
// For deserialization, we want to create the object with the correct prototype
|
||||
// but don't want to actually run the constructor again
|
||||
// XXX: actually we don't currently use this path for deserialization - maybe delete it?
|
||||
obj = m_ScriptInterface.NewObjectFromConstructor(ctor.get());
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +260,8 @@ public:
|
|||
m_ScriptRuntime(ScriptInterface::CreateRuntime()),
|
||||
m_ScriptInterface("Engine", "AI", m_ScriptRuntime),
|
||||
m_TurnNum(0),
|
||||
m_CommandsComputed(true)
|
||||
m_CommandsComputed(true),
|
||||
m_HasLoadedEntityTemplates(false)
|
||||
{
|
||||
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
|
||||
|
||||
|
|
@ -337,6 +340,8 @@ public:
|
|||
|
||||
void LoadEntityTemplates(const std::vector<std::pair<std::string, const CParamNode*> >& templates)
|
||||
{
|
||||
m_HasLoadedEntityTemplates = true;
|
||||
|
||||
m_ScriptInterface.Eval("({})", m_EntityTemplates);
|
||||
|
||||
for (size_t i = 0; i < templates.size(); ++i)
|
||||
|
|
@ -369,13 +374,18 @@ public:
|
|||
|
||||
void SerializeState(ISerializer& serializer)
|
||||
{
|
||||
std::stringstream rngStream;
|
||||
rngStream << m_RNG;
|
||||
serializer.StringASCII("rng", rngStream.str(), 0, 32);
|
||||
|
||||
serializer.NumberU32_Unbounded("turn", m_TurnNum);
|
||||
|
||||
serializer.NumberU32_Unbounded("num ais", (u32)m_Players.size());
|
||||
|
||||
for (size_t i = 0; i < m_Players.size(); ++i)
|
||||
{
|
||||
serializer.String("name", m_Players[i]->m_AIName, 0, 256);
|
||||
serializer.String("name", m_Players[i]->m_AIName, 1, 256);
|
||||
serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player);
|
||||
serializer.ScriptVal("data", m_Players[i]->m_Obj);
|
||||
|
||||
serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size());
|
||||
for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j)
|
||||
|
|
@ -383,6 +393,11 @@ public:
|
|||
CScriptVal val = m_ScriptInterface.ReadStructuredClone(m_Players[i]->m_Commands[j]);
|
||||
serializer.ScriptVal("command", val);
|
||||
}
|
||||
|
||||
CScriptVal scriptData;
|
||||
if (!m_ScriptInterface.CallFunction(m_Players[i]->m_Obj.get(), "Serialize", scriptData))
|
||||
LOGERROR(L"AI script Serialize call failed");
|
||||
serializer.ScriptVal("data", scriptData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -395,6 +410,14 @@ public:
|
|||
m_PlayerMetadata.clear();
|
||||
m_Players.clear();
|
||||
|
||||
std::string rngString;
|
||||
std::stringstream rngStream;
|
||||
deserializer.StringASCII("rng", rngString, 0, 32);
|
||||
rngStream << rngString;
|
||||
rngStream >> m_RNG;
|
||||
|
||||
deserializer.NumberU32_Unbounded("turn", m_TurnNum);
|
||||
|
||||
uint32_t numAis;
|
||||
deserializer.NumberU32_Unbounded("num ais", numAis);
|
||||
|
||||
|
|
@ -402,15 +425,11 @@ public:
|
|||
{
|
||||
std::wstring name;
|
||||
player_id_t player;
|
||||
deserializer.String("name", name, 0, 256);
|
||||
deserializer.String("name", name, 1, 256);
|
||||
deserializer.NumberI32_Unbounded("player", player);
|
||||
if (!AddPlayer(name, player, false))
|
||||
if (!AddPlayer(name, player, true))
|
||||
throw PSERROR_Deserialize_ScriptError();
|
||||
|
||||
// Use ScriptObjectAppend so we don't lose the carefully-constructed
|
||||
// prototype/parent of this object
|
||||
deserializer.ScriptObjectAppend("data", m_Players.back()->m_Obj.getRef());
|
||||
|
||||
uint32_t numCommands;
|
||||
deserializer.NumberU32_Unbounded("num commands", numCommands);
|
||||
m_Players.back()->m_Commands.reserve(numCommands);
|
||||
|
|
@ -420,6 +439,11 @@ public:
|
|||
deserializer.ScriptVal("command", val);
|
||||
m_Players.back()->m_Commands.push_back(m_ScriptInterface.WriteStructuredClone(val.get()));
|
||||
}
|
||||
|
||||
CScriptVal scriptData;
|
||||
deserializer.ScriptVal("data", scriptData);
|
||||
if (!m_ScriptInterface.CallFunctionVoid(m_Players.back()->m_Obj.get(), "Deserialize", scriptData))
|
||||
LOGERROR(L"AI script Deserialize call failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -473,9 +497,11 @@ private:
|
|||
shared_ptr<ScriptRuntime> m_ScriptRuntime;
|
||||
ScriptInterface m_ScriptInterface;
|
||||
boost::rand48 m_RNG;
|
||||
size_t m_TurnNum;
|
||||
u32 m_TurnNum;
|
||||
|
||||
CScriptValRooted m_EntityTemplates;
|
||||
bool m_HasLoadedEntityTemplates;
|
||||
|
||||
std::map<VfsPath, CScriptValRooted> m_PlayerMetadata;
|
||||
std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying
|
||||
|
||||
|
|
@ -523,17 +549,16 @@ public:
|
|||
// directly. So we'll just grab the ISerializer's stream and write to it
|
||||
// with an independent serializer.
|
||||
|
||||
// TODO: make the serialization/deserialization actually work, and not really slowly
|
||||
// m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
|
||||
UNUSED2(serialize);
|
||||
m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
|
||||
}
|
||||
|
||||
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
|
||||
{
|
||||
Init(paramNode);
|
||||
|
||||
// m_Worker.Deserialize(deserialize.GetStream());
|
||||
UNUSED2(deserialize);
|
||||
ForceLoadEntityTemplates();
|
||||
|
||||
m_Worker.Deserialize(deserialize.GetStream());
|
||||
}
|
||||
|
||||
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
|
||||
|
|
|
|||
|
|
@ -163,7 +163,9 @@ void IDeserializer::StringASCII(const char* name, std::string& out, uint32_t min
|
|||
{
|
||||
uint32_t len;
|
||||
NumberU32("string length", len, minlength, maxlength);
|
||||
out.resize(len); // TODO: should check len <= bytes remaining in stream
|
||||
|
||||
RequireBytesInStream(len);
|
||||
out.resize(len);
|
||||
Get(name, (u8*)out.data(), len);
|
||||
|
||||
for (size_t i = 0; i < out.length(); ++i)
|
||||
|
|
@ -176,7 +178,9 @@ void IDeserializer::String(const char* name, std::wstring& out, uint32_t minleng
|
|||
std::string str;
|
||||
uint32_t len;
|
||||
NumberU32_Unbounded("string length", len);
|
||||
str.resize(len); // TODO: should check len <= bytes remaining in stream
|
||||
|
||||
RequireBytesInStream(len);
|
||||
str.resize(len);
|
||||
Get(name, (u8*)str.data(), len);
|
||||
|
||||
Status err;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,14 @@ public:
|
|||
*/
|
||||
virtual std::istream& GetStream() = 0;
|
||||
|
||||
/**
|
||||
* Throws an exception if the stream cannot provide the required number of
|
||||
* bytes. (This should be used when allocating memory based on data in the
|
||||
* stream, e.g. reading strings, to avoid dangerously large allocations
|
||||
* when the data is invalid.)
|
||||
*/
|
||||
virtual void RequireBytesInStream(size_t numBytes) = 0;
|
||||
|
||||
protected:
|
||||
virtual void Get(const char* name, u8* data, size_t len) = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ std::istream& CStdDeserializer::GetStream()
|
|||
return m_Stream;
|
||||
}
|
||||
|
||||
void CStdDeserializer::RequireBytesInStream(size_t numBytes)
|
||||
{
|
||||
if (numBytes >= m_Stream.rdbuf()->in_avail())
|
||||
throw PSERROR_Deserialize_OutOfBounds("RequireBytesInStream");
|
||||
}
|
||||
|
||||
void CStdDeserializer::AddScriptBackref(JSObject* obj)
|
||||
{
|
||||
std::pair<std::map<u32, JSObject*>::iterator, bool> it = m_ScriptBackrefs.insert(std::make_pair((u32)m_ScriptBackrefs.size()+1, obj));
|
||||
|
|
@ -197,7 +203,8 @@ void CStdDeserializer::ReadStringUTF16(const char* name, utf16string& str)
|
|||
{
|
||||
uint32_t len;
|
||||
NumberU32_Unbounded("string length", len);
|
||||
str.resize(len); // TODO: should check len*2 <= bytes remaining in stream, before resizing
|
||||
RequireBytesInStream(len*2);
|
||||
str.resize(len);
|
||||
Get(name, (u8*)str.data(), len*2);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ public:
|
|||
virtual void ScriptString(const char* name, JSString*& out);
|
||||
|
||||
virtual std::istream& GetStream();
|
||||
|
||||
virtual void RequireBytesInStream(size_t numBytes);
|
||||
|
||||
protected:
|
||||
virtual void Get(const char* name, u8* data, size_t len);
|
||||
|
||||
|
|
|
|||
|
|
@ -261,69 +261,77 @@ bool CComponentManager::SerializeState(std::ostream& stream)
|
|||
|
||||
bool CComponentManager::DeserializeState(std::istream& stream)
|
||||
{
|
||||
CStdDeserializer deserializer(m_ScriptInterface, stream);
|
||||
|
||||
ResetState();
|
||||
|
||||
std::string rng;
|
||||
deserializer.StringASCII("rng", rng, 0, 32);
|
||||
DeserializeRNG(rng, m_RNG);
|
||||
|
||||
deserializer.NumberU32_Unbounded("next entity id", m_NextEntityId); // TODO: use sensible bounds
|
||||
|
||||
uint32_t numComponentTypes;
|
||||
deserializer.NumberU32_Unbounded("num component types", numComponentTypes);
|
||||
|
||||
ICmpTemplateManager* templateManager = NULL;
|
||||
CParamNode noParam;
|
||||
|
||||
for (size_t i = 0; i < numComponentTypes; ++i)
|
||||
try
|
||||
{
|
||||
std::string ctname;
|
||||
deserializer.StringASCII("name", ctname, 0, 255);
|
||||
|
||||
ComponentTypeId ctid = LookupCID(ctname);
|
||||
if (ctid == CID__Invalid)
|
||||
CStdDeserializer deserializer(m_ScriptInterface, stream);
|
||||
|
||||
ResetState();
|
||||
|
||||
std::string rng;
|
||||
deserializer.StringASCII("rng", rng, 0, 32);
|
||||
DeserializeRNG(rng, m_RNG);
|
||||
|
||||
deserializer.NumberU32_Unbounded("next entity id", m_NextEntityId); // TODO: use sensible bounds
|
||||
|
||||
uint32_t numComponentTypes;
|
||||
deserializer.NumberU32_Unbounded("num component types", numComponentTypes);
|
||||
|
||||
ICmpTemplateManager* templateManager = NULL;
|
||||
CParamNode noParam;
|
||||
|
||||
for (size_t i = 0; i < numComponentTypes; ++i)
|
||||
{
|
||||
LOGERROR(L"Deserialization saw unrecognised component type '%hs'", ctname.c_str());
|
||||
std::string ctname;
|
||||
deserializer.StringASCII("name", ctname, 0, 255);
|
||||
|
||||
ComponentTypeId ctid = LookupCID(ctname);
|
||||
if (ctid == CID__Invalid)
|
||||
{
|
||||
LOGERROR(L"Deserialization saw unrecognised component type '%hs'", ctname.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t numComponents;
|
||||
deserializer.NumberU32_Unbounded("num components", numComponents);
|
||||
|
||||
for (size_t j = 0; j < numComponents; ++j)
|
||||
{
|
||||
entity_id_t ent;
|
||||
deserializer.NumberU32_Unbounded("entity id", ent);
|
||||
IComponent* component = ConstructComponent(ent, ctid);
|
||||
if (!component)
|
||||
return false;
|
||||
|
||||
// Try to find the template for this entity
|
||||
const CParamNode* entTemplate = NULL;
|
||||
if (templateManager && ent != SYSTEM_ENTITY) // (system entities don't use templates)
|
||||
entTemplate = templateManager->LoadLatestTemplate(ent);
|
||||
|
||||
// Deserialize, with the appropriate template for this component
|
||||
if (entTemplate)
|
||||
component->Deserialize(entTemplate->GetChild(ctname.c_str()), deserializer);
|
||||
else
|
||||
component->Deserialize(noParam, deserializer);
|
||||
|
||||
// If this was the template manager, remember it so we can use it when
|
||||
// deserializing any further non-system entities
|
||||
if (ent == SYSTEM_ENTITY && ctid == CID_TemplateManager)
|
||||
templateManager = static_cast<ICmpTemplateManager*> (component);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.peek() != EOF)
|
||||
{
|
||||
LOGERROR(L"Deserialization didn't reach EOF");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t numComponents;
|
||||
deserializer.NumberU32_Unbounded("num components", numComponents);
|
||||
|
||||
for (size_t j = 0; j < numComponents; ++j)
|
||||
{
|
||||
entity_id_t ent;
|
||||
deserializer.NumberU32_Unbounded("entity id", ent);
|
||||
IComponent* component = ConstructComponent(ent, ctid);
|
||||
if (!component)
|
||||
return false;
|
||||
|
||||
// Try to find the template for this entity
|
||||
const CParamNode* entTemplate = NULL;
|
||||
if (templateManager && ent != SYSTEM_ENTITY) // (system entities don't use templates)
|
||||
entTemplate = templateManager->LoadLatestTemplate(ent);
|
||||
|
||||
// Deserialize, with the appropriate template for this component
|
||||
if (entTemplate)
|
||||
component->Deserialize(entTemplate->GetChild(ctname.c_str()), deserializer);
|
||||
else
|
||||
component->Deserialize(noParam, deserializer);
|
||||
|
||||
// If this was the template manager, remember it so we can use it when
|
||||
// deserializing any further non-system entities
|
||||
if (ent == SYSTEM_ENTITY && ctid == CID_TemplateManager)
|
||||
templateManager = static_cast<ICmpTemplateManager*> (component);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stream.peek() != EOF)
|
||||
catch (PSERROR_Deserialize& e)
|
||||
{
|
||||
LOGERROR(L"Deserialization didn't reach EOF");
|
||||
LOGERROR(L"Deserialization failed: %hs", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: catch exceptions
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue