1
0
forked from 0ad/0ad

# Object creation in Atlas with new simulation system

Merge from 6e8efe8f706c

This was SVN commit r7276.
This commit is contained in:
Ykkrosh 2010-01-14 20:36:29 +00:00
parent f5632af192
commit c8138208bf
17 changed files with 547 additions and 97 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="actor|example">test</Entity>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity>
<VisualActor>
<Actor>example</Actor>
</VisualActor>
<Selectable/>
<Position>
<Altitude>0</Altitude>
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<UnitMotion/>
</Entity>

View File

@ -44,16 +44,13 @@ public:
m_SimContext.m_Terrain = terrain; m_SimContext.m_Terrain = terrain;
m_ComponentManager.LoadComponentTypes(); m_ComponentManager.LoadComponentTypes();
m_NextId = SYSTEM_ENTITY + 1;
m_DeltaTime = 0.0;
// (can't call ResetState here since the scripts haven't been loaded yet) // (can't call ResetState here since the scripts haven't been loaded yet)
} }
void ResetState(bool skipGui) void ResetState(bool skipGui)
{ {
m_ComponentManager.DestroyAllComponents(); m_ComponentManager.ResetState();
m_NextId = SYSTEM_ENTITY + 1;
m_DeltaTime = 0.0; m_DeltaTime = 0.0;
CParamNode noParam; CParamNode noParam;
@ -77,17 +74,15 @@ public:
bool LoadScripts(const VfsPath& path); bool LoadScripts(const VfsPath& path);
LibError ReloadChangedFile(const VfsPath& path); LibError ReloadChangedFile(const VfsPath& path);
entity_id_t AllocateNewEntity();
void AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& paramNode); void AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& paramNode);
entity_id_t AddEntity(const std::wstring& templateName, entity_id_t preferredId); entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent);
void Update(float frameTime); void Update(float frameTime);
void Interpolate(float frameTime); void Interpolate(float frameTime);
CSimContext m_SimContext; CSimContext m_SimContext;
CComponentManager m_ComponentManager; CComponentManager m_ComponentManager;
entity_id_t m_NextId;
double m_DeltaTime; double m_DeltaTime;
std::set<std::wstring> m_LoadedScripts; std::set<std::wstring> m_LoadedScripts;
@ -134,28 +129,17 @@ LibError CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
return INFO::OK; return INFO::OK;
} }
entity_id_t CSimulation2Impl::AllocateNewEntity()
{
return m_NextId++;
}
void CSimulation2Impl::AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& paramNode) void CSimulation2Impl::AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& paramNode)
{ {
m_ComponentManager.AddComponent(ent, cid, paramNode); m_ComponentManager.AddComponent(ent, cid, paramNode);
} }
entity_id_t CSimulation2Impl::AddEntity(const std::wstring& templateName, entity_id_t preferredId) entity_id_t CSimulation2Impl::AddEntity(const std::wstring& templateName, entity_id_t ent)
{ {
CmpPtr<ICmpTemplateManager> tempMan(m_SimContext, SYSTEM_ENTITY); CmpPtr<ICmpTemplateManager> tempMan(m_SimContext, SYSTEM_ENTITY);
debug_assert(!tempMan.null()); debug_assert(!tempMan.null());
entity_id_t ent = preferredId; // TODO: should assert that ent doesn't exist
// TODO: should check if this entity is already defined (might happen with bogus map files)
// and choose a new ID in that case
// Make sure any newly allocated IDs won't conflict with ones that are already added
if (m_NextId <= ent)
m_NextId = ent + 1;
const CParamNode* tmpl = tempMan->LoadTemplate(ent, templateName, -1); const CParamNode* tmpl = tempMan->LoadTemplate(ent, templateName, -1);
if (!tmpl) if (!tmpl)
@ -230,12 +214,27 @@ CSimulation2::~CSimulation2()
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName) entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
{ {
return m->AddEntity(templateName, m->AllocateNewEntity()); return m->AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
} }
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId) entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
{ {
return m->AddEntity(templateName, preferredId); return m->AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
}
entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
{
return m->AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
}
void CSimulation2::DestroyEntity(entity_id_t ent)
{
m->m_ComponentManager.DestroyComponentsSoon(ent);
}
void CSimulation2::FlushDestroyedEntities()
{
m->m_ComponentManager.FlushDestroyedComponents();
} }
IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const
@ -258,11 +257,6 @@ const CSimulation2::InterfaceList& CSimulation2::GetEntitiesWithInterface(int ii
return m->m_ComponentManager.GetEntitiesWithInterface(iid); return m->m_ComponentManager.GetEntitiesWithInterface(iid);
} }
entity_id_t CSimulation2::AllocateNewEntity()
{
return m->AllocateNewEntity();
}
const CSimContext& CSimulation2::GetSimContext() const const CSimContext& CSimulation2::GetSimContext() const
{ {
return m->m_SimContext; return m->m_SimContext;
@ -310,13 +304,11 @@ bool CSimulation2::DumpDebugState(std::ostream& stream)
bool CSimulation2::SerializeState(std::ostream& stream) bool CSimulation2::SerializeState(std::ostream& stream)
{ {
// TODO: need to save m->m_NextId
return m->m_ComponentManager.SerializeState(stream); return m->m_ComponentManager.SerializeState(stream);
} }
bool CSimulation2::DeserializeState(std::istream& stream) bool CSimulation2::DeserializeState(std::istream& stream)
{ {
// TODO: need to update m->m_NextId
// TODO: need to make sure the required SYSTEM_ENTITY components get constructed // TODO: need to make sure the required SYSTEM_ENTITY components get constructed
return m->m_ComponentManager.DeserializeState(stream); return m->m_ComponentManager.DeserializeState(stream);
} }

View File

@ -71,8 +71,27 @@ public:
void Update(float frameTime); void Update(float frameTime);
void Interpolate(float frameTime); void Interpolate(float frameTime);
/**
* Construct a new entity and add it to the world.
* @param templateName see ICmpTemplateManager for syntax
* @return the new entity ID, or INVALID_ENTITY on error
*/
entity_id_t AddEntity(const std::wstring& templateName); entity_id_t AddEntity(const std::wstring& templateName);
entity_id_t AddEntity(const std::wstring& templateName, entity_id_t preferredId); entity_id_t AddEntity(const std::wstring& templateName, entity_id_t preferredId);
entity_id_t AddLocalEntity(const std::wstring& templateName);
/**
* Destroys the specified entity, once FlushDestroyedEntities is called.
* Has no effect if the entity does not exist, or has already been added to the destruction queue.
*/
void DestroyEntity(entity_id_t ent);
/**
* Does the actual destruction of entities from DestroyEntity.
* This should be called at the beginning of each frame or after an Update message.
*/
void FlushDestroyedEntities();
IComponent* QueryInterface(entity_id_t ent, int iid) const; IComponent* QueryInterface(entity_id_t ent, int iid) const;
void PostMessage(entity_id_t ent, const CMessage& msg) const; void PostMessage(entity_id_t ent, const CMessage& msg) const;
void BroadcastMessage(const CMessage& msg) const; void BroadcastMessage(const CMessage& msg) const;
@ -88,8 +107,6 @@ public:
bool SerializeState(std::ostream& stream); bool SerializeState(std::ostream& stream);
bool DeserializeState(std::istream& stream); bool DeserializeState(std::istream& stream);
entity_id_t AllocateNewEntity();
private: private:
CSimulation2Impl* m; CSimulation2Impl* m;

View File

@ -88,8 +88,10 @@ public:
virtual std::vector<std::wstring> FindAllTemplates(); virtual std::vector<std::wstring> FindAllTemplates();
private: private:
// Map from template XML filename to last loaded valid template data // Map from template name (XML filename or special |-separated string) to the most recently
// (We store "last loaded valid" to behave more nicely when hotloading broken files) // loaded valid template data.
// (Failed loads won't remove valid entries under the same name, so we behave more nicely
// when hotloading broken files)
std::map<std::wstring, CParamNode> m_TemplateFileData; std::map<std::wstring, CParamNode> m_TemplateFileData;
// Remember the template used by each entity, so we can return them // Remember the template used by each entity, so we can return them
@ -100,10 +102,15 @@ private:
// (Re)loads the given template, regardless of whether it exists already, // (Re)loads the given template, regardless of whether it exists already,
// and saves into m_TemplateFileData. Also loads any parents that are not yet // and saves into m_TemplateFileData. Also loads any parents that are not yet
// loaded. Returns false on error. // loaded. Returns false on error.
// @param templateName XML filename to load (not a |-separated string)
bool LoadTemplateFile(const std::wstring& templateName, int depth); bool LoadTemplateFile(const std::wstring& templateName, int depth);
// Constructs a standard static-decorative-object template for the given actor // Constructs a standard static-decorative-object template for the given actor
void ConstructTemplateActor(const std::wstring& actorName, CParamNode& out); void ConstructTemplateActor(const std::wstring& actorName, CParamNode& out);
// Copy the non-interactive components of an entity template (position, actor, etc) into
// a new entity template
void CopyPreviewSubset(CParamNode& out, const CParamNode& in);
}; };
REGISTER_COMPONENT_TYPE(TemplateManager) REGISTER_COMPONENT_TYPE(TemplateManager)
@ -112,24 +119,11 @@ const CParamNode* CCmpTemplateManager::LoadTemplate(entity_id_t ent, const std::
{ {
m_LatestTemplates[ent] = templateName; m_LatestTemplates[ent] = templateName;
bool isNew = (m_TemplateFileData.find(templateName) == m_TemplateFileData.end()); // Load the template if necessary
if (!LoadTemplateFile(templateName, 0))
if (isNew)
{ {
// Handle special case "actor|foo" LOGERROR(L"Failed to load entity template '%ls'", templateName.c_str());
if (templateName.find(L"actor|") == 0) return NULL;
{
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
}
else
{
// Load it as a plain XML file
if (!LoadTemplateFile(templateName, 0))
{
LOGERROR(L"Failed to load entity template '%ls'", templateName.c_str());
return NULL;
}
}
} }
// TODO: Eventually we need to support techs in here, and return a different template per playerID // TODO: Eventually we need to support techs in here, and return a different template per playerID
@ -164,6 +158,10 @@ std::wstring CCmpTemplateManager::GetCurrentTemplateName(entity_id_t ent)
bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int depth) bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int depth)
{ {
// If this file was already loaded, we don't need to do anything
if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
return true;
// Handle infinite loops more gracefully than running out of stack space and crashing // Handle infinite loops more gracefully than running out of stack space and crashing
if (depth > 100) if (depth > 100)
{ {
@ -171,6 +169,30 @@ bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int
return false; return false;
} }
// Handle special case "actor|foo"
if (templateName.find(L"actor|") == 0)
{
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
return true;
}
// Handle special case "preview|foo"
if (templateName.find(L"preview|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::wstring baseName = templateName.substr(8);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%ls'", baseName.c_str());
return NULL;
}
// Copy a subset to the requested template
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Normal case: templateName is an XML file:
VfsPath path = VfsPath(TEMPLATE_ROOT) / (templateName + L".xml"); VfsPath path = VfsPath(TEMPLATE_ROOT) / (templateName + L".xml");
CXeromyces xero; CXeromyces xero;
PSRETURN ok = xero.Load(path); PSRETURN ok = xero.Load(path);
@ -183,14 +205,18 @@ bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int
{ {
std::wstring parentName(parentStr.begin(), parentStr.end()); std::wstring parentName(parentStr.begin(), parentStr.end());
// If the parent wasn't already loaded, then load it. // To prevent needless complexity in template design, we don't allow |-separated strings as parents
if (m_TemplateFileData.find(parentName) == m_TemplateFileData.end()) if (parentName.find('|') != parentName.npos)
{ {
if (!LoadTemplateFile(parentName, depth+1)) LOGERROR(L"Invalid parent '%ls' in entity template '%ls'", parentName.c_str(), templateName.c_str());
{ return false;
LOGERROR(L"Failed to load parent '%ls' of entity template '%ls'", parentName.c_str(), templateName.c_str()); }
return false;
} // Ensure the parent is loaded
if (!LoadTemplateFile(parentName, depth+1))
{
LOGERROR(L"Failed to load parent '%ls' of entity template '%ls'", parentName.c_str(), templateName.c_str());
return false;
} }
CParamNode& parentData = m_TemplateFileData[parentName]; CParamNode& parentData = m_TemplateFileData[parentName];
@ -228,8 +254,10 @@ static LibError AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(f
{ {
std::vector<std::wstring>& templates = *(std::vector<std::wstring>*)cbData; std::vector<std::wstring>& templates = *(std::vector<std::wstring>*)cbData;
// Strip the .xml extension
VfsPath pathstem = change_extension(pathname, L"");
// Strip the root from the path // Strip the root from the path
std::wstring name = pathname.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1); std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
// We want to ignore template_*.xml templates, since they should never be built in the editor // We want to ignore template_*.xml templates, since they should never be built in the editor
if (name.substr(0, 9) == L"template_") if (name.substr(0, 9) == L"template_")
@ -270,3 +298,19 @@ std::vector<std::wstring> CCmpTemplateManager::FindAllTemplates()
return templates; return templates;
} }
void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in)
{
// We only want to include components which are necessary (for the visual previewing of an entity)
// and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
// to this list should be carefully considered
std::set<std::string> permittedComponentTypes;
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
// (This could be initialised once and reused, but it's not worth the effort)
CParamNode::LoadXMLString(out, "<Entity/>");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// In the future, we might want to add some extra flags to certain components, to indicate they're
// running in 'preview' mode and should not e.g. register with global managers
}

View File

@ -34,8 +34,15 @@ public:
* from parent XML files), and applies the techs that are currently active for * from parent XML files), and applies the techs that are currently active for
* player 'playerID', for use with a new entity 'ent'. * player 'playerID', for use with a new entity 'ent'.
* The returned CParamNode must not be used for any entities other than 'ent'. * The returned CParamNode must not be used for any entities other than 'ent'.
* Alternatively, if templateName is of the form "actor|foo" then it will load a *
* default stationary entity template that uses actor "foo". * If templateName is of the form "actor|foo" then it will load a default
* stationary entity template that uses actor "foo". (This is a convenience to
* avoid the need for hundreds of tiny decorative-object entity templates.)
*
* If templateName is of the form "preview|foo" then it will load a template
* based on entity template "foo" with the non-graphical components removed.
* (This is for previewing construction/placement of units.)
*
* @return NULL on error * @return NULL on error
*/ */
virtual const CParamNode* LoadTemplate(entity_id_t ent, const std::wstring& templateName, int playerID) = 0; virtual const CParamNode* LoadTemplate(entity_id_t ent, const std::wstring& templateName, int playerID) = 0;
@ -53,7 +60,7 @@ public:
/** /**
* Returns a list of strings that could be validly passed as @c templateName to LoadTemplate. * Returns a list of strings that could be validly passed as @c templateName to LoadTemplate.
* (This includes "actor|foo" names). * (This includes "actor|foo" etc names).
* Intended for use by the map editor. This is likely to be quite slow. * Intended for use by the map editor. This is likely to be quite slow.
*/ */
virtual std::vector<std::wstring> FindAllTemplates() = 0; virtual std::vector<std::wstring> FindAllTemplates() = 0;

View File

@ -48,11 +48,13 @@ CComponentManager::CComponentManager(const CSimContext& context, bool skipScript
#undef COMPONENT #undef COMPONENT
m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY);
ResetState();
} }
CComponentManager::~CComponentManager() CComponentManager::~CComponentManager()
{ {
DestroyAllComponents(); ResetState();
// Release GC roots // Release GC roots
std::map<ComponentTypeId, ComponentType>::iterator it = m_ComponentTypesById.begin(); std::map<ComponentTypeId, ComponentType>::iterator it = m_ComponentTypesById.begin();
@ -234,7 +236,7 @@ void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptV
delete msg; delete msg;
} }
void CComponentManager::DestroyAllComponents() void CComponentManager::ResetState()
{ {
// Delete all IComponents // Delete all IComponents
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::iterator iit = m_ComponentsByTypeId.begin(); std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::iterator iit = m_ComponentsByTypeId.begin();
@ -250,6 +252,12 @@ void CComponentManager::DestroyAllComponents()
m_ComponentsByInterface.clear(); m_ComponentsByInterface.clear();
m_ComponentsByTypeId.clear(); m_ComponentsByTypeId.clear();
m_DestructionQueue.clear();
// Reset IDs
m_NextEntityId = SYSTEM_ENTITY + 1;
m_NextLocalEntityId = FIRST_LOCAL_ENTITY;
} }
void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc,
@ -309,6 +317,34 @@ CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(Interface
return CID__Invalid; return CID__Invalid;
} }
entity_id_t CComponentManager::AllocateNewEntity()
{
entity_id_t id = m_NextEntityId++;
// TODO: check for overflow
return id;
}
entity_id_t CComponentManager::AllocateNewLocalEntity()
{
entity_id_t id = m_NextLocalEntityId++;
// TODO: check for overflow
return id;
}
entity_id_t CComponentManager::AllocateNewEntity(entity_id_t preferredId)
{
// TODO: ensure this ID hasn't been allocated before
// (this might occur with broken map files)
entity_id_t id = preferredId;
// Ensure this ID won't be allocated again
if (id >= m_NextEntityId)
m_NextEntityId = id+1;
// TODO: check for overflow
return id;
}
bool CComponentManager::AddComponent(entity_id_t ent, ComponentTypeId cid, const CParamNode& paramNode) bool CComponentManager::AddComponent(entity_id_t ent, ComponentTypeId cid, const CParamNode& paramNode)
{ {
IComponent* component = ConstructComponent(ent, cid); IComponent* component = ConstructComponent(ent, cid);
@ -360,6 +396,11 @@ IComponent* CComponentManager::ConstructComponent(entity_id_t ent, ComponentType
// Store a reference to the new component // Store a reference to the new component
emap1.insert(std::make_pair(ent, component)); emap1.insert(std::make_pair(ent, component));
emap2.insert(std::make_pair(ent, component)); emap2.insert(std::make_pair(ent, component));
// TODO: We need to more careful about this - if an entity is constructed by a component
// while we're iterating over all components, this will invalidate the iterators and everything
// will break.
// We probably need some kind of delayed addition, so they get pushed onto a queue and then
// inserted into the world later on. (Be careful about immediation deletion in that case, too.)
return component; return component;
} }
@ -375,9 +416,44 @@ void CComponentManager::AddMockComponent(entity_id_t ent, InterfaceId iid, IComp
emap1.insert(std::make_pair(ent, &component)); emap1.insert(std::make_pair(ent, &component));
} }
void CComponentManager::DestroyComponentsSoon(entity_id_t ent)
{
m_DestructionQueue.push_back(ent);
}
void CComponentManager::FlushDestroyedComponents()
{
for (std::vector<entity_id_t>::iterator it = m_DestructionQueue.begin(); it != m_DestructionQueue.end(); ++it)
{
entity_id_t ent = *it;
// Destroy the components, and remove from m_ComponentsByTypeId:
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::iterator iit = m_ComponentsByTypeId.begin();
for (; iit != m_ComponentsByTypeId.end(); ++iit)
{
std::map<entity_id_t, IComponent*>::iterator eit = iit->second.find(ent);
if (eit != iit->second.end())
{
eit->second->Deinit(m_SimContext);
m_ComponentTypesById[iit->first].dealloc(eit->second);
iit->second.erase(ent);
}
}
// Remove from m_ComponentsByInterface
std::map<InterfaceId, std::map<entity_id_t, IComponent*> >::iterator ifcit = m_ComponentsByInterface.begin();
for (; ifcit != m_ComponentsByInterface.end(); ++ifcit)
{
ifcit->second.erase(ent);
}
}
m_DestructionQueue.clear();
}
IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const
{ {
std::map<int, std::map<entity_id_t, IComponent*> >::const_iterator iit = m_ComponentsByInterface.find(iid); std::map<InterfaceId, std::map<entity_id_t, IComponent*> >::const_iterator iit = m_ComponentsByInterface.find(iid);
if (iit == m_ComponentsByInterface.end()) if (iit == m_ComponentsByInterface.end())
{ {
// Invalid iid, or no entities implement this interface // Invalid iid, or no entities implement this interface
@ -397,7 +473,7 @@ IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid)
static std::map<entity_id_t, IComponent*> g_EmptyEntityMap; static std::map<entity_id_t, IComponent*> g_EmptyEntityMap;
const std::map<entity_id_t, IComponent*>& CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const const std::map<entity_id_t, IComponent*>& CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const
{ {
std::map<int, std::map<entity_id_t, IComponent*> >::const_iterator iit = m_ComponentsByInterface.find(iid); std::map<InterfaceId, std::map<entity_id_t, IComponent*> >::const_iterator iit = m_ComponentsByInterface.find(iid);
if (iit == m_ComponentsByInterface.end()) if (iit == m_ComponentsByInterface.end())
{ {
// Invalid iid, or no entities implement this interface // Invalid iid, or no entities implement this interface

View File

@ -97,6 +97,25 @@ public:
*/ */
std::string LookupComponentTypeName(ComponentTypeId cid) const; std::string LookupComponentTypeName(ComponentTypeId cid) const;
/**
* Returns a new entity ID that has never been used before.
* This affects the simulation state so it must only be called in network-synchronised ways.
*/
entity_id_t AllocateNewEntity();
/**
* Returns a new local entity ID that has never been used before.
* This entity will not be synchronised over the network, stored in saved games, etc.
*/
entity_id_t AllocateNewLocalEntity();
/**
* Returns a new entity ID that has never been used before.
* If possible, returns preferredId, and ensures this ID won't be allocated again.
* This affects the simulation state so it must only be called in network-synchronised ways.
*/
entity_id_t AllocateNewEntity(entity_id_t preferredId);
/** /**
* Constructs a component of type 'cid', initialised with data 'paramNode', * Constructs a component of type 'cid', initialised with data 'paramNode',
* and attaches it to entity 'ent'. * and attaches it to entity 'ent'.
@ -120,16 +139,36 @@ public:
*/ */
IComponent* ConstructComponent(entity_id_t ent, ComponentTypeId cid); IComponent* ConstructComponent(entity_id_t ent, ComponentTypeId cid);
/**
* Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called.
* Has no effect if the entity does not exist, or has already been added to the destruction queue.
*/
void DestroyComponentsSoon(entity_id_t ent);
/**
* Does the actual destruction of components from DestroyComponentsSoon.
* This must not be called if the component manager is on the call stack (since it
* will break internal iterators).
*/
void FlushDestroyedComponents();
IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const;
const std::map<entity_id_t, IComponent*>& GetEntitiesWithInterface(InterfaceId iid) const; const std::map<entity_id_t, IComponent*>& GetEntitiesWithInterface(InterfaceId iid) const;
void PostMessage(entity_id_t ent, const CMessage& msg) const; void PostMessage(entity_id_t ent, const CMessage& msg) const;
void BroadcastMessage(const CMessage& msg) const; void BroadcastMessage(const CMessage& msg) const;
void DestroyAllComponents(); /**
* Resets the dynamic simulation state (deletes all entities, resets entity ID counters;
* doesn't unload/reload component scripts).
*/
void ResetState();
// Various state serialization functions:
bool ComputeStateHash(std::string& outHash); bool ComputeStateHash(std::string& outHash);
bool DumpDebugState(std::ostream& stream); bool DumpDebugState(std::ostream& stream);
// FlushDestroyedComponents must be called before SerializeState (since the destruction queue
// won't get serialized)
bool SerializeState(std::ostream& stream); bool SerializeState(std::ostream& stream);
bool DeserializeState(std::istream& stream); bool DeserializeState(std::istream& stream);
@ -161,7 +200,11 @@ private:
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one, // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
// while keeping QueryInterface and PostMessage sufficiently efficient? // while keeping QueryInterface and PostMessage sufficiently efficient?
std::vector<entity_id_t> m_DestructionQueue;
ComponentTypeId m_NextScriptComponentTypeId; ComponentTypeId m_NextScriptComponentTypeId;
entity_id_t m_NextEntityId;
entity_id_t m_NextLocalEntityId;
friend class TestComponentManager; friend class TestComponentManager;
}; };

View File

@ -55,6 +55,9 @@ bool CComponentManager::DumpDebugState(std::ostream& stream)
n << "- id: " << cit->first; n << "- id: " << cit->first;
serializer.TextLine(n.str()); serializer.TextLine(n.str());
if (ENTITY_IS_LOCAL(cit->first))
serializer.TextLine(" type: local");
std::map<ComponentTypeId, IComponent*>::const_iterator ctit = cit->second.begin(); std::map<ComponentTypeId, IComponent*>::const_iterator ctit = cit->second.begin();
for (; ctit != cit->second.end(); ++ctit) for (; ctit != cit->second.end(); ++ctit)
{ {
@ -88,6 +91,10 @@ bool CComponentManager::ComputeStateHash(std::string& outHash)
std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin(); std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin();
for (; eit != cit->second.end(); ++eit) for (; eit != cit->second.end(); ++eit)
{ {
// Don't hash local entities
if (ENTITY_IS_LOCAL(eit->first))
continue;
serializer.NumberU32_Unbounded("entity id", eit->first); serializer.NumberU32_Unbounded("entity id", eit->first);
eit->second->Serialize(serializer); eit->second->Serialize(serializer);
} }
@ -125,6 +132,12 @@ bool CComponentManager::SerializeState(std::ostream& stream)
{ {
CStdSerializer serializer(m_ScriptInterface, stream); CStdSerializer serializer(m_ScriptInterface, stream);
// We don't serialize the destruction queue, since we'd have to be careful to skip local entities etc
// and it's (hopefully) easier to just expect callers to flush the queue before serializing
debug_assert(m_DestructionQueue.empty());
serializer.NumberU32_Unbounded("next entity id", m_NextEntityId);
uint32_t numComponentTypes = 0; uint32_t numComponentTypes = 0;
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator cit; std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> >::const_iterator cit;
@ -153,12 +166,29 @@ bool CComponentManager::SerializeState(std::ostream& stream)
serializer.StringASCII("name", ctit->second.name, 0, 255); serializer.StringASCII("name", ctit->second.name, 0, 255);
uint32_t numComponents = cit->second.size(); std::map<entity_id_t, IComponent*>::const_iterator eit;
// Count the components before serializing any of them
uint32_t numComponents = 0;
for (eit = cit->second.begin(); eit != cit->second.end(); ++eit)
{
// Don't serialize local entities
if (ENTITY_IS_LOCAL(eit->first))
continue;
numComponents++;
}
// Emit the count
serializer.NumberU32_Unbounded("num components", numComponents); serializer.NumberU32_Unbounded("num components", numComponents);
std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin(); // Serialize the components now
for (; eit != cit->second.end(); ++eit) for (eit = cit->second.begin(); eit != cit->second.end(); ++eit)
{ {
// Don't serialize local entities
if (ENTITY_IS_LOCAL(eit->first))
continue;
serializer.NumberU32_Unbounded("entity id", eit->first); serializer.NumberU32_Unbounded("entity id", eit->first);
eit->second->Serialize(serializer); eit->second->Serialize(serializer);
} }
@ -172,7 +202,9 @@ bool CComponentManager::DeserializeState(std::istream& stream)
{ {
CStdDeserializer deserializer(m_ScriptInterface, stream); CStdDeserializer deserializer(m_ScriptInterface, stream);
DestroyAllComponents(); ResetState();
deserializer.NumberU32_Unbounded(m_NextEntityId); // TODO: use sensible bounds
uint32_t numComponentTypes; uint32_t numComponentTypes;
deserializer.NumberU32_Unbounded(numComponentTypes); deserializer.NumberU32_Unbounded(numComponentTypes);

View File

@ -41,4 +41,22 @@ const entity_id_t INVALID_ENTITY = 0;
*/ */
const entity_id_t SYSTEM_ENTITY = 1; const entity_id_t SYSTEM_ENTITY = 1;
// Entities are split into two kinds:
// "Normal" (for most entities)
// "Local" (for entities that only exist on the local machine, aren't synchronised across the network,
// aren't retained in saved games, etc)
// The distinction is encoded in the entity ID, so that they're easily distinguished.
//
// We want all entity_id_ts to fit in jsval ints, i.e. 1-2^30 .. 2^30-1 (inclusive)
// We want them to be unsigned ints (actually it shouldn't matter but unsigned seems simpler)
// We want 1 tag bit
// So we have 1 JS-reserved bit, 1 unused sign bit, 1 local tag bit, 29 counter bits
// (0.5B entities should be plenty)
#define ENTITY_TAGMASK (1 << 29)
#define ENTITY_IS_NORMAL(id) (((id) & ENTITY_TAGMASK) == 0)
#define ENTITY_IS_LOCAL(id) (((id) & ENTITY_TAGMASK) == ENTITY_TAGMASK)
const entity_id_t FIRST_LOCAL_ENTITY = ENTITY_TAGMASK;
#endif // INCLUDED_SIM2_ENTITY #endif // INCLUDED_SIM2_ENTITY

View File

@ -66,6 +66,19 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element)
// TODO: support some kind of 'delete' marker, for use with inheritance // TODO: support some kind of 'delete' marker, for use with inheritance
} }
void CParamNode::CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set<std::string>& permitted)
{
ChildrenMap::iterator dstChild = m_Childs.find(name);
ChildrenMap::const_iterator srcChild = src.m_Childs.find(name);
if (dstChild == m_Childs.end() || srcChild == src.m_Childs.end())
return; // error
ChildrenMap::const_iterator it = srcChild->second.m_Childs.begin();
for (; it != srcChild->second.m_Childs.end(); ++it)
if (permitted.count(it->first))
dstChild->second.m_Childs[it->first] = it->second;
}
const CParamNode* CParamNode::GetChild(const char* name) const const CParamNode* CParamNode::GetChild(const char* name) const
{ {
ChildrenMap::const_iterator it = m_Childs.find(name); ChildrenMap::const_iterator it = m_Childs.find(name);

View File

@ -32,22 +32,32 @@ public:
typedef std::map<std::string, CParamNode> ChildrenMap; typedef std::map<std::string, CParamNode> ChildrenMap;
/** /**
* Loads the XML data specified by 'file' into the node 'ret'. * Loads the XML data specified by @a file into the node @a ret.
* Any existing data in 'ret' will be overwritten or else kept, so this * Any existing data in @a ret will be overwritten or else kept, so this
* can be called multiple times to build up a node from multiple inputs. * can be called multiple times to build up a node from multiple inputs.
*/ */
static void LoadXML(CParamNode& ret, const XMBFile& file); static void LoadXML(CParamNode& ret, const XMBFile& file);
/** /**
* See LoadXML, but parses the XML string 'xml'. * See LoadXML, but parses the XML string @a xml.
* @return error code if parsing failed, else PSRETURN_OK * @return error code if parsing failed, else @c PSRETURN_OK
*/ */
static PSRETURN LoadXMLString(CParamNode& ret, const char* xml); static PSRETURN LoadXMLString(CParamNode& ret, const char* xml);
/**
* Finds the childs named @a name from @a src and from @a this, and copies the source child's children
* which are in the @a permitted set into this node's child.
* Intended for use as a filtered clone of XML files.
* @a this and @a src must have childs named @a name.
*/
void CopyFilteredChildrenOfChild(const CParamNode& src, const char* name, const std::set<std::string>& permitted);
/** /**
* Returns the (unique) child node with the given name, or NULL if there is none. * Returns the (unique) child node with the given name, or NULL if there is none.
*/ */
const CParamNode* GetChild(const char* name) const; const CParamNode* GetChild(const char* name) const;
// (Children are returned as const in order to allow future optimisations, where we assume
// a node is always modified explicitly and not indirectly via its children, e.g. to cache jsvals)
/** /**
* Returns the content of this node as a string * Returns the content of this node as a string

View File

@ -74,7 +74,15 @@ public:
const CParamNode* actor = tempMan->LoadTemplate(ent2, L"actor|example", -1); const CParamNode* actor = tempMan->LoadTemplate(ent2, L"actor|example", -1);
TS_ASSERT(actor != NULL); TS_ASSERT(actor != NULL);
TS_ASSERT_WSTR_EQUALS(actor->ToXML(), L"<MotionBallScripted></MotionBallScripted><Position><Altitude>0</Altitude><Anchor>upright</Anchor><Floating>false</Floating></Position><VisualActor><Actor>example</Actor></VisualActor>"); TS_ASSERT_WSTR_EQUALS(actor->ToXML(), L"<MotionBallScripted></MotionBallScripted><Position><Altitude>0</Altitude><Anchor>upright</Anchor><Floating>false</Floating></Position><VisualActor><Actor>example</Actor></VisualActor>");
}
const CParamNode* preview = tempMan->LoadTemplate(ent2, L"preview|unit", -1);
TS_ASSERT(preview != NULL);
TS_ASSERT_WSTR_EQUALS(preview->ToXML(), L"<Position><Altitude>0</Altitude><Anchor>upright</Anchor><Floating>false</Floating></Position><VisualActor><Actor>example</Actor></VisualActor>");
const CParamNode* previewactor = tempMan->LoadTemplate(ent2, L"preview|actor|example2", -1);
TS_ASSERT(previewactor != NULL);
TS_ASSERT_WSTR_EQUALS(previewactor->ToXML(), L"<Position><Altitude>0</Altitude><Anchor>upright</Anchor><Floating>false</Floating></Position><VisualActor><Actor>example2</Actor></VisualActor>");
}
void test_LoadTemplate_errors() void test_LoadTemplate_errors()
{ {
@ -94,11 +102,13 @@ public:
TS_ASSERT(tempMan->LoadTemplate(ent2, L"nonexistent", -1) == NULL); TS_ASSERT(tempMan->LoadTemplate(ent2, L"nonexistent", -1) == NULL);
const CParamNode* inherit_loop = tempMan->LoadTemplate(ent2, L"inherit-loop", -1); TS_ASSERT(tempMan->LoadTemplate(ent2, L"inherit-loop", -1) == NULL);
TS_ASSERT(inherit_loop == NULL);
const CParamNode* inherit_broken = tempMan->LoadTemplate(ent2, L"inherit-broken", -1); TS_ASSERT(tempMan->LoadTemplate(ent2, L"inherit-broken", -1) == NULL);
TS_ASSERT(inherit_broken == NULL);
TS_ASSERT(tempMan->LoadTemplate(ent2, L"inherit-special", -1) == NULL);
TS_ASSERT(tempMan->LoadTemplate(ent2, L"preview|nonexistent", -1) == NULL);
} }
void test_LoadTemplate_multiple() void test_LoadTemplate_multiple()

View File

@ -71,6 +71,29 @@ public:
TS_ASSERT_EQUALS(man.LookupCID("Test1B"), (int)CID_Test1B); TS_ASSERT_EQUALS(man.LookupCID("Test1B"), (int)CID_Test1B);
} }
void test_AllocateNewEntity()
{
CSimContext context;
CComponentManager man(context);
TS_ASSERT_EQUALS(man.AllocateNewEntity(), (u32)2);
TS_ASSERT_EQUALS(man.AllocateNewEntity(), (u32)3);
TS_ASSERT_EQUALS(man.AllocateNewEntity(), (u32)4);
TS_ASSERT_EQUALS(man.AllocateNewEntity(100), (u32)100);
TS_ASSERT_EQUALS(man.AllocateNewEntity(), (u32)101);
// TODO:
// TS_ASSERT_EQUALS(man.AllocateNewEntity(3), (u32)102);
TS_ASSERT_EQUALS(man.AllocateNewLocalEntity(), (u32)FIRST_LOCAL_ENTITY);
TS_ASSERT_EQUALS(man.AllocateNewLocalEntity(), (u32)FIRST_LOCAL_ENTITY+1);
man.ResetState();
TS_ASSERT_EQUALS(man.AllocateNewEntity(), (u32)2);
TS_ASSERT_EQUALS(man.AllocateNewEntity(3), (u32)3);
TS_ASSERT_EQUALS(man.AllocateNewLocalEntity(), (u32)FIRST_LOCAL_ENTITY);
}
void test_AddComponent_errors() void test_AddComponent_errors()
{ {
CSimContext context; CSimContext context;
@ -403,7 +426,7 @@ public:
CComponentManager man(context); CComponentManager man(context);
man.LoadComponentTypes(); man.LoadComponentTypes();
entity_id_t ent1 = 1, ent2 = 2; entity_id_t ent1 = 1, ent2 = 2, ent3 = FIRST_LOCAL_ENTITY;
CParamNode noParam; CParamNode noParam;
CParamNode testParam; CParamNode testParam;
@ -412,10 +435,12 @@ public:
man.AddComponent(ent1, CID_Test1A, noParam); man.AddComponent(ent1, CID_Test1A, noParam);
man.AddComponent(ent1, CID_Test2A, noParam); man.AddComponent(ent1, CID_Test2A, noParam);
man.AddComponent(ent2, CID_Test1A, testParam); man.AddComponent(ent2, CID_Test1A, testParam);
man.AddComponent(ent3, CID_Test2A, noParam);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11000); TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent1, IID_Test1))->GetX(), 11000);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent1, IID_Test2))->GetX(), 21000); TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent1, IID_Test2))->GetX(), 21000);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 1234); TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man.QueryInterface(ent2, IID_Test1))->GetX(), 1234);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man.QueryInterface(ent3, IID_Test2))->GetX(), 21000);
std::stringstream debugStream; std::stringstream debugStream;
TS_ASSERT(man.DumpDebugState(debugStream)); TS_ASSERT(man.DumpDebugState(debugStream));
@ -430,6 +455,11 @@ public:
" Test1A:\n" " Test1A:\n"
" x: 1234\n" " x: 1234\n"
"\n" "\n"
"- id: 536870912\n"
" type: local\n"
" Test2A:\n"
" x: 21000\n"
"\n"
); );
std::string hash; std::string hash;
@ -441,7 +471,8 @@ public:
std::stringstream stateStream; std::stringstream stateStream;
TS_ASSERT(man.SerializeState(stateStream)); TS_ASSERT(man.SerializeState(stateStream));
TS_ASSERT_STREAM(stateStream, 56, TS_ASSERT_STREAM(stateStream, 60,
"\x02\x00\x00\x00" // next entity ID
"\x02\x00\x00\x00" // num component types "\x02\x00\x00\x00" // num component types
"\x06\x00\x00\x00Test1A" "\x06\x00\x00\x00Test1A"
"\x02\x00\x00\x00" // num ents "\x02\x00\x00\x00" // num ents
@ -462,12 +493,14 @@ public:
TS_ASSERT(man2.QueryInterface(ent1, IID_Test1) == NULL); TS_ASSERT(man2.QueryInterface(ent1, IID_Test1) == NULL);
TS_ASSERT(man2.QueryInterface(ent1, IID_Test2) == NULL); TS_ASSERT(man2.QueryInterface(ent1, IID_Test2) == NULL);
TS_ASSERT(man2.QueryInterface(ent2, IID_Test1) == NULL); TS_ASSERT(man2.QueryInterface(ent2, IID_Test1) == NULL);
TS_ASSERT(man2.QueryInterface(ent3, IID_Test2) == NULL);
TS_ASSERT(man2.DeserializeState(stateStream)); TS_ASSERT(man2.DeserializeState(stateStream));
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man2.QueryInterface(ent1, IID_Test1))->GetX(), 11000); TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man2.QueryInterface(ent1, IID_Test1))->GetX(), 11000);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man2.QueryInterface(ent1, IID_Test2))->GetX(), 21000); TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (man2.QueryInterface(ent1, IID_Test2))->GetX(), 21000);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man2.QueryInterface(ent2, IID_Test1))->GetX(), 1234); TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (man2.QueryInterface(ent2, IID_Test1))->GetX(), 1234);
TS_ASSERT(man2.QueryInterface(ent3, IID_Test2) == NULL);
} }
void test_script_serialization() void test_script_serialization()

View File

@ -18,6 +18,7 @@
#include "lib/self_test.h" #include "lib/self_test.h"
#include "simulation2/Simulation2.h" #include "simulation2/Simulation2.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpTest.h" #include "simulation2/components/ICmpTest.h"
#include "graphics/Terrain.h" #include "graphics/Terrain.h"
@ -51,15 +52,6 @@ public:
g_VFS.reset(); g_VFS.reset();
} }
void test_AllocateNewEntity()
{
CSimulation2 sim(NULL, &m_Terrain);
sim.ResetState(true);
TS_ASSERT_EQUALS(sim.AllocateNewEntity(), (u32)2);
TS_ASSERT_EQUALS(sim.AllocateNewEntity(), (u32)3);
TS_ASSERT_EQUALS(sim.AllocateNewEntity(), (u32)4);
}
void test_AddEntity() void test_AddEntity()
{ {
CSimulation2 sim(NULL, &m_Terrain); CSimulation2 sim(NULL, &m_Terrain);
@ -80,6 +72,57 @@ public:
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent2, IID_Test2))->GetX(), 12345); TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent2, IID_Test2))->GetX(), 12345);
} }
void test_DestroyEntity()
{
CSimulation2 sim(NULL, &m_Terrain);
TS_ASSERT(sim.LoadScripts(L"simulation/components/addentity/"));
sim.ResetState(true);
entity_id_t ent1 = sim.AddEntity(L"test1");
entity_id_t ent2 = sim.AddEntity(L"test1");
entity_id_t ent3 = sim.AddEntity(L"test1");
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (sim.QueryInterface(ent1, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent1, IID_Test2))->GetX(), 12345);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (sim.QueryInterface(ent2, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent2, IID_Test2))->GetX(), 12345);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (sim.QueryInterface(ent3, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent3, IID_Test2))->GetX(), 12345);
sim.DestroyEntity(ent2); // mark it for deletion
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (sim.QueryInterface(ent2, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent2, IID_Test2))->GetX(), 12345);
sim.FlushDestroyedEntities(); // actually delete it
TS_ASSERT(sim.QueryInterface(ent2, IID_Test1) == NULL);
TS_ASSERT(sim.QueryInterface(ent2, IID_Test2) == NULL);
sim.FlushDestroyedEntities(); // nothing in the queue
sim.DestroyEntity(ent2);
sim.FlushDestroyedEntities(); // already deleted
// Other entities weren't affected
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (sim.QueryInterface(ent1, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent1, IID_Test2))->GetX(), 12345);
TS_ASSERT_EQUALS(static_cast<ICmpTest1*> (sim.QueryInterface(ent3, IID_Test1))->GetX(), 999);
TS_ASSERT_EQUALS(static_cast<ICmpTest2*> (sim.QueryInterface(ent3, IID_Test2))->GetX(), 12345);
sim.DestroyEntity(ent3); // mark it for deletion twice
sim.DestroyEntity(ent3);
sim.FlushDestroyedEntities();
TS_ASSERT(sim.QueryInterface(ent3, IID_Test1) == NULL);
TS_ASSERT(sim.QueryInterface(ent3, IID_Test2) == NULL);
// Messages mustn't get sent to the destroyed components (else we'll crash)
sim.BroadcastMessage(CMessageTurnStart());
}
void test_hotload_scripts() void test_hotload_scripts()
{ {
CSimulation2 sim(NULL, &m_Terrain); CSimulation2 sim(NULL, &m_Terrain);

View File

@ -289,9 +289,9 @@ END_COMMAND(SetObjectSettings);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
static size_t g_PreviewUnitID = invalidUnitId;
static CStrW g_PreviewUnitName; static CStrW g_PreviewUnitName;
static entity_id_t g_PreviewEntityID = INVALID_ENTITY; // used if g_UseSimulation2
static size_t g_PreviewUnitID = invalidUnitId; // old simulation
static bool g_PreviewUnitFloating; static bool g_PreviewUnitFloating;
static CVector3D GetUnitPos(const Position& pos, bool floating) static CVector3D GetUnitPos(const Position& pos, bool floating)
@ -343,6 +343,58 @@ static bool ParseObjectName(const CStrW& obj, bool& isEntity, CStrW& name)
MESSAGEHANDLER(ObjectPreview) MESSAGEHANDLER(ObjectPreview)
{ {
if (g_UseSimulation2)
{
// If the selection has changed...
if (*msg->id != g_PreviewUnitName)
{
// Delete old entity
if (g_PreviewEntityID != INVALID_ENTITY)
g_Game->GetSimulation2()->DestroyEntity(g_PreviewEntityID);
// Create the new entity
if ((*msg->id).empty())
g_PreviewEntityID = INVALID_ENTITY;
else
g_PreviewEntityID = g_Game->GetSimulation2()->AddLocalEntity(*msg->id);
g_PreviewUnitName = *msg->id;
}
if (g_PreviewEntityID != INVALID_ENTITY)
{
// Update the unit's position and orientation:
CmpPtr<ICmpPosition> cmpPos (*g_Game->GetSimulation2(), g_PreviewEntityID);
if (!cmpPos.null())
{
CVector3D pos = GetUnitPos(msg->pos, false);
cmpPos->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z));
float angle;
if (msg->usetarget)
{
// Aim from pos towards msg->target
CVector3D target = msg->target->GetWorldSpace(pos.Y);
angle = atan2(target.X-pos.X, target.Z-pos.Z);
}
else
{
angle = msg->angle;
}
cmpPos->SetYRotation(entity_angle_t::FromFloat(angle));
}
// TODO: handle random variations somehow
// TODO: set player colour
}
return;
}
// Old simulation system:
CUnit* previewUnit = GetUnitManager().FindByID(g_PreviewUnitID); CUnit* previewUnit = GetUnitManager().FindByID(g_PreviewUnitID);
// Don't recreate the unit unless it's changed // Don't recreate the unit unless it's changed
@ -433,8 +485,9 @@ BEGIN_COMMAND(CreateObject)
{ {
CVector3D m_Pos; CVector3D m_Pos;
float m_Angle; float m_Angle;
size_t m_ID; size_t m_ID; // old simulation system
size_t m_Player; size_t m_Player;
entity_id_t m_EntityID; // new simulation system
void Do() void Do()
{ {
@ -446,8 +499,7 @@ BEGIN_COMMAND(CreateObject)
{ {
// Aim from m_Pos towards msg->target // Aim from m_Pos towards msg->target
CVector3D target = msg->target->GetWorldSpace(m_Pos.Y); CVector3D target = msg->target->GetWorldSpace(m_Pos.Y);
CVector2D dir(target.X-m_Pos.X, target.Z-m_Pos.Z); m_Angle = atan2(target.X-m_Pos.X, target.Z-m_Pos.Z);
m_Angle = atan2(dir.x, dir.y);
} }
else else
{ {
@ -457,14 +509,38 @@ BEGIN_COMMAND(CreateObject)
// TODO: variations too // TODO: variations too
m_Player = msg->settings->player; m_Player = msg->settings->player;
// Get a new ID, for future reference to this unit if (!g_UseSimulation2)
m_ID = GetUnitManager().GetNewID(); {
// Get a new ID, for future reference to this unit
m_ID = GetUnitManager().GetNewID();
}
Redo(); Redo();
} }
void Redo() void Redo()
{ {
if (g_UseSimulation2)
{
m_EntityID = g_Game->GetSimulation2()->AddEntity(*msg->id);
if (m_EntityID == INVALID_ENTITY)
return;
CmpPtr<ICmpPosition> cmpPos (*g_Game->GetSimulation2(), m_EntityID);
if (!cmpPos.null())
{
cmpPos->JumpTo(entity_pos_t::FromFloat(m_Pos.X), entity_pos_t::FromFloat(m_Pos.Z));
cmpPos->SetYRotation(entity_angle_t::FromFloat(m_Angle));
}
// TODO: handle random variations somehow
// TODO: set player colour
return;
}
// Old simulation system:
bool isEntity; bool isEntity;
CStrW name; CStrW name;
if (ParseObjectName(*msg->id, isEntity, name)) if (ParseObjectName(*msg->id, isEntity, name))
@ -532,6 +608,19 @@ BEGIN_COMMAND(CreateObject)
void Undo() void Undo()
{ {
if (g_UseSimulation2)
{
if (m_EntityID != INVALID_ENTITY)
{
g_Game->GetSimulation2()->DestroyEntity(m_EntityID);
m_EntityID = INVALID_ENTITY;
}
return;
}
// Old simulation system:
CUnit* unit = GetUnitManager().FindByID(m_ID); CUnit* unit = GetUnitManager().FindByID(m_ID);
if (unit) if (unit)
{ {

View File

@ -159,6 +159,10 @@ void ViewGame::Update(float frameLength)
{ {
float actualFrameLength = frameLength * m_SpeedMultiplier; float actualFrameLength = frameLength * m_SpeedMultiplier;
// Clean up any entities destroyed during UI message processing
if (g_UseSimulation2)
g_Game->GetSimulation2()->FlushDestroyedEntities();
if (m_SpeedMultiplier == 0.f) if (m_SpeedMultiplier == 0.f)
{ {
// Update unit interpolation // Update unit interpolation
@ -184,6 +188,10 @@ void ViewGame::Update(float frameLength)
} }
} }
// Clean up any entities destroyed during simulation update
if (g_UseSimulation2)
g_Game->GetSimulation2()->FlushDestroyedEntities();
// Interpolate the graphics - we only want to do this once per visual frame, // Interpolate the graphics - we only want to do this once per visual frame,
// not in every call to g_Game->Update // not in every call to g_Game->Update
g_Game->GetSimulation()->Interpolate(actualFrameLength); g_Game->GetSimulation()->Interpolate(actualFrameLength);