1
1
forked from 0ad/0ad

Dynamic message subscriptions.

Allow components to individually subscribe/unsubscribe to messages,
instead of statically subscribing the entire component type. Use this
for most Interpolate/RenderSubmit messages, to avoid the performance
cost of passing those messages to a large number of components that will
just ignore them anyway.

On Azure Coast this reduces total time per frame by about 30% on a
CPU-bound system.

This was SVN commit r15400.
This commit is contained in:
Ykkrosh 2014-06-19 23:20:12 +00:00
parent ec7a452d4e
commit d936bde74a
12 changed files with 391 additions and 22 deletions

View File

@ -45,9 +45,8 @@
class CCmpDecay : public ICmpDecay
{
public:
static void ClassInit(CComponentManager& componentManager)
static void ClassInit(CComponentManager& UNUSED(componentManager))
{
componentManager.SubscribeToMessageType(MT_Interpolate);
}
DEFAULT_COMPONENT_ALLOCATOR(Decay)
@ -109,6 +108,9 @@ public:
debug_warn(L"CCmpDecay must not be used on non-local (network-synchronised) entities");
m_Active = false;
}
if (m_Active)
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, true);
}
virtual void Deinit()
@ -131,6 +133,8 @@ public:
{
case MT_Interpolate:
{
PROFILE3("Decay::Interpolate");
if (!m_Active)
break;

View File

@ -30,10 +30,8 @@
class CCmpOverlayRenderer : public ICmpOverlayRenderer
{
public:
static void ClassInit(CComponentManager& componentManager)
static void ClassInit(CComponentManager& UNUSED(componentManager))
{
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(OverlayRenderer)
@ -79,12 +77,14 @@ public:
{
case MT_Interpolate:
{
PROFILE3("OverlayRenderer::Interpolate");
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
PROFILE3("OverlayRenderer::RenderSubmit");
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector);
break;
@ -92,10 +92,24 @@ public:
}
}
/*
* Must be called whenever the size of m_Sprites changes,
* to determine whether we need to respond to rendering messages.
*/
void UpdateMessageSubscriptions()
{
bool needRender = !m_Sprites.empty();
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needRender);
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
}
virtual void Reset()
{
m_Sprites.clear();
m_SpriteOffsets.clear();
UpdateMessageSubscriptions();
}
virtual void AddSprite(VfsPath textureName, CFixedVector2D corner0, CFixedVector2D corner1, CFixedVector3D position)
@ -111,6 +125,8 @@ public:
m_Sprites.push_back(sprite);
m_SpriteOffsets.push_back(CVector3D(position));
UpdateMessageSubscriptions();
}
void Interpolate(float UNUSED(frameTime), float frameOffset)

View File

@ -44,7 +44,6 @@ public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_WaterChanged);
componentManager.SubscribeToMessageType(MT_Deserialized);
@ -97,6 +96,8 @@ public:
float m_LastInterpolatedRotX, m_LastInterpolatedRotZ;
bool m_ActorFloating;
bool m_EnabledMessageInterpolate;
static std::string GetSchema()
{
return
@ -156,6 +157,8 @@ public:
m_TurretPosition = CFixedVector3D();
m_ActorFloating = false;
m_EnabledMessageInterpolate = false;
}
virtual void Deinit()
@ -252,6 +255,8 @@ public:
if (m_InWorld)
UpdateXZRotation();
UpdateMessageSubscriptions();
}
void Deserialized()
@ -528,6 +533,7 @@ public:
m_RotY = y;
AdvertisePositionChanges();
UpdateMessageSubscriptions();
}
virtual void SetYRotation(entity_angle_t y)
@ -550,6 +556,7 @@ public:
}
AdvertisePositionChanges();
UpdateMessageSubscriptions();
}
virtual void SetXZRotation(entity_angle_t x, entity_angle_t z)
@ -751,6 +758,8 @@ public:
{
case MT_Interpolate:
{
PROFILE3("Position::Interpolate");
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
float rotY = m_RotY.ToFloat();
@ -776,6 +785,8 @@ public:
UpdateXZRotation();
}
UpdateMessageSubscriptions();
}
break;
@ -837,6 +848,25 @@ public:
private:
/*
* Must be called whenever m_RotY or m_InterpolatedRotY change,
* to determine whether we need to call Interpolate to make the unit rotate.
*/
void UpdateMessageSubscriptions()
{
bool needInterpolate = false;
float rotY = m_RotY.ToFloat();
if (rotY != m_InterpolatedRotY)
needInterpolate = true;
if (needInterpolate != m_EnabledMessageInterpolate)
{
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate);
m_EnabledMessageInterpolate = needInterpolate;
}
}
/**
* This must be called after changing anything that will affect the
* return value of GetPosition2D() or GetRotation().Y:

View File

@ -77,7 +77,6 @@ class CCmpRallyPointRenderer : public ICmpRallyPointRenderer
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Destroy);
@ -224,6 +223,7 @@ public:
{
case MT_RenderSubmit:
{
PROFILE3("RallyPoint::RenderSubmit");
if (m_Displayed && IsSet())
{
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
@ -263,6 +263,16 @@ public:
}
}
/*
* Must be called whenever m_Displayed or the size of m_RallyPoints change,
* to determine whether we need to respond to render messages.
*/
void UpdateMessageSubscriptions()
{
bool needRender = m_Displayed && IsSet();
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
}
virtual void AddPosition_wrapper(CFixedVector2D pos)
{
AddPosition(pos, false);
@ -274,6 +284,7 @@ public:
{
m_RallyPoints.clear();
AddPosition(pos, true);
// Don't need to UpdateMessageSubscriptions here since AddPosition already calls it
}
}
@ -305,6 +316,8 @@ public:
// only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
// while this rally point was not being displayed.
UpdateOverlayLines();
UpdateMessageSubscriptions();
}
}
@ -312,6 +325,7 @@ public:
{
m_RallyPoints.clear();
RecomputeAllRallyPointPaths();
UpdateMessageSubscriptions();
}
/**
@ -336,6 +350,8 @@ private:
RecomputeAllRallyPointPaths();
else
RecomputeRallyPointPath_wrapper(m_RallyPoints.size()-1);
UpdateMessageSubscriptions();
}
/**

View File

@ -53,14 +53,10 @@ class CCmpSelectable : public ICmpSelectable
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_PositionChanged);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_WaterChanged);
// TODO: it'd be nice if we didn't get these messages except in the rare
// cases where we're actually drawing a selection highlight
}
DEFAULT_COMPONENT_ALLOCATOR(Selectable)
@ -148,6 +144,10 @@ public:
m_OverlayDescriptor.m_LineTextureMask = CStrIntern(textureBasePath + outlineNode.GetChild("LineTextureMask").ToUTF8());
m_OverlayDescriptor.m_LineThickness = outlineNode.GetChild("LineThickness").ToFloat();
}
m_EnabledInterpolate = false;
m_EnabledRenderSubmit = false;
UpdateMessageSubscriptions();
}
virtual void Deinit() { }
@ -200,11 +200,14 @@ public:
m_FadeBaselineAlpha = m_Color.a;
m_FadeDeltaAlpha = alpha - m_FadeBaselineAlpha;
m_FadeProgress = 0.f;
UpdateMessageSubscriptions();
}
virtual void SetVisibility(bool visible)
{
m_Visible = visible;
UpdateMessageSubscriptions();
}
virtual bool IsEditorOnly()
@ -230,6 +233,16 @@ public:
/// Explicitly invalidates the static overlay.
void InvalidateStaticOverlay();
/**
* Subscribe/unsubscribe to MT_Interpolate, MT_RenderSubmit, depending on
* whether we will do any actual work when receiving them. (This is to avoid
* the performance cost of receiving messages in the typical case when the
* entity is not selected.)
*
* Must be called after changing m_Visible, m_FadeDeltaAlpha, m_Color.a
*/
void UpdateMessageSubscriptions();
private:
SOverlayDescriptor m_OverlayDescriptor;
SOverlayTexturedLine* m_BuildingOverlay;
@ -238,6 +251,9 @@ private:
SOverlayLine* m_DebugBoundingBoxOverlay;
SOverlayLine* m_DebugSelectionBoxOverlay;
bool m_EnabledInterpolate;
bool m_EnabledRenderSubmit;
// Whether the selectable will be rendered.
bool m_Visible;
// Whether the entity is only selectable in Atlas editor
@ -272,6 +288,8 @@ void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
{
case MT_Interpolate:
{
PROFILE3("Selectable::Interpolate");
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
if (m_FadeDeltaAlpha != 0.f)
@ -297,6 +315,8 @@ void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
if (m_Color.a > 0)
UpdateDynamicOverlay(msgData.offset);
UpdateMessageSubscriptions();
break;
}
case MT_OwnershipChanged:
@ -333,6 +353,8 @@ void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
}
case MT_RenderSubmit:
{
PROFILE3("Selectable::RenderSubmit");
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector);
@ -341,6 +363,30 @@ void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
}
}
void CCmpSelectable::UpdateMessageSubscriptions()
{
bool needInterpolate = false;
bool needRenderSubmit = false;
if (m_FadeDeltaAlpha != 0.f || m_Color.a > 0)
needInterpolate = true;
if (m_Visible && m_Color.a > 0)
needRenderSubmit = true;
if (needInterpolate != m_EnabledInterpolate)
{
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate);
m_EnabledInterpolate = needInterpolate;
}
if (needRenderSubmit != m_EnabledRenderSubmit)
{
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRenderSubmit);
m_EnabledRenderSubmit = needRenderSubmit;
}
}
void CCmpSelectable::InvalidateStaticOverlay()
{
SAFE_DELETE(m_BuildingOverlay);

View File

@ -116,7 +116,6 @@ public:
{
componentManager.SubscribeToMessageType(MT_Update_MotionFormation);
componentManager.SubscribeToMessageType(MT_Update_MotionUnit);
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
componentManager.SubscribeToMessageType(MT_PathResult);
componentManager.SubscribeToMessageType(MT_ValueModification);
}
@ -403,6 +402,7 @@ public:
}
case MT_RenderSubmit:
{
PROFILE3("UnitMotion::RenderSubmit");
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector);
break;
@ -436,6 +436,12 @@ public:
}
}
void UpdateMessageSubscriptions()
{
bool needRender = m_DebugOverlayEnabled;
GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
}
virtual bool IsMoving()
{
return m_Moving;
@ -487,6 +493,7 @@ public:
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;
UpdateMessageSubscriptions();
}
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);

