# Object creation in Atlas with new simulation system
Merge from 6e8efe8f706c This was SVN commit r7276.
This commit is contained in:
parent
f5632af192
commit
c8138208bf
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity parent="actor|example">test</Entity>
|
13
binaries/data/mods/_test.sim/simulation/templates/unit.xml
Normal file
13
binaries/data/mods/_test.sim/simulation/templates/unit.xml
Normal 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>
|
@ -44,16 +44,13 @@ public:
|
||||
m_SimContext.m_Terrain = terrain;
|
||||
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)
|
||||
}
|
||||
|
||||
void ResetState(bool skipGui)
|
||||
{
|
||||
m_ComponentManager.DestroyAllComponents();
|
||||
m_ComponentManager.ResetState();
|
||||
|
||||
m_NextId = SYSTEM_ENTITY + 1;
|
||||
m_DeltaTime = 0.0;
|
||||
|
||||
CParamNode noParam;
|
||||
@ -77,17 +74,15 @@ public:
|
||||
bool LoadScripts(const VfsPath& path);
|
||||
LibError ReloadChangedFile(const VfsPath& path);
|
||||
|
||||
entity_id_t AllocateNewEntity();
|
||||
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 Interpolate(float frameTime);
|
||||
|
||||
CSimContext m_SimContext;
|
||||
CComponentManager m_ComponentManager;
|
||||
entity_id_t m_NextId;
|
||||
double m_DeltaTime;
|
||||
|
||||
std::set<std::wstring> m_LoadedScripts;
|
||||
@ -134,28 +129,17 @@ LibError CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
entity_id_t CSimulation2Impl::AllocateNewEntity()
|
||||
{
|
||||
return m_NextId++;
|
||||
}
|
||||
|
||||
void CSimulation2Impl::AddComponent(entity_id_t ent, EComponentTypeId cid, const CParamNode& 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);
|
||||
debug_assert(!tempMan.null());
|
||||
|
||||
entity_id_t ent = preferredId;
|
||||
// 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;
|
||||
// TODO: should assert that ent doesn't exist
|
||||
|
||||
const CParamNode* tmpl = tempMan->LoadTemplate(ent, templateName, -1);
|
||||
if (!tmpl)
|
||||
@ -230,12 +214,27 @@ CSimulation2::~CSimulation2()
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
@ -258,11 +257,6 @@ const CSimulation2::InterfaceList& CSimulation2::GetEntitiesWithInterface(int ii
|
||||
return m->m_ComponentManager.GetEntitiesWithInterface(iid);
|
||||
}
|
||||
|
||||
entity_id_t CSimulation2::AllocateNewEntity()
|
||||
{
|
||||
return m->AllocateNewEntity();
|
||||
}
|
||||
|
||||
const CSimContext& CSimulation2::GetSimContext() const
|
||||
{
|
||||
return m->m_SimContext;
|
||||
@ -310,13 +304,11 @@ bool CSimulation2::DumpDebugState(std::ostream& stream)
|
||||
|
||||
bool CSimulation2::SerializeState(std::ostream& stream)
|
||||
{
|
||||
// TODO: need to save m->m_NextId
|
||||
return m->m_ComponentManager.SerializeState(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
|
||||
return m->m_ComponentManager.DeserializeState(stream);
|
||||
}
|
||||
|
@ -71,8 +71,27 @@ public:
|
||||
void Update(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 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;
|
||||
void PostMessage(entity_id_t ent, const CMessage& msg) const;
|
||||
void BroadcastMessage(const CMessage& msg) const;
|
||||
@ -88,8 +107,6 @@ public:
|
||||
bool SerializeState(std::ostream& stream);
|
||||
bool DeserializeState(std::istream& stream);
|
||||
|
||||
entity_id_t AllocateNewEntity();
|
||||
|
||||
private:
|
||||
CSimulation2Impl* m;
|
||||
|
||||
|
@ -88,8 +88,10 @@ public:
|
||||
virtual std::vector<std::wstring> FindAllTemplates();
|
||||
|
||||
private:
|
||||
// Map from template XML filename to last loaded valid template data
|
||||
// (We store "last loaded valid" to behave more nicely when hotloading broken files)
|
||||
// Map from template name (XML filename or special |-separated string) to the most recently
|
||||
// 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;
|
||||
|
||||
// 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,
|
||||
// and saves into m_TemplateFileData. Also loads any parents that are not yet
|
||||
// loaded. Returns false on error.
|
||||
// @param templateName XML filename to load (not a |-separated string)
|
||||
bool LoadTemplateFile(const std::wstring& templateName, int depth);
|
||||
|
||||
// Constructs a standard static-decorative-object template for the given actor
|
||||
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)
|
||||
@ -112,24 +119,11 @@ const CParamNode* CCmpTemplateManager::LoadTemplate(entity_id_t ent, const std::
|
||||
{
|
||||
m_LatestTemplates[ent] = templateName;
|
||||
|
||||
bool isNew = (m_TemplateFileData.find(templateName) == m_TemplateFileData.end());
|
||||
|
||||
if (isNew)
|
||||
// Load the template if necessary
|
||||
if (!LoadTemplateFile(templateName, 0))
|
||||
{
|
||||
// Handle special case "actor|foo"
|
||||
if (templateName.find(L"actor|") == 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
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
|
||||
@ -164,6 +158,10 @@ std::wstring CCmpTemplateManager::GetCurrentTemplateName(entity_id_t ent)
|
||||
|
||||
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
|
||||
if (depth > 100)
|
||||
{
|
||||
@ -171,6 +169,30 @@ bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int
|
||||
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");
|
||||
CXeromyces xero;
|
||||
PSRETURN ok = xero.Load(path);
|
||||
@ -183,14 +205,18 @@ bool CCmpTemplateManager::LoadTemplateFile(const std::wstring& templateName, int
|
||||
{
|
||||
std::wstring parentName(parentStr.begin(), parentStr.end());
|
||||
|
||||
// If the parent wasn't already loaded, then load it.
|
||||
if (m_TemplateFileData.find(parentName) == m_TemplateFileData.end())
|
||||
// To prevent needless complexity in template design, we don't allow |-separated strings as parents
|
||||
if (parentName.find('|') != parentName.npos)
|
||||
{
|
||||
if (!LoadTemplateFile(parentName, depth+1))
|
||||
{
|
||||
LOGERROR(L"Failed to load parent '%ls' of entity template '%ls'", parentName.c_str(), templateName.c_str());
|
||||
return false;
|
||||
}
|
||||
LOGERROR(L"Invalid parent '%ls' in 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];
|
||||
@ -228,8 +254,10 @@ static LibError AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(f
|
||||
{
|
||||
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
|
||||
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
|
||||
if (name.substr(0, 9) == L"template_")
|
||||
@ -270,3 +298,19 @@ std::vector<std::wstring> CCmpTemplateManager::FindAllTemplates()
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -34,8 +34,15 @@ public:
|
||||
* from parent XML files), and applies the techs that are currently active for
|
||||
* player 'playerID', for use with a new entity '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
|
||||
*/
|
||||
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.
|
||||
* (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.
|
||||
*/
|
||||
virtual std::vector<std::wstring> FindAllTemplates() = 0;
|
||||
|
@ -48,11 +48,13 @@ CComponentManager::CComponentManager(const CSimContext& context, bool skipScript
|
||||
#undef COMPONENT
|
||||
|
||||
m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY);
|
||||
|
||||
ResetState();
|
||||
}
|
||||
|
||||
CComponentManager::~CComponentManager()
|
||||
{
|
||||
DestroyAllComponents();
|
||||
ResetState();
|
||||
|
||||
// Release GC roots
|
||||
std::map<ComponentTypeId, ComponentType>::iterator it = m_ComponentTypesById.begin();
|
||||
@ -234,7 +236,7 @@ void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptV
|
||||
delete msg;
|
||||
}
|
||||
|
||||
void CComponentManager::DestroyAllComponents()
|
||||
void CComponentManager::ResetState()
|
||||
{
|
||||
// Delete all IComponents
|
||||
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_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,
|
||||
@ -309,6 +317,34 @@ CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(Interface
|
||||
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)
|
||||
{
|
||||
IComponent* component = ConstructComponent(ent, cid);
|
||||
@ -360,6 +396,11 @@ IComponent* CComponentManager::ConstructComponent(entity_id_t ent, ComponentType
|
||||
// Store a reference to the new component
|
||||
emap1.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;
|
||||
}
|
||||
@ -375,9 +416,44 @@ void CComponentManager::AddMockComponent(entity_id_t ent, InterfaceId iid, IComp
|
||||
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
|
||||
{
|
||||
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())
|
||||
{
|
||||
// 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;
|
||||
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())
|
||||
{
|
||||
// Invalid iid, or no entities implement this interface
|
||||
|
@ -97,6 +97,25 @@ public:
|
||||
*/
|
||||
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',
|
||||
* and attaches it to entity 'ent'.
|
||||
@ -120,16 +139,36 @@ public:
|
||||
*/
|
||||
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;
|
||||
const std::map<entity_id_t, IComponent*>& GetEntitiesWithInterface(InterfaceId iid) const;
|
||||
|
||||
void PostMessage(entity_id_t ent, 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 DumpDebugState(std::ostream& stream);
|
||||
// FlushDestroyedComponents must be called before SerializeState (since the destruction queue
|
||||
// won't get serialized)
|
||||
bool SerializeState(std::ostream& stream);
|
||||
bool DeserializeState(std::istream& stream);
|
||||
|
||||
@ -161,7 +200,11 @@ private:
|
||||
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,
|
||||
// while keeping QueryInterface and PostMessage sufficiently efficient?
|
||||
|
||||
std::vector<entity_id_t> m_DestructionQueue;
|
||||
|
||||
ComponentTypeId m_NextScriptComponentTypeId;
|
||||
entity_id_t m_NextEntityId;
|
||||
entity_id_t m_NextLocalEntityId;
|
||||
|
||||
friend class TestComponentManager;
|
||||
};
|
||||
|
@ -55,6 +55,9 @@ bool CComponentManager::DumpDebugState(std::ostream& stream)
|
||||
n << "- id: " << cit->first;
|
||||
serializer.TextLine(n.str());
|
||||
|
||||
if (ENTITY_IS_LOCAL(cit->first))
|
||||
serializer.TextLine(" type: local");
|
||||
|
||||
std::map<ComponentTypeId, IComponent*>::const_iterator ctit = cit->second.begin();
|
||||
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();
|
||||
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);
|
||||
eit->second->Serialize(serializer);
|
||||
}
|
||||
@ -125,6 +132,12 @@ bool CComponentManager::SerializeState(std::ostream& 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;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
std::map<entity_id_t, IComponent*>::const_iterator eit = cit->second.begin();
|
||||
for (; eit != cit->second.end(); ++eit)
|
||||
// Serialize the components now
|
||||
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);
|
||||
eit->second->Serialize(serializer);
|
||||
}
|
||||
@ -172,7 +202,9 @@ bool CComponentManager::DeserializeState(std::istream& stream)
|
||||
{
|
||||
CStdDeserializer deserializer(m_ScriptInterface, stream);
|
||||
|
||||
DestroyAllComponents();
|
||||
ResetState();
|
||||
|
||||
deserializer.NumberU32_Unbounded(m_NextEntityId); // TODO: use sensible bounds
|
||||
|
||||
uint32_t numComponentTypes;
|
||||
deserializer.NumberU32_Unbounded(numComponentTypes);
|
||||
|
@ -41,4 +41,22 @@ const entity_id_t INVALID_ENTITY = 0;
|
||||
*/
|
||||
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
|
||||
|
@ -66,6 +66,19 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element)
|
||||
// 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
|
||||
{
|
||||
ChildrenMap::const_iterator it = m_Childs.find(name);
|
||||
|
@ -32,22 +32,32 @@ public:
|
||||
typedef std::map<std::string, CParamNode> ChildrenMap;
|
||||
|
||||
/**
|
||||
* Loads the XML data specified by 'file' into the node 'ret'.
|
||||
* Any existing data in 'ret' will be overwritten or else kept, so this
|
||||
* Loads the XML data specified by @a file into the node @a ret.
|
||||
* 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.
|
||||
*/
|
||||
static void LoadXML(CParamNode& ret, const XMBFile& file);
|
||||
|
||||
/**
|
||||
* See LoadXML, but parses the XML string 'xml'.
|
||||
* @return error code if parsing failed, else PSRETURN_OK
|
||||
* See LoadXML, but parses the XML string @a xml.
|
||||
* @return error code if parsing failed, else @c PSRETURN_OK
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
|
@ -74,7 +74,15 @@ public:
|
||||
const CParamNode* actor = tempMan->LoadTemplate(ent2, L"actor|example", -1);
|
||||
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>");
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
@ -94,11 +102,13 @@ public:
|
||||
|
||||
TS_ASSERT(tempMan->LoadTemplate(ent2, L"nonexistent", -1) == NULL);
|
||||
|
||||
const CParamNode* inherit_loop = tempMan->LoadTemplate(ent2, L"inherit-loop", -1);
|
||||
TS_ASSERT(inherit_loop == NULL);
|
||||
TS_ASSERT(tempMan->LoadTemplate(ent2, L"inherit-loop", -1) == NULL);
|
||||
|
||||
const CParamNode* inherit_broken = tempMan->LoadTemplate(ent2, L"inherit-broken", -1);
|
||||
TS_ASSERT(inherit_broken == NULL);
|
||||
TS_ASSERT(tempMan->LoadTemplate(ent2, L"inherit-broken", -1) == 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()
|
||||
|
@ -71,6 +71,29 @@ public:
|
||||
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()
|
||||
{
|
||||
CSimContext context;
|
||||
@ -403,7 +426,7 @@ public:
|
||||
CComponentManager man(context);
|
||||
man.LoadComponentTypes();
|
||||
|
||||
entity_id_t ent1 = 1, ent2 = 2;
|
||||
entity_id_t ent1 = 1, ent2 = 2, ent3 = FIRST_LOCAL_ENTITY;
|
||||
CParamNode noParam;
|
||||
|
||||
CParamNode testParam;
|
||||
@ -412,10 +435,12 @@ public:
|
||||
man.AddComponent(ent1, CID_Test1A, noParam);
|
||||
man.AddComponent(ent1, CID_Test2A, noParam);
|
||||
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<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<ICmpTest2*> (man.QueryInterface(ent3, IID_Test2))->GetX(), 21000);
|
||||
|
||||
std::stringstream debugStream;
|
||||
TS_ASSERT(man.DumpDebugState(debugStream));
|
||||
@ -430,6 +455,11 @@ public:
|
||||
" Test1A:\n"
|
||||
" x: 1234\n"
|
||||
"\n"
|
||||
"- id: 536870912\n"
|
||||
" type: local\n"
|
||||
" Test2A:\n"
|
||||
" x: 21000\n"
|
||||
"\n"
|
||||
);
|
||||
|
||||
std::string hash;
|
||||
@ -441,7 +471,8 @@ public:
|
||||
|
||||
std::stringstream 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
|
||||
"\x06\x00\x00\x00Test1A"
|
||||
"\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_Test2) == NULL);
|
||||
TS_ASSERT(man2.QueryInterface(ent2, IID_Test1) == NULL);
|
||||
TS_ASSERT(man2.QueryInterface(ent3, IID_Test2) == NULL);
|
||||
|
||||
TS_ASSERT(man2.DeserializeState(stateStream));
|
||||
|
||||
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<ICmpTest1*> (man2.QueryInterface(ent2, IID_Test1))->GetX(), 1234);
|
||||
TS_ASSERT(man2.QueryInterface(ent3, IID_Test2) == NULL);
|
||||
}
|
||||
|
||||
void test_script_serialization()
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "lib/self_test.h"
|
||||
|
||||
#include "simulation2/Simulation2.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "simulation2/components/ICmpTest.h"
|
||||
|
||||
#include "graphics/Terrain.h"
|
||||
@ -51,15 +52,6 @@ public:
|
||||
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()
|
||||
{
|
||||
CSimulation2 sim(NULL, &m_Terrain);
|
||||
@ -80,6 +72,57 @@ public:
|
||||
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()
|
||||
{
|
||||
CSimulation2 sim(NULL, &m_Terrain);
|
||||
|
@ -289,9 +289,9 @@ END_COMMAND(SetObjectSettings);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static size_t g_PreviewUnitID = invalidUnitId;
|
||||
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 CVector3D GetUnitPos(const Position& pos, bool floating)
|
||||
@ -343,6 +343,58 @@ static bool ParseObjectName(const CStrW& obj, bool& isEntity, CStrW& name)
|
||||
|
||||
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);
|
||||
|
||||
// Don't recreate the unit unless it's changed
|
||||
@ -433,8 +485,9 @@ BEGIN_COMMAND(CreateObject)
|
||||
{
|
||||
CVector3D m_Pos;
|
||||
float m_Angle;
|
||||
size_t m_ID;
|
||||
size_t m_ID; // old simulation system
|
||||
size_t m_Player;
|
||||
entity_id_t m_EntityID; // new simulation system
|
||||
|
||||
void Do()
|
||||
{
|
||||
@ -446,8 +499,7 @@ BEGIN_COMMAND(CreateObject)
|
||||
{
|
||||
// Aim from m_Pos towards msg->target
|
||||
CVector3D target = msg->target->GetWorldSpace(m_Pos.Y);
|
||||
CVector2D dir(target.X-m_Pos.X, target.Z-m_Pos.Z);
|
||||
m_Angle = atan2(dir.x, dir.y);
|
||||
m_Angle = atan2(target.X-m_Pos.X, target.Z-m_Pos.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -457,14 +509,38 @@ BEGIN_COMMAND(CreateObject)
|
||||
// TODO: variations too
|
||||
m_Player = msg->settings->player;
|
||||
|
||||
// Get a new ID, for future reference to this unit
|
||||
m_ID = GetUnitManager().GetNewID();
|
||||
if (!g_UseSimulation2)
|
||||
{
|
||||
// Get a new ID, for future reference to this unit
|
||||
m_ID = GetUnitManager().GetNewID();
|
||||
}
|
||||
|
||||
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;
|
||||
CStrW name;
|
||||
if (ParseObjectName(*msg->id, isEntity, name))
|
||||
@ -532,6 +608,19 @@ BEGIN_COMMAND(CreateObject)
|
||||
|
||||
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);
|
||||
if (unit)
|
||||
{
|
||||
|
@ -159,6 +159,10 @@ void ViewGame::Update(float frameLength)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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,
|
||||
// not in every call to g_Game->Update
|
||||
g_Game->GetSimulation()->Interpolate(actualFrameLength);
|
||||
|
Loading…
Reference in New Issue
Block a user