diff --git a/source/simulation2/components/CCmpDecay.cpp b/source/simulation2/components/CCmpDecay.cpp index eac41ee9b5..cc33837649 100644 --- a/source/simulation2/components/CCmpDecay.cpp +++ b/source/simulation2/components/CCmpDecay.cpp @@ -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; diff --git a/source/simulation2/components/CCmpOverlayRenderer.cpp b/source/simulation2/components/CCmpOverlayRenderer.cpp index b5ca581f45..b2ad7e8de5 100644 --- a/source/simulation2/components/CCmpOverlayRenderer.cpp +++ b/source/simulation2/components/CCmpOverlayRenderer.cpp @@ -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 (msg); Interpolate(msgData.deltaSimTime, msgData.offset); break; } case MT_RenderSubmit: { + PROFILE3("OverlayRenderer::RenderSubmit"); const CMessageRenderSubmit& msgData = static_cast (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) diff --git a/source/simulation2/components/CCmpPosition.cpp b/source/simulation2/components/CCmpPosition.cpp index 6a1870cb98..3c7ae09d31 100644 --- a/source/simulation2/components/CCmpPosition.cpp +++ b/source/simulation2/components/CCmpPosition.cpp @@ -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 (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: diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp index 193d044df4..a5258fe617 100644 --- a/source/simulation2/components/CCmpRallyPointRenderer.cpp +++ b/source/simulation2/components/CCmpRallyPointRenderer.cpp @@ -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 (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(); } /** diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp index e1322ad44d..b482be3c2d 100644 --- a/source/simulation2/components/CCmpSelectable.cpp +++ b/source/simulation2/components/CCmpSelectable.cpp @@ -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 (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 (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); diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index 10a5cf639b..6a4ebdab66 100644 --- a/source/simulation2/components/CCmpUnitMotion.cpp +++ b/source/simulation2/components/CCmpUnitMotion.cpp @@ -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 (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); diff --git a/source/simulation2/system/Component.h b/source/simulation2/system/Component.h index 3c27e60e13..789149fdc4 100644 --- a/source/simulation2/system/Component.h +++ b/source/simulation2/system/Component.h @@ -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 (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)) \ { \ } \ diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index 224774edd3..ffeee2d53e 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -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::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 >::iterator it = m_DynamicMessageSubscriptionsNonsyncByComponent.find(component); + if (it == m_DynamicMessageSubscriptionsNonsyncByComponent.end()) + return; + + std::set::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::const_iterator it = m_ComponentTypeIdsByName.find(cname); @@ -830,6 +875,10 @@ void CComponentManager::FlushDestroyedComponents() std::vector 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::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 >::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 >::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::iterator dit = m_DynamicMessageSubscriptionsNonsync.find(msg.GetType()); + if (dit != m_DynamicMessageSubscriptionsNonsync.end()) + { + dit->second.Flatten(); + const std::vector& dynamic = dit->second.GetComponents(); + for (size_t i = 0; i < dynamic.size(); i++) + dynamic[i]->HandleMessage(msg, false); + } +} std::string CComponentManager::GenerateSchema() { diff --git a/source/simulation2/system/ComponentManager.h b/source/simulation2/system/ComponentManager.h index 00f2f691af..0d1b37737b 100644 --- a/source/simulation2/system/ComponentManager.h +++ b/source/simulation2/system/ComponentManager.h @@ -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 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 m_MessageTypeNamesById; std::map m_InterfaceIdsByName; + std::map m_DynamicMessageSubscriptionsNonsync; + std::map > m_DynamicMessageSubscriptionsNonsyncByComponent; + std::map m_ComponentCaches; // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one, diff --git a/source/simulation2/system/DynamicSubscription.cpp b/source/simulation2/system/DynamicSubscription.cpp new file mode 100644 index 0000000000..5894560074 --- /dev/null +++ b/source/simulation2/system/DynamicSubscription.cpp @@ -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 . + */ + +#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 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& CDynamicSubscription::GetComponents() +{ + // Must be flattened before calling this function + ENSURE(m_Added.empty() && m_Removed.empty()); + + return m_Components; +} + +void CDynamicSubscription::DebugDump() +{ + std::set::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"); +} diff --git a/source/simulation2/system/DynamicSubscription.h b/source/simulation2/system/DynamicSubscription.h new file mode 100644 index 0000000000..62d004368d --- /dev/null +++ b/source/simulation2/system/DynamicSubscription.h @@ -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 . + */ + +#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& GetComponents(); + void DebugDump(); + +private: + std::vector m_Components; // always in CompareIComponent order + std::set m_Added; + std::set m_Removed; +}; + +#endif // INCLUDED_DYNAMICSUBSCRIPTION diff --git a/source/simulation2/system/IComponent.h b/source/simulation2/system/IComponent.h index 6fcfe0fd6f..7e1b29a935 100644 --- a/source/simulation2/system/IComponent.h +++ b/source/simulation2/system/IComponent.h @@ -58,6 +58,7 @@ public: virtual JSClass* GetJSClass() const; virtual jsval GetJSInstance() const; + virtual int GetComponentTypeId() const = 0; private: CEntityHandle m_EntityHandle;