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:
parent
ec7a452d4e
commit
d936bde74a
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)) \
|
||||
{ \
|
||||
} \
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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,
|
||||
|
88
source/simulation2/system/DynamicSubscription.cpp
Normal file
88
source/simulation2/system/DynamicSubscription.cpp
Normal 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");
|
||||
}
|
65
source/simulation2/system/DynamicSubscription.h
Normal file
65
source/simulation2/system/DynamicSubscription.h
Normal 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
|
@ -58,6 +58,7 @@ public:
|
||||
|
||||
virtual JSClass* GetJSClass() const;
|
||||
virtual jsval GetJSInstance() const;
|
||||
virtual int GetComponentTypeId() const = 0;
|
||||
|
||||
private:
|
||||
CEntityHandle m_EntityHandle;
|
||||
|
Loading…
Reference in New Issue
Block a user