Simplify component deserialization.

Deserialize SYSTEM_ENTITY before any other entities. This makes it safe
for Deserialize() methods to access system components (mirroring how
Init() can already access system components).

Add a Deserialized message, sent after all entities have been
deserialized, to help with some other sequencing problems.

This was SVN commit r15264.
This commit is contained in:
Ykkrosh 2014-06-01 18:14:09 +00:00
parent b30a9a6384
commit d117d96d22
5 changed files with 129 additions and 29 deletions

View File

@ -183,6 +183,24 @@ public:
int* progress;
};
/**
* Broadcast after the entire simulation state has been deserialized.
* Components should do all their self-contained work in their Deserialize
* function when possible. But any reinitialisation that depends on other
* components or other entities, that might not be constructed until later
* in the deserialization process, may be done in response to this message
* instead.
*/
class CMessageDeserialized : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(Deserialized)
CMessageDeserialized()
{
}
};
/**
* This is sent immediately after a new entity's components have all been created

View File

@ -38,6 +38,7 @@ MESSAGE(Update_Final)
MESSAGE(Interpolate) // non-deterministic (use with caution)
MESSAGE(RenderSubmit) // non-deterministic (use with caution)
MESSAGE(ProgressiveLoad) // non-deterministic (use with caution)
MESSAGE(Deserialized) // non-deterministic (use with caution)
MESSAGE(Create)
MESSAGE(Destroy)
MESSAGE(OwnershipChanged)

View File

@ -148,6 +148,20 @@ CMessage* CMessageProgressiveLoad::FromJSVal(ScriptInterface& UNUSED(scriptInter
////////////////////////////////
jsval CMessageDeserialized::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
LOGWARNING(L"CMessageDeserialized::ToJSVal not implemented");
return JSVAL_VOID;
}
CMessage* CMessageDeserialized::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
{
LOGWARNING(L"CMessageDeserialized::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
jsval CMessageCreate::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();

View File

@ -21,6 +21,8 @@
#include "IComponent.h"
#include "ParamNode.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/serialization/DebugSerializer.h"
#include "simulation2/serialization/HashSerializer.h"
#include "simulation2/serialization/StdSerializer.h"
@ -105,6 +107,7 @@ bool CComponentManager::ComputeStateHash(std::string& outHash, bool quick)
CHashSerializer serializer(m_ScriptInterface);
serializer.StringASCII("rng", SerializeRNG(m_RNG), 0, 32);
serializer.NumberU32_Unbounded("next entity id", m_NextEntityId);
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator cit = m_ComponentsByTypeId.begin();
for (; cit != m_ComponentsByTypeId.end(); ++cit)
@ -151,7 +154,12 @@ bool CComponentManager::ComputeStateHash(std::string& outHash, bool quick)
* Simulation state serialization format:
*
* TODO: Global version number.
* Number of (non-empty) component types.
* Number of SYSTEM_ENTITY component types
* For each SYSTEM_ENTITY component type:
* Component type name
* TODO: Component type version number.
* Component state.
* Number of (non-empty, non-SYSTEM_ENTITY-only) component types.
* For each component type:
* Component type name.
* TODO: Component type version number.
@ -182,7 +190,9 @@ bool CComponentManager::SerializeState(std::ostream& stream)
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator cit;
uint32_t numSystemComponentTypes = 0;
uint32_t numComponentTypes = 0;
std::set<ComponentTypeId> serializedSystemComponentTypes;
std::set<ComponentTypeId> serializedComponentTypes;
for (cit = m_ComponentsByTypeId.begin(); cit != m_ComponentsByTypeId.end(); ++cit)
@ -191,19 +201,50 @@ bool CComponentManager::SerializeState(std::ostream& stream)
bool needsSerialization = false;
for (std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin(); eit != cit->second.end(); ++eit)
{
// Don't serialize local entities
if (ENTITY_IS_LOCAL(eit->first))
// Don't serialize local entities, and handle SYSTEM_ENTITY separately
if (ENTITY_IS_LOCAL(eit->first) || eit->first == SYSTEM_ENTITY)
continue;
needsSerialization = true;
break;
}
if (!needsSerialization)
if (needsSerialization)
{
numComponentTypes++;
serializedComponentTypes.insert(cit->first);
}
if (cit->second.find(SYSTEM_ENTITY) != cit->second.end())
{
numSystemComponentTypes++;
serializedSystemComponentTypes.insert(cit->first);
}
}
serializer.NumberU32_Unbounded("num system component types", numSystemComponentTypes);
for (cit = m_ComponentsByTypeId.begin(); cit != m_ComponentsByTypeId.end(); ++cit)
{
if (serializedSystemComponentTypes.find(cit->first) == serializedSystemComponentTypes.end())
continue;
numComponentTypes++;
serializedComponentTypes.insert(cit->first);
std::map<ComponentTypeId, ComponentType>::const_iterator ctit = m_ComponentTypesById.find(cit->first);
if (ctit == m_ComponentTypesById.end())
{
debug_warn(L"Invalid ctit"); // this should never happen
return false;
}
serializer.StringASCII("name", ctit->second.name, 0, 255);
std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.find(SYSTEM_ENTITY);
if (eit == cit->second.end())
{
debug_warn(L"Invalid eit"); // this should never happen
return false;
}
eit->second->Serialize(serializer);
}
serializer.NumberU32_Unbounded("num component types", numComponentTypes);
@ -226,8 +267,8 @@ bool CComponentManager::SerializeState(std::ostream& stream)
uint32_t numComponents = 0;
for (std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin(); eit != cit->second.end(); ++eit)
{
// Don't serialize local entities
if (ENTITY_IS_LOCAL(eit->first))
// Don't serialize local entities or SYSTEM_ENTITY
if (ENTITY_IS_LOCAL(eit->first) || eit->first == SYSTEM_ENTITY)
continue;
numComponents++;
@ -239,8 +280,8 @@ bool CComponentManager::SerializeState(std::ostream& stream)
// Serialize the components now
for (std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin(); eit != cit->second.end(); ++eit)
{
// Don't serialize local entities
if (ENTITY_IS_LOCAL(eit->first))
// Don't serialize local entities or SYSTEM_ENTITY
if (ENTITY_IS_LOCAL(eit->first) || eit->first == SYSTEM_ENTITY)
continue;
serializer.NumberU32_Unbounded("entity id", eit->first);
@ -256,7 +297,6 @@ bool CComponentManager::DeserializeState(std::istream& stream)
{
try
{
CStdDeserializer deserializer(m_ScriptInterface, stream);
ResetState();
@ -268,12 +308,39 @@ bool CComponentManager::DeserializeState(std::istream& stream)
deserializer.NumberU32_Unbounded("next entity id", m_NextEntityId); // TODO: use sensible bounds
uint32_t numComponentTypes;
deserializer.NumberU32_Unbounded("num component types", numComponentTypes);
uint32_t numSystemComponentTypes;
deserializer.NumberU32_Unbounded("num system component types", numSystemComponentTypes);
ICmpTemplateManager* templateManager = NULL;
CParamNode noParam;
for (size_t i = 0; i < numSystemComponentTypes; ++i)
{
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;
}
IComponent* component = ConstructComponent(m_SystemEntity, ctid);
if (!component)
return false;
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 (ctid == CID_TemplateManager)
templateManager = static_cast<ICmpTemplateManager*> (component);
}
uint32_t numComponentTypes;
deserializer.NumberU32_Unbounded("num component types", numComponentTypes);
for (size_t i = 0; i < numComponentTypes; ++i)
{
std::string ctname;
@ -299,7 +366,7 @@ bool CComponentManager::DeserializeState(std::istream& stream)
// Try to find the template for this entity
const CParamNode* entTemplate = NULL;
if (templateManager && ent != SYSTEM_ENTITY) // (system entities don't use templates)
if (templateManager)
entTemplate = templateManager->LoadLatestTemplate(ent);
// Deserialize, with the appropriate template for this component
@ -307,11 +374,6 @@ bool CComponentManager::DeserializeState(std::istream& stream)
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);
}
}
@ -321,6 +383,10 @@ bool CComponentManager::DeserializeState(std::istream& stream)
return false;
}
// Allow components to do some final reinitialisation after everything is loaded
CMessageDeserialized msg;
BroadcastMessage(msg);
return true;
}
catch (PSERROR_Deserialize& e)

View File

@ -597,7 +597,7 @@ public:
CComponentManager man(context, g_ScriptRuntime);
man.LoadComponentTypes();
entity_id_t ent1 = 1, ent2 = 2, ent3 = FIRST_LOCAL_ENTITY;
entity_id_t ent1 = 10, ent2 = 20, ent3 = FIRST_LOCAL_ENTITY;
CEntityHandle hnd1 = man.AllocateEntityHandle(ent1);
CEntityHandle hnd2 = man.AllocateEntityHandle(ent2);
CEntityHandle hnd3 = man.AllocateEntityHandle(ent3);
@ -621,13 +621,13 @@ public:
TS_ASSERT_STR_EQUALS(debugStream.str(),
"rng: \"78606\"\n"
"entities:\n"
"- id: 1\n"
"- id: 10\n"
" Test1A:\n"
" x: 11000\n"
" Test2A:\n"
" x: 21000\n"
"\n"
"- id: 2\n"
"- id: 20\n"
" Test1A:\n"
" x: 1234\n"
"\n"
@ -641,25 +641,26 @@ public:
std::string hash;
TS_ASSERT(man.ComputeStateHash(hash, false));
TS_ASSERT_EQUALS(hash.length(), (size_t)16);
TS_ASSERT_SAME_DATA(hash.data(), "\x1c\x45\x2b\x20\x1f\x0c\x00\x93\x60\x78\xe2\x63\xb1\x47\x08\x19", 16);
// echo -en "\x05\x00\x00\x0078606\x01\0\0\0\x01\0\0\0\xf8\x2a\0\0\x02\0\0\0\xd2\x04\0\0\x04\0\0\0\x01\0\0\0\x08\x52\0\0" | openssl md5 | perl -pe 's/(..)/\\x$1/g'
// ^^^^^^^^ rng ^^^^^^^^ ^^Test1A^^ ^^^ent1^^ ^^^11000^^^ ^^^ent2^^ ^^^1234^^^ ^^Test2A^^ ^^ent1^^ ^^^21000^^^
TS_ASSERT_SAME_DATA(hash.data(), "\x3c\x25\x6e\x22\x58\x23\x09\x58\x38\xca\xb2\x1e\x0b\x8c\xac\xcf", 16);
// echo -en "\x05\x00\x00\x0078606\x02\0\0\0\x01\0\0\0\x0a\0\0\0\xf8\x2a\0\0\x14\0\0\0\xd2\x04\0\0\x04\0\0\0\x0a\0\0\0\x08\x52\0\0" | md5sum | perl -pe 's/([0-9a-f]{2})/\\x$1/g'
// ^^^^^^^^ rng ^^^^^^^^ ^^next^^ ^^Test1A^^ ^^^ent1^^ ^^^11000^^^ ^^^ent2^^ ^^^1234^^^ ^^Test2A^^ ^^ent1^^ ^^^21000^^^
std::stringstream stateStream;
TS_ASSERT(man.SerializeState(stateStream));
TS_ASSERT_STREAM(stateStream, 69,
TS_ASSERT_STREAM(stateStream, 73,
"\x05\x00\x00\x00\x37\x38\x36\x30\x36" // RNG
"\x02\x00\x00\x00" // next entity ID
"\x00\x00\x00\x00" // num system component types
"\x02\x00\x00\x00" // num component types
"\x06\x00\x00\x00Test1A"
"\x02\x00\x00\x00" // num ents
"\x01\x00\x00\x00" // ent1
"\x0a\x00\x00\x00" // ent1
"\xf8\x2a\x00\x00" // 11000
"\x02\x00\x00\x00" // ent2
"\x14\x00\x00\x00" // ent2
"\xd2\x04\x00\x00" // 1234
"\x06\x00\x00\x00Test2A"
"\x01\x00\x00\x00" // num ents
"\x01\x00\x00\x00" // ent1
"\x0a\x00\x00\x00" // ent1
"\x08\x52\x00\x00" // 21000
);