Index: source/graphics/MapWriter.cpp =================================================================== --- source/graphics/MapWriter.cpp +++ source/graphics/MapWriter.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -319,8 +319,8 @@ // This will probably need to be changed in the future, but for now we'll // just save all entities that have a position - CSimulation2::InterfaceList ents = sim.GetEntitiesWithInterface(IID_Position); - for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) + CSimulation2::InterfaceList comps = sim.GetComponentsByInterface(IID_Position); + for (CSimulation2::InterfaceList::const_iterator it = comps.begin(); it != comps.end(); ++it) { entity_id_t ent = it->first; Index: source/graphics/MiniMapTexture.cpp =================================================================== --- source/graphics/MiniMapTexture.cpp +++ source/graphics/MiniMapTexture.cpp @@ -518,7 +518,7 @@ { m_Icons.clear(); - CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap); + CSimulation2::InterfaceList comps = m_Simulation.GetComponentsByInterface(IID_Minimap); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); @@ -544,7 +544,7 @@ bool iconsCountOverflow = false; entity_pos_t posX, posZ; - for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) + for (CSimulation2::InterfaceList::const_iterator it = comps.begin(); it != comps.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) Index: source/graphics/ObjectManager.cpp =================================================================== --- source/graphics/ObjectManager.cpp +++ source/graphics/ObjectManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -30,6 +30,7 @@ #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpVisual.h" +#include "simulation2/system/ComponentDataHolder.h" bool CObjectManager::ObjectKey::operator< (const CObjectManager::ObjectKey& a) const { @@ -167,7 +168,7 @@ changed = true; } - const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual); + SComponentDataGenerator gen = m_Simulation.GetComponentsByInterfaceUnordered(IID_Visual); // Reload actors that use a changed object for (std::pair>& actor : m_ActorDefs) @@ -181,8 +182,9 @@ // object with all correct variations, and we don't want to waste space storing it just for the // rare occurrence of hotloading, so we'll tell the component (which does preserve the information) // to do the reloading itself - for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit) - static_cast(eit->second)->Hotload(actor.first); + while (IComponent* cmp = gen.Next()) + static_cast(cmp)->Hotload(actor.first); + gen.Reset(); } } @@ -203,9 +205,9 @@ m_QualityLevel = quality > 255 ? 255 : quality < 0 ? 0 : quality; // No need to reload entries or actors, but we do need to reload all units. - const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual); - for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit) - static_cast(eit->second)->Hotload(); + SComponentDataGenerator gen = m_Simulation.GetComponentsByInterfaceUnordered(IID_Visual); + while (IComponent* cmp = gen.Next()) + static_cast(cmp)->Hotload(); // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise. m_Simulation.Interpolate(0.f, 0.f, 0.f); @@ -236,9 +238,9 @@ actor.second.outdated = true; // Reload visual actor components. - const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual); - for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit) - static_cast(eit->second)->Hotload(); + SComponentDataGenerator gen = m_Simulation.GetComponentsByInterfaceUnordered(IID_Visual); + while (IComponent* cmp = gen.Next()) + static_cast(cmp)->Hotload(); // Trigger an interpolate call - needed because the game is generally paused & models disappear otherwise. m_Simulation.Interpolate(0.f, 0.f, 0.f); Index: source/ps/Game.cpp =================================================================== --- source/ps/Game.cpp +++ source/ps/Game.cpp @@ -457,10 +457,10 @@ bool CGame::IsGameFinished() const { - for (const std::pair& p : m_Simulation2->GetEntitiesWithInterface(IID_Player)) + SComponentDataGenerator gen = m_Simulation2->GetComponentsByInterfaceUnordered(IID_Player); + while (IComponent* cmp = gen.Next()) { - CmpPtr cmpPlayer(*m_Simulation2, p.first); - if (cmpPlayer && cmpPlayer->GetState() == "won") + if (static_cast(cmp)->GetState() == "won") return true; } Index: source/simulation2/Simulation2.h =================================================================== --- source/simulation2/Simulation2.h +++ source/simulation2/Simulation2.h @@ -22,6 +22,7 @@ #include "simulation2/helpers/SimulationCommand.h" #include "simulation2/system/CmpPtr.h" #include "simulation2/system/Components.h" +#include "simulation2/system/ComponentDataHolder.h" #include #include @@ -215,13 +216,13 @@ * Returns a list of components implementing the given interface, and their * associated entities, sorted by entity ID. */ - InterfaceList GetEntitiesWithInterface(int iid); + InterfaceList GetComponentsByInterface(int iid); /** * Returns a list of components implementing the given interface, and their * associated entities, as an unordered map. */ - const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(int iid); + SComponentDataGenerator GetComponentsByInterfaceUnordered(int iid); const CSimContext& GetSimContext() const; ScriptInterface& GetScriptInterface() const; Index: source/simulation2/Simulation2.cpp =================================================================== --- source/simulation2/Simulation2.cpp +++ source/simulation2/Simulation2.cpp @@ -708,14 +708,14 @@ m->m_ComponentManager.BroadcastMessage(msg); } -CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid) +CSimulation2::InterfaceList CSimulation2::GetComponentsByInterface(int iid) { - return m->m_ComponentManager.GetEntitiesWithInterface(iid); + return m->m_ComponentManager.GetComponentsByInterface(iid); } -const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid) +SComponentDataGenerator CSimulation2::GetComponentsByInterfaceUnordered(int iid) { - return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid); + return m->m_ComponentManager.GetComponentsByInterfaceUnordered(iid); } const CSimContext& CSimulation2::GetSimContext() const Index: source/simulation2/components/CCmpTerritoryManager.cpp =================================================================== --- source/simulation2/components/CCmpTerritoryManager.cpp +++ source/simulation2/components/CCmpTerritoryManager.cpp @@ -434,7 +434,7 @@ count = 0; // Find all territory influence entities - CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence); + CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetComponentsByInterface(IID_TerritoryInfluence); // Split influence entities into per-player lists, ignoring any with invalid properties std::map > influenceEntities; Index: source/simulation2/helpers/Selection.h =================================================================== --- source/simulation2/helpers/Selection.h +++ source/simulation2/helpers/Selection.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -92,16 +92,12 @@ std::swap(sy0, sy1); std::vector hitEnts; + hitEnts.reserve(100); - Filter filter; - const CSimulation2::InterfaceListUnordered& entities = simulation.GetEntitiesWithInterfaceUnordered(cid); - for (const std::pair& item : entities) - { - if (!filter(item.second)) - continue; - if (CheckEntityInRect(item.second->GetEntityHandle(), camera, sx0, sy0, sx1, sy1, false)) - hitEnts.push_back(item.first); - } + SComponentDataGenerator gen = simulation.GetComponentsByInterfaceUnordered(cid); + while (IComponent* cmp = gen.Next()) + if (CheckEntityInRect(cmp->GetEntityHandle(), camera, sx0, sy0, sx1, sy1, false)) + hitEnts.push_back(cmp->GetEntityId()); return hitEnts; } Index: source/simulation2/helpers/Selection.cpp =================================================================== --- source/simulation2/helpers/Selection.cpp +++ source/simulation2/helpers/Selection.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -158,11 +158,11 @@ } else // owner == INVALID_PLAYER; Used when selecting units in Atlas or other mods that allow all kinds of selectables to be selected. { - const CSimulation2::InterfaceListUnordered& selectableEnts = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable); - for (CSimulation2::InterfaceListUnordered::const_iterator it = selectableEnts.begin(); it != selectableEnts.end(); ++it) + SComponentDataGenerator gen = simulation.GetComponentsByInterfaceUnordered(IID_Selectable); + while (IComponent* cmp = gen.Next()) { - if (CheckEntityVisibleAndInRect(it->second->GetEntityHandle(), cmpRangeManager, camera, sx0, sy0, sx1, sy1, owner, allowEditorSelectables)) - hitEnts.push_back(it->first); + if (CheckEntityVisibleAndInRect(cmp->GetEntityHandle(), cmpRangeManager, camera, sx0, sy0, sx1, sy1, owner, allowEditorSelectables)) + hitEnts.push_back(cmp->GetEntityId()); } } @@ -202,16 +202,16 @@ std::vector hitEnts; - const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable); - for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) + SComponentDataGenerator gen = simulation.GetComponentsByInterfaceUnordered(IID_Selectable); + while (IComponent* cmp = gen.Next()) { - entity_id_t ent = it->first; - CEntityHandle handle = it->second->GetEntityHandle(); - // Check if this entity is only selectable in Atlas - if (static_cast(it->second)->IsEditorOnly() && !allowEditorSelectables) + if (static_cast(cmp)->IsEditorOnly() && !allowEditorSelectables) continue; + CEntityHandle handle = cmp->GetEntityHandle(); + entity_id_t ent = handle.GetId(); + if (matchRank) { // Exact template name matching, optionally also allowing foundations Index: source/simulation2/scripting/ScriptComponent.h =================================================================== --- source/simulation2/scripting/ScriptComponent.h +++ source/simulation2/scripting/ScriptComponent.h @@ -82,20 +82,20 @@ #define REGISTER_COMPONENT_SCRIPT_WRAPPER(cname) \ void RegisterComponentType_##cname(CComponentManager& mgr) \ { \ - IComponent::RegisterComponentTypeScriptWrapper(mgr, CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, #cname, CCmp##cname::GetSchema()); \ + IComponent::RegisterComponentTypeScriptWrapper(mgr, CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, sizeof(CCmp##cname), alignof(CCmp##cname), #cname, CCmp##cname::GetSchema()); \ CCmp##cname::ClassInit(mgr); \ } #define DEFAULT_SCRIPT_WRAPPER(cname) \ static void ClassInit(CComponentManager& UNUSED(componentManager)) { } \ - static IComponent* Allocate(const ScriptInterface& scriptInterface, JS::HandleValue instance) \ + static IComponent* Allocate(void* slot, const ScriptInterface& scriptInterface, JS::HandleValue instance) \ { \ - return new CCmp##cname(scriptInterface, instance); \ + return new (slot) CCmp##cname(scriptInterface, instance); \ } \ static void Deallocate(IComponent* cmp) \ { \ - delete static_cast (cmp); \ + static_cast(cmp)->~CCmp##cname(); \ } \ CCmp##cname(const ScriptInterface& scriptInterface, JS::HandleValue instance) : m_Script(scriptInterface, instance) { } \ static std::string GetSchema() \ Index: source/simulation2/system/Component.h =================================================================== --- source/simulation2/system/Component.h +++ source/simulation2/system/Component.h @@ -32,13 +32,15 @@ #define REGISTER_COMPONENT_TYPE(cname) \ void RegisterComponentType_##cname(CComponentManager& mgr) \ { \ - IComponent::RegisterComponentType(mgr, CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, #cname, CCmp##cname::GetSchema()); \ + IComponent::RegisterComponentType(mgr, CCmp##cname::GetInterfaceId(), CID_##cname, CCmp##cname::Allocate, CCmp##cname::Deallocate, sizeof(CCmp##cname), alignof(CCmp##cname), #cname, CCmp##cname::GetSchema()); \ CCmp##cname::ClassInit(mgr); \ } #define DEFAULT_COMPONENT_ALLOCATOR(cname) \ - static IComponent* Allocate(const ScriptInterface&, JS::HandleValue) { return new CCmp##cname(); } \ - static void Deallocate(IComponent* cmp) { delete static_cast (cmp); } \ + static IComponent* Allocate(void* slot, const ScriptInterface&, JS::HandleValue) { return new (slot) CCmp##cname(); } \ + static void Deallocate(IComponent* cmp) { \ + static_cast(cmp)->~CCmp##cname(); /* deallocation is handled by ComponentDataHolder */\ + } \ int GetComponentTypeId() const override \ { \ return CID_##cname; \ Index: source/simulation2/system/ComponentDataHolder.h =================================================================== --- /dev/null +++ source/simulation2/system/ComponentDataHolder.h @@ -0,0 +1,66 @@ +/* Copyright (C) 2022 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_COMPONENTDATAHOLDER +#define INCLUDED_COMPONENTDATAHOLDER +#include "simulation2/system/IComponent.h" + +#include +#include + + +// this is a pool allocator for a type with constant size +class CComponentDataHolder final +{ +private: + std::byte* m_Back; + std::byte* m_End; + size_t m_SlotSize; + size_t m_PoolSize; + std::vector m_OpenSlots; + std::vector m_Pools; + void MakePool(); + CComponentDataHolder(const CComponentDataHolder&) = delete; + CComponentDataHolder & operator=(const CComponentDataHolder&) = delete; +public: + CComponentDataHolder(size_t size, size_t align, uint32_t capacity = 10000); + ~CComponentDataHolder(); + void ResetState(); + std::byte* Allocate(); + void Deallocate(IComponent* ptr); + std::vector Pools() const; + size_t SlotSize() const; + size_t PoolSize() const; + std::byte* Back() const; +}; + +struct SComponentDataGenerator final +{ +private: + std::byte* m_Ptr; + const CComponentDataHolder* m_Data; + size_t m_PoolIndex; + std::byte* m_Back; + size_t m_LastIndex; + void SetPoolIndex(size_t index); +public: + explicit SComponentDataGenerator(const CComponentDataHolder* data); + IComponent* Next(); + void Reset(); +}; + +#endif // INCLUDED_COMPONENTDATAHOLDER Index: source/simulation2/system/ComponentDataHolder.cpp =================================================================== --- /dev/null +++ source/simulation2/system/ComponentDataHolder.cpp @@ -0,0 +1,117 @@ +/* Copyright (C) 2022 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 "ComponentDataHolder.h" + +#include +#include + +size_t AlignRequirement(size_t size, size_t align) +{ + // https://stackoverflow.com/questions/61647725/custom-allocator-and-memory-align + return size % align == 0 ? size : size + (align - size % align); +} + +/** + * @class CComponentDataHolder + * @brief This is a pool allocator for a type with constant size. It can grow as needed but performs best with a high 'chunkCapacity' + */ +CComponentDataHolder::CComponentDataHolder(size_t size, size_t align, uint32_t chunkCapacity) +{ + m_SlotSize = AlignRequirement(size, align); + m_OpenSlots.reserve(chunkCapacity / 2); + m_PoolSize = chunkCapacity * m_SlotSize; + MakePool(); +} +CComponentDataHolder::~CComponentDataHolder() +{ + std::for_each(m_Pools.begin(), m_Pools.end(), free); +} +void CComponentDataHolder::ResetState() +{ + std::for_each(m_Pools.begin() + 1, m_Pools.end(), free); + m_Pools.resize(1); + m_Back = m_Pools.front(); + m_OpenSlots.clear(); +} +std::byte* CComponentDataHolder::Allocate() +{ + std::byte* output; + if (m_OpenSlots.empty()) + { + if (m_Back == m_End) + MakePool(); + output = m_Back; + m_Back += m_SlotSize; + } + else + { + output = m_OpenSlots.back(); + m_OpenSlots.pop_back(); + } + return output; +} +void CComponentDataHolder::Deallocate(IComponent* ptr) +{ + ptr->SetInvalid(); + m_OpenSlots.push_back(reinterpret_cast(ptr)); +} +void CComponentDataHolder::MakePool() +{ + m_Back = static_cast(malloc(m_PoolSize)); + m_Pools.push_back(m_Back); + m_End = m_Back + m_PoolSize; +} +std::vector CComponentDataHolder::Pools() const { return m_Pools; } +size_t CComponentDataHolder::SlotSize() const { return m_SlotSize; } +size_t CComponentDataHolder::PoolSize() const { return m_PoolSize; } +std::byte* CComponentDataHolder::Back() const { return m_Back; } + +// This is a generator to iterate over the allocator pool, it performs best under high strain due to density. +SComponentDataGenerator::SComponentDataGenerator(const CComponentDataHolder* data) +{ + m_Data = data; + m_LastIndex = m_Data->Pools().size() - 1; + Reset(); +} +void SComponentDataGenerator::SetPoolIndex(size_t index) +{ + m_PoolIndex = index; + m_Ptr = m_Data->Pools()[index]; + if (index == m_LastIndex) + m_Back = m_Data->Back(); + else + m_Back = m_Ptr + m_Data->PoolSize(); +} +void SComponentDataGenerator::Reset() { SetPoolIndex(0); } +IComponent* SComponentDataGenerator::Next() +{ + IComponent* output; + while (m_Ptr < m_Back) + { + output = reinterpret_cast(m_Ptr); + m_Ptr += m_Data->SlotSize(); + if (output->IsValid()) + return output; + } + if (m_PoolIndex == m_LastIndex) + return nullptr; + SetPoolIndex(m_PoolIndex + 1); + return Next(); +} Index: source/simulation2/system/ComponentManager.h =================================================================== --- source/simulation2/system/ComponentManager.h +++ source/simulation2/system/ComponentManager.h @@ -24,6 +24,7 @@ #include "simulation2/system/Components.h" #include "simulation2/system/Entity.h" #include "simulation2/system/IComponent.h" +#include "simulation2/system/ComponentDataHolder.h" #include #include @@ -48,7 +49,7 @@ private: using AllocFunc = IComponent::AllocFunc; - using DeallocFunc = IComponent::DeallocFunc; + using DestructFunc = IComponent::DestructFunc; // ComponentTypes come in three types: // Native: normal C++ component @@ -67,11 +68,14 @@ EComponentTypeType type; InterfaceId iid; AllocFunc alloc; - DeallocFunc dealloc; + DestructFunc destruct; + size_t size; + size_t align; std::string name; std::string schema; // RelaxNG fragment std::unique_ptr ctor; // only valid if type == CT_Script }; + std::unordered_map m_ComponentData; public: CComponentManager(CSimContext&, std::shared_ptr cx, bool skipScriptFunctions = false); @@ -89,8 +93,8 @@ void RegisterMessageType(MessageTypeId mtid, const char* name); - void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); - void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); + void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DestructFunc, size_t size, size_t align, const char*, const std::string& schema); + void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DestructFunc, size_t size, size_t align, const char*, const std::string& schema); void MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid); @@ -232,8 +236,8 @@ using InterfaceList = std::vector; using InterfaceListUnordered = std::unordered_map; - InterfaceList GetEntitiesWithInterface(InterfaceId iid) const; - const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(InterfaceId iid) const; + InterfaceList GetComponentsByInterface(InterfaceId iid) const; + SComponentDataGenerator GetComponentsByInterfaceUnordered(InterfaceId iid) const; /** * Send a message, targeted at a particular entity. The message will be received by any @@ -242,6 +246,12 @@ */ 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 responding to a givent interface id. + */ + void BroadcastMessageToInterface(const CMessage& msg, InterfaceId iid, bool global) const; + /** * 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. @@ -310,6 +320,7 @@ std::map m_ComponentTypesById; std::vector m_ScriptedSystemComponents; std::vector > m_ComponentsByInterface; // indexed by InterfaceId + std::vector m_ComponentDataByInterface; // indexed by InterfaceId std::map > m_ComponentsByTypeId; std::map > m_LocalMessageSubscriptions; std::map > m_GlobalMessageSubscriptions; Index: source/simulation2/system/ComponentManager.cpp =================================================================== --- source/simulation2/system/ComponentManager.cpp +++ source/simulation2/system/ComponentManager.cpp @@ -31,6 +31,9 @@ #include "simulation2/system/IComponent.h" #include "simulation2/system/ParamNode.h" #include "simulation2/system/SimContext.h" +#include "simulation2/system/ComponentDataHolder.h" + +#include /** * Used for script-only message types. @@ -109,7 +112,7 @@ m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); m_ComponentsByInterface.resize(IID__LastNative); - + m_ComponentDataByInterface.resize(IID__LastNative); ResetState(); } @@ -255,13 +258,16 @@ CT_Script, iid, ctWrapper.alloc, - ctWrapper.dealloc, + ctWrapper.destruct, + ctWrapper.size, + ctWrapper.align, cname, schema, std::make_unique(rq.cx, ctor) }; m_ComponentTypesById[cid] = std::move(ct); - + m_ComponentData.try_emplace(cid, ct.size, ct.align); + m_ComponentDataByInterface[iid] = &m_ComponentData.at(cid); m_CurrentComponent = cid; // needed by Subscribe // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: @@ -355,6 +361,7 @@ size_t id = m_InterfaceIdsByName.size() + 1; m_InterfaceIdsByName[name] = (InterfaceId)id; m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId + m_ComponentDataByInterface.resize(id+1); m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); } @@ -404,19 +411,19 @@ std::vector CComponentManager::Script_GetEntitiesWithInterface(int iid) { std::vector ret; - const InterfaceListUnordered& ents = GetEntitiesWithInterfaceUnordered(iid); - for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) - if (!ENTITY_IS_LOCAL(it->first)) - ret.push_back(it->first); - std::sort(ret.begin(), ret.end()); + const InterfaceList& comps = GetComponentsByInterface(iid); + ret.reserve(comps.size()); + for (std::pair it : GetComponentsByInterface(iid)) + if (!ENTITY_IS_LOCAL(it.second->GetEntityId())) + ret.push_back(it.second->GetEntityId()); return ret; } std::vector CComponentManager::Script_GetComponentsWithInterface(int iid) { std::vector ret; - InterfaceList ents = GetEntitiesWithInterface(iid); - for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) + const InterfaceList& comps = GetComponentsByInterface(iid); + for (InterfaceList::const_iterator it = comps.begin(); it != comps.end(); ++it) ret.push_back(it->second); // TODO: maybe we should exclude local entities return ret; } @@ -486,7 +493,7 @@ for (; eit != iit->second.end(); ++eit) { eit->second->Deinit(); - m_ComponentTypesById[iit->first].dealloc(eit->second); + m_ComponentTypesById[iit->first].destruct(eit->second); } } @@ -496,6 +503,9 @@ m_ComponentsByTypeId.clear(); + for(auto& kv : m_ComponentData) + kv.second.ResetState(); + // Delete all SEntityComponentCaches std::unordered_map::iterator ccit = m_ComponentCaches.begin(); for (; ccit != m_ComponentCaches.end(); ++ccit) @@ -515,20 +525,24 @@ m_RNG.seed(seed); } -void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, - const char* name, const std::string& schema) +void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DestructFunc destruct, + size_t size, size_t align, const char* name, const std::string& schema) { - ComponentType c{ CT_Native, iid, alloc, dealloc, name, schema, std::unique_ptr() }; + ComponentType c{ CT_Native, iid, alloc, destruct, size, align, name, schema, std::unique_ptr() }; m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; + m_ComponentData.try_emplace(cid, size, align); + m_ComponentDataByInterface[iid] = &m_ComponentData.at(cid); } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, - DeallocFunc dealloc, const char* name, const std::string& schema) + DestructFunc destruct, size_t size, size_t align, const char* name, const std::string& schema) { - ComponentType c{ CT_ScriptWrapper, iid, alloc, dealloc, name, schema, std::unique_ptr() }; + ComponentType c{ CT_ScriptWrapper, iid, alloc, destruct, size, align, name, schema, std::unique_ptr() }; m_ComponentTypesById.insert(std::make_pair(cid, std::move(c))); m_ComponentTypeIdsByName[name] = cid; + m_ComponentData.try_emplace(cid, size, align); + m_ComponentDataByInterface[iid] = &m_ComponentData.at(cid); // TODO: merge with RegisterComponentType } @@ -748,7 +762,8 @@ // Construct the new component // NB: The unit motion manager relies on components not moving in memory once constructed. - IComponent* component = ct.alloc(m_ScriptInterface, obj); + std::byte* address = m_ComponentData.at(cid).Allocate(); + IComponent* component = ct.alloc(address, m_ScriptInterface, obj); ENSURE(component); component->SetEntityHandle(ent); @@ -916,7 +931,8 @@ { eit->second->Deinit(); RemoveComponentDynamicSubscriptions(eit->second); - m_ComponentTypesById[iit->first].dealloc(eit->second); + m_ComponentTypesById[iit->first].destruct(eit->second); + m_ComponentData.at(iit->first).Deallocate(eit->second); iit->second.erase(ent); handle.GetComponentCache()->interfaces[m_ComponentTypesById[iit->first].iid] = NULL; } @@ -953,37 +969,28 @@ return eit->second; } -CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const +CComponentManager::InterfaceList CComponentManager::GetComponentsByInterface(InterfaceId iid) const { - std::vector > ret; - if ((size_t)iid >= m_ComponentsByInterface.size()) - { - // Invalid iid - return ret; - } + ASSERT ((size_t)iid >= m_ComponentsByInterface.size()); + + std::vector > ret; ret.reserve(m_ComponentsByInterface[iid].size()); - std::unordered_map::const_iterator it = m_ComponentsByInterface[iid].begin(); - for (; it != m_ComponentsByInterface[iid].end(); ++it) - ret.push_back(*it); + SComponentDataGenerator gen = GetComponentsByInterfaceUnordered(iid); + while (IComponent* cmp = gen.Next()) + ret.push_back(std::pair(cmp->GetEntityId(), cmp)); std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID return ret; } -static CComponentManager::InterfaceListUnordered g_EmptyEntityMap; -const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesWithInterfaceUnordered(InterfaceId iid) const +SComponentDataGenerator CComponentManager::GetComponentsByInterfaceUnordered(InterfaceId iid) const { - if ((size_t)iid >= m_ComponentsByInterface.size()) - { - // Invalid iid - return g_EmptyEntityMap; - } - - return m_ComponentsByInterface[iid]; + ASSERT(static_cast(iid) <= m_ComponentsByInterface.size()); + return SComponentDataGenerator(m_ComponentDataByInterface[iid]); } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) @@ -1013,6 +1020,13 @@ SendGlobalMessage(ent, msg); } +void CComponentManager::BroadcastMessageToInterface(const CMessage& msg, InterfaceId iid, bool global) const +{ + SComponentDataGenerator gen = SComponentDataGenerator(&m_ComponentData.at(iid)); + while (IComponent* cmp = gen.Next()) + cmp->HandleMessage(msg, global); +} + void CComponentManager::BroadcastMessage(const CMessage& msg) { // Send the message to components of all entities that subscribed locally to this message @@ -1022,19 +1036,8 @@ { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) - { - // Find the component instances of this type (if any) - std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); - if (emap == m_ComponentsByTypeId.end()) - continue; - - // Send the message to all of them - std::map::const_iterator eit = emap->second.begin(); - for (; eit != emap->second.end(); ++eit) - eit->second->HandleMessage(msg, false); - } + BroadcastMessageToInterface(msg, *ctit, false); } - SendGlobalMessage(INVALID_ENTITY, msg); } @@ -1062,15 +1065,7 @@ continue; } - // Find the component instances of this type (if any) - std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); - if (emap == m_ComponentsByTypeId.end()) - continue; - - // Send the message to all of them - std::map::const_iterator eit = emap->second.begin(); - for (; eit != emap->second.end(); ++eit) - eit->second->HandleMessage(msg, true); + BroadcastMessageToInterface(msg, *ctit, true); } } Index: source/simulation2/system/Entity.h =================================================================== --- source/simulation2/system/Entity.h +++ source/simulation2/system/Entity.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -87,6 +87,7 @@ entity_id_t GetId() const { return m_Id; } SEntityComponentCache* GetComponentCache() const { return m_ComponentCache; } + void SetInvalid() { m_Id = INVALID_ENTITY; } private: entity_id_t m_Id; Index: source/simulation2/system/IComponent.h =================================================================== --- source/simulation2/system/IComponent.h +++ source/simulation2/system/IComponent.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -33,15 +33,15 @@ { public: // Component allocation types - using AllocFunc = IComponent* (*)(const ScriptInterface& scriptInterface, JS::HandleValue ctor); - using DeallocFunc = void (*)(IComponent*); + using AllocFunc = IComponent* (*)(void* slot, const ScriptInterface& scriptInterface, JS::HandleValue ctor); + using DestructFunc = void (*)(IComponent*); virtual ~IComponent(); static std::string GetSchema(); - static void RegisterComponentType(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema); - static void RegisterComponentTypeScriptWrapper(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema); + static void RegisterComponentType(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DestructFunc destruct, size_t size, size_t align, const char* name, const std::string& schema); + static void RegisterComponentTypeScriptWrapper(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DestructFunc destruct, size_t size, size_t align, const char* name, const std::string& schema); virtual void Init(const CParamNode& paramNode) = 0; virtual void Deinit() = 0; @@ -69,6 +69,8 @@ virtual bool NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const; virtual JS::Value GetJSInstance() const; virtual int GetComponentTypeId() const = 0; + void SetInvalid() { m_EntityHandle.SetInvalid(); } + bool IsValid() const { return GetEntityId() != INVALID_ENTITY; } private: CEntityHandle m_EntityHandle; Index: source/simulation2/system/IComponent.cpp =================================================================== --- source/simulation2/system/IComponent.cpp +++ source/simulation2/system/IComponent.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -33,14 +33,14 @@ return ""; } -void IComponent::RegisterComponentType(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) +void IComponent::RegisterComponentType(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DestructFunc destruct, size_t size, size_t align, const char* name, const std::string& schema) { - mgr.RegisterComponentType(iid, cid, alloc, dealloc, name, schema); + mgr.RegisterComponentType(iid, cid, alloc, destruct, size, align, name, schema); } -void IComponent::RegisterComponentTypeScriptWrapper(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) +void IComponent::RegisterComponentTypeScriptWrapper(CComponentManager& mgr, EInterfaceId iid, EComponentTypeId cid, AllocFunc alloc, DestructFunc destruct, size_t size, size_t align, const char* name, const std::string& schema) { - mgr.RegisterComponentTypeScriptWrapper(iid, cid, alloc, dealloc, name, schema); + mgr.RegisterComponentTypeScriptWrapper(iid, cid, alloc, destruct, size, align, name, schema); } void IComponent::HandleMessage(const CMessage& UNUSED(msg), bool UNUSED(global)) Index: source/simulation2/tests/test_ComponentDataHolder.h =================================================================== --- /dev/null +++ source/simulation2/tests/test_ComponentDataHolder.h @@ -0,0 +1,78 @@ +/* Copyright (C) 2022 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 "lib/self_test.h" + +#include "simulation2/system/IComponent.h" +#include "simulation2/system/ComponentDataHolder.h" + +#include "ps/CLogger.h" + +#include +#include + + +class TestComponentDataHolder : public CxxTest::TestSuite +{ +public: + void test_SetUp() + { + CComponentDataHolder cdh = CComponentDataHolder(32, 8); + TS_ASSERT_EQUALS(cdh.Pools()[0], cdh.Back()); + } + + void test_Allocate() + { + CComponentDataHolder cdh = CComponentDataHolder(32, 8); + std::byte* ptr = cdh.Allocate(); + TS_ASSERT_EQUALS(ptr, cdh.Pools()[0]); + TS_ASSERT_EQUALS(cdh.Pools()[0] + cdh.SlotSize(), cdh.Back()); + } + + void test_Deallocate() + { + CComponentDataHolder cdh = CComponentDataHolder(32, 8); + std::byte* ptr = cdh.Allocate(); + cdh.Deallocate(reinterpret_cast(ptr)); + std::byte* ptr2 = cdh.Allocate(); + // Verify slot is reused. + TS_ASSERT_EQUALS(ptr, ptr2); + } + + void test_AllocateMultiple() + { + std::byte* ptrs[10]; + CComponentDataHolder cdh = CComponentDataHolder(32, 8); + for (uint32_t i = 0; i < 10; ++i) + ptrs[i] = cdh.Allocate(); + TS_ASSERT_EQUALS(cdh.Pools()[0] + (cdh.SlotSize() * 10), cdh.Back()); + cdh.Deallocate(reinterpret_cast(ptrs[9])); + ptrs[9] = cdh.Allocate(); + cdh.Deallocate(reinterpret_cast(ptrs[8])); + ptrs[8] = cdh.Allocate(); + TS_ASSERT_EQUALS(cdh.Pools()[0] + (cdh.SlotSize() * 10), cdh.Back()); + TS_ASSERT(ptrs[8] != ptrs[9]); + } + + void test_PoolOverflow() + { + CComponentDataHolder cdh = CComponentDataHolder(32, 8, 32); + for (uint32_t i = 0; i < 33; ++i) + cdh.Allocate(); + TS_ASSERT_EQUALS(cdh.Pools().size(), 2); + } +}; Index: source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -518,10 +518,10 @@ const int offsetZ = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; const CFixedVector3D offset = CFixedVector3D(fixed::FromInt(offsetX), fixed::FromInt(0), fixed::FromInt(offsetZ)); - const CSimulation2::InterfaceListUnordered& ents = sim.GetEntitiesWithInterfaceUnordered(IID_Selectable); - for (const std::pair& ent : ents) + SComponentDataGenerator gen = sim.GetComponentsByInterfaceUnordered(IID_Selectable); + while (IComponent* cmp = gen.Next()) { - const entity_id_t entityId = ent.first; + const entity_id_t entityId = cmp->GetEntityId(); CmpPtr cmpPosition(sim, entityId); if (cmpPosition && cmpPosition->IsInWorld() && Within(cmpPosition->GetPosition(), mapCenterX, mapCenterZ, radiusInTerrainUnits)) Index: source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -1093,16 +1093,12 @@ std::vector ids; player_id_t playerID = msg->player; - const CSimulation2::InterfaceListUnordered& cmps = g_Game->GetSimulation2()->GetEntitiesWithInterfaceUnordered(IID_Ownership); - for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit) - { - if (static_cast(eit->second)->GetOwner() == playerID) - { - ids.push_back(eit->first); - } - } + SComponentDataGenerator gen = g_Game->GetSimulation2()->GetComponentsByInterfaceUnordered(IID_Ownership); + while (IComponent* cmp = gen.Next()) + if (static_cast(cmp)->GetOwner() == playerID) + ids.push_back(cmp->GetEntityId()); - msg->ids = ids; + msg->ids = std::move(ids); } MESSAGEHANDLER(SetBandbox)