View File

@ -44,6 +44,10 @@
#define DEFAULT_COMPONENT_ALLOCATOR(cname) \
static IComponent* Allocate(ScriptInterface&, jsval) { return new CCmp##cname(); } \
static void Deallocate(IComponent* cmp) { delete static_cast<CCmp##cname*> (cmp); } \
virtual int GetComponentTypeId() const \
{ \
return CID_##cname; \
}
#define DEFAULT_SCRIPT_WRAPPER(cname) \
static void ClassInit(CComponentManager& UNUSED(componentManager)) { } \
@ -84,11 +88,19 @@
{ \
return m_Script.GetInstance(); \
} \
virtual int GetComponentTypeId() const \
{ \
return CID_##cname; \
} \
private: \
CComponentTypeScript m_Script; \
CComponentTypeScript m_Script; \
public:
#define DEFAULT_MOCK_COMPONENT() \
virtual int GetComponentTypeId() const \
{ \
return -1; \
} \
virtual void Init(const CParamNode& UNUSED(paramNode)) \
{ \
} \

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,6 +19,7 @@
#include "ComponentManager.h"
#include "DynamicSubscription.h"
#include "IComponent.h"
#include "ParamNode.h"
#include "SimContext.h"
@ -567,6 +568,50 @@ void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid)
std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents
}
void CComponentManager::FlattenDynamicSubscriptions()
{
std::map<MessageTypeId, CDynamicSubscription>::iterator it;
for (it = m_DynamicMessageSubscriptionsNonsync.begin();
it != m_DynamicMessageSubscriptionsNonsync.end(); ++it)
{
it->second.Flatten();
}
}
void CComponentManager::DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enable)
{
if (enable)
{
bool newlyInserted = m_DynamicMessageSubscriptionsNonsyncByComponent[component].insert(mtid).second;
if (newlyInserted)
m_DynamicMessageSubscriptionsNonsync[mtid].Add(component);
}
else
{
size_t numRemoved = m_DynamicMessageSubscriptionsNonsyncByComponent[component].erase(mtid);
if (numRemoved)
m_DynamicMessageSubscriptionsNonsync[mtid].Remove(component);
}
}
void CComponentManager::RemoveComponentDynamicSubscriptions(IComponent* component)
{
std::map<IComponent*, std::set<MessageTypeId> >::iterator it = m_DynamicMessageSubscriptionsNonsyncByComponent.find(component);
if (it == m_DynamicMessageSubscriptionsNonsyncByComponent.end())
return;
std::set<MessageTypeId>::iterator mtit;
for (mtit = it->second.begin(); mtit != it->second.end(); ++mtit)
{
m_DynamicMessageSubscriptionsNonsync[*mtit].Remove(component);
// Need to flatten the subscription lists immediately to avoid dangling IComponent* references
m_DynamicMessageSubscriptionsNonsync[*mtit].Flatten();
}
m_DynamicMessageSubscriptionsNonsyncByComponent.erase(it);
}
CComponentManager::ComponentTypeId CComponentManager::LookupCID(const std::string& cname) const
{
std::map<std::string, ComponentTypeId>::const_iterator it = m_ComponentTypeIdsByName.find(cname);
@ -830,6 +875,10 @@ void CComponentManager::FlushDestroyedComponents()
std::vector<entity_id_t> queue;
queue.swap(m_DestructionQueue);
// Flatten all the dynamic subscriptions to ensure there are no dangling
// references in the 'removed' lists to components we're going to delete
FlattenDynamicSubscriptions();
for (std::vector<entity_id_t>::iterator it = queue.begin(); it != queue.end(); ++it)
{
entity_id_t ent = *it;
@ -846,6 +895,7 @@ void CComponentManager::FlushDestroyedComponents()
if (eit != iit->second.end())
{
eit->second->Deinit();
RemoveComponentDynamicSubscriptions(eit->second);
m_ComponentTypesById[iit->first].dealloc(eit->second);
iit->second.erase(ent);
handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL;
@ -916,7 +966,7 @@ const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesW
return m_ComponentsByInterface[iid];
}
void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const
void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg)
{
// Send the message to components of ent, that subscribed locally to this message
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it;
@ -941,7 +991,7 @@ void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const
SendGlobalMessage(ent, msg);
}
void CComponentManager::BroadcastMessage(const CMessage& msg) const
void CComponentManager::BroadcastMessage(const CMessage& msg)
{
// Send the message to components of all entities that subscribed locally to this message
std::map<MessageTypeId, std::vector<ComponentTypeId> >::const_iterator it;
@ -966,7 +1016,7 @@ void CComponentManager::BroadcastMessage(const CMessage& msg) const
SendGlobalMessage(INVALID_ENTITY, msg);
}
void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) const
void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg)
{
// (Common functionality for PostMessage and BroadcastMessage)
@ -999,8 +1049,17 @@ void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg)
eit->second->HandleMessage(msg, true);
}
}
}
// Send the message to component instances that dynamically subscribed to this message
std::map<MessageTypeId, CDynamicSubscription>::iterator dit = m_DynamicMessageSubscriptionsNonsync.find(msg.GetType());
if (dit != m_DynamicMessageSubscriptionsNonsync.end())
{
dit->second.Flatten();
const std::vector<IComponent*>& dynamic = dit->second.GetComponents();
for (size_t i = 0; i < dynamic.size(); i++)
dynamic[i]->HandleMessage(msg, false);
}
}
std::string CComponentManager::GenerateSchema()
{

View File

@ -33,6 +33,7 @@ class IComponent;
class CParamNode;
class CMessage;
class CSimContext;
class CDynamicSubscription;
class CComponentManager
{
@ -72,7 +73,8 @@ private:
CScriptValRooted ctor; // only valid if type == CT_Script
};
struct FindJSONFilesCallbackData {
struct FindJSONFilesCallbackData
{
VfsPath path;
std::vector<std::string> templates;
};
@ -114,6 +116,23 @@ public:
*/
void SubscribeGloballyToMessageType(MessageTypeId mtid);
/**
* Subscribe the given component instance to all messages of the given message type.
* The component's HandleMessage will be called on any BroadcastMessage or PostMessage of
* this message type, regardless of the entity.
*
* This can be called at any time (including inside the HandleMessage callback for this message type).
*
* The component type must not have statically subscribed to this message type in its ClassInit.
*
* The subscription status is not saved or network-synchronised. Components must remember to
* resubscribe in their Deserialize methods if they still want the message.
*
* This is primarily intended for Interpolate and RenderSubmit messages, to avoid the cost of
* sending the message to components that do not currently need to do any rendering.
*/
void DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enabled);
/**
* @param cname Requested component type name (not including any "CID" or "CCmp" prefix)
* @return The component type id, or CID__Invalid if not found
@ -221,13 +240,13 @@ public:
* components of that entity which subscribed to the message type, and by any other components
* that subscribed globally to the message type.
*/
void PostMessage(entity_id_t ent, const CMessage& msg) const;
void PostMessage(entity_id_t ent, const CMessage& msg);
/**
* Send a message, not targeted at any particular entity. The message will be received by any
* components that subscribed (either globally or not) to the message type.
*/
void BroadcastMessage(const CMessage& msg) const;
void BroadcastMessage(const CMessage& msg);
/**
* Resets the dynamic simulation state (deletes all entities, resets entity ID counters;
@ -273,7 +292,10 @@ private:
static Status FindJSONFilesCallback(const VfsPath&, const CFileInfo&, const uintptr_t);
CMessage* ConstructMessage(int mtid, CScriptVal data);
void SendGlobalMessage(entity_id_t ent, const CMessage& msg) const;
void SendGlobalMessage(entity_id_t ent, const CMessage& msg);
void FlattenDynamicSubscriptions();
void RemoveComponentDynamicSubscriptions(IComponent* component);
ComponentTypeId GetScriptWrapper(InterfaceId iid);
@ -299,6 +321,9 @@ private:
std::map<MessageTypeId, std::string> m_MessageTypeNamesById;
std::map<std::string, InterfaceId> m_InterfaceIdsByName;
std::map<MessageTypeId, CDynamicSubscription> m_DynamicMessageSubscriptionsNonsync;
std::map<IComponent*, std::set<MessageTypeId> > m_DynamicMessageSubscriptionsNonsyncByComponent;
std::map<entity_id_t, SEntityComponentCache*> m_ComponentCaches;
// TODO: maintaining both ComponentsBy* is nasty; can we get rid of one,

View File

@ -0,0 +1,88 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "DynamicSubscription.h"
void CDynamicSubscription::Add(IComponent* cmp)
{
m_Removed.erase(cmp);
m_Added.insert(cmp);
}
void CDynamicSubscription::Remove(IComponent* cmp)
{
m_Added.erase(cmp);
m_Removed.insert(cmp);
}
void CDynamicSubscription::Flatten()
{
if (m_Added.empty() && m_Removed.empty())
return;
std::vector<IComponent*> tmp;
tmp.reserve(m_Components.size() + m_Added.size());
// tmp = m_Components - m_Removed
std::set_difference(
m_Components.begin(), m_Components.end(),
m_Removed.begin(), m_Removed.end(),
std::back_inserter(tmp),
CompareIComponent());
m_Components.clear();
// m_Components = tmp + m_Added
std::set_union(
tmp.begin(), tmp.end(),
m_Added.begin(), m_Added.end(),
std::back_inserter(m_Components),
CompareIComponent());
m_Added.clear();
m_Removed.clear();
}
const std::vector<IComponent*>& CDynamicSubscription::GetComponents()
{
// Must be flattened before calling this function
ENSURE(m_Added.empty() && m_Removed.empty());
return m_Components;
}
void CDynamicSubscription::DebugDump()
{
std::set<IComponent*, CompareIComponent>::iterator it;
debug_printf(L"components:");
for (size_t i = 0; i < m_Components.size(); i++)
debug_printf(L" %p", m_Components[i]);
debug_printf(L"\n");
debug_printf(L"added:");
for (it = m_Added.begin(); it != m_Added.end(); ++it)
debug_printf(L" %p", *it);
debug_printf(L"\n");
debug_printf(L"removed:");
for (it = m_Removed.begin(); it != m_Removed.end(); ++it)
debug_printf(L" %p", *it);
debug_printf(L"\n");
}

View File

@ -0,0 +1,65 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_DYNAMICSUBSCRIPTION
#define INCLUDED_DYNAMICSUBSCRIPTION
#include "IComponent.h"
/**
* A list of components that are dynamically subscribed to a particular
* message. The components list is sorted by (entity_id, ComponentTypeId),
* with no duplicates.
*
* To cope with changes to the subscription list while a message is still
* being broadcast, all changes are stored in the added/removed sets. The
* next time a message is sent, they will be merged into the main components
* list.
*/
class CDynamicSubscription
{
struct CompareIComponent
{
bool operator()(const IComponent* cmpA, const IComponent* cmpB)
{
entity_id_t entityA = cmpA->GetEntityId();
entity_id_t entityB = cmpB->GetEntityId();
if (entityA < entityB)
return true;
if (entityB < entityA)
return false;
int cidA = cmpA->GetComponentTypeId();
int cidB = cmpB->GetComponentTypeId();
if (cidA < cidB)
return true;
return false;
}
};
public:
void Add(IComponent* cmp);
void Remove(IComponent* cmp);
void Flatten();
const std::vector<IComponent*>& GetComponents();
void DebugDump();
private:
std::vector<IComponent*> m_Components; // always in CompareIComponent order
std::set<IComponent*, CompareIComponent> m_Added;
std::set<IComponent*, CompareIComponent> m_Removed;
};
#endif // INCLUDED_DYNAMICSUBSCRIPTION

View File

@ -58,6 +58,7 @@ public:
virtual JSClass* GetJSClass() const;
virtual jsval GetJSInstance() const;
virtual int GetComponentTypeId() const = 0;
private:
CEntityHandle m_EntityHandle;