diff --git a/source/lib/ps_stl.h b/source/lib/ps_stl.h new file mode 100644 index 0000000000..94162d56b1 --- /dev/null +++ b/source/lib/ps_stl.h @@ -0,0 +1,101 @@ +/* Copyright (c) 2013 Wildfire Games + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef INCLUDED_PS_STL +#define INCLUDED_PS_STL + +/** + * @author Jorma Rebane + * @note Pyrogenesis STL methods + * @note This file contains useful and optimized methods for use with STL + */ + +namespace ps +{ + /** + * Removes the first occurrence of the specified value from the container. + * @param container The STL-compatible container to remove from. + * @param value The value to remove. + */ +template + inline void remove_first_occurrence(Container& container, const T& value) + { + if (int count = container.size()) + { + T* data = &container[0]; + for (int i = 0; i < count; ++i) + { + if (data[i] == value) + { + container.erase(container.begin() + i); + return; + } + } + } + } + + /** + * @param container The STL-compatible container to search in. + * @param value The value to search for. + * @return TRUE if [value] exists in [container]. + */ +template + inline bool exists_in(const Container& container, const T& value) + { + if (int count = container.size()) + { + for (const T* data = &container[0]; count; ++data, --count) + { + if (*data == value) + { + return true; + } + } + } + return false; + } + + /** + * Finds a value in a container + * @param container The STL-compatible container to search in. + * @param value The value to search for. + * @return Pointer to the value if found, NULL if not found. + */ +template + inline T* find_in(const Container& container, const T& value) + { + if (int count = container.size()) + { + for (const T* data = &container[0]; count; ++data, --count) + { + if (*data == value) + { + return (T*)data; + } + } + } + return NULL; + } + + +} // namespace ps + +#endif // INCLUDED_PS_STL \ No newline at end of file diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h index de8a31b566..701284f981 100644 --- a/source/simulation2/MessageTypes.h +++ b/source/simulation2/MessageTypes.h @@ -314,10 +314,7 @@ class CMessageRangeUpdate : public CMessage public: DEFAULT_MESSAGE_IMPL(RangeUpdate) - CMessageRangeUpdate(u32 tag, const std::vector& added, const std::vector& removed) : - tag(tag), added(added), removed(removed) - { - } + u32 tag; std::vector added; @@ -327,16 +324,21 @@ public: // swap vectors instead of copying (to save on memory allocations), // so add some constructors for it: - CMessageRangeUpdate(u32 tag) : - tag(tag) + // don't init tag in empty ctor + CMessageRangeUpdate() { } - - CMessageRangeUpdate(const CMessageRangeUpdate& other) : - CMessage(), tag(other.tag), added(other.added), removed(other.removed) + CMessageRangeUpdate(u32 tag) : tag(tag) + { + } + CMessageRangeUpdate(u32 tag, const std::vector& added, const std::vector& removed) + : tag(tag), added(added), removed(removed) + { + } + CMessageRangeUpdate(const CMessageRangeUpdate& other) + : CMessage(), tag(other.tag), added(other.added), removed(other.removed) { } - CMessageRangeUpdate& operator=(const CMessageRangeUpdate& other) { tag = other.tag; diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index 2f5eed0167..fe843e89fb 100644 --- a/source/simulation2/components/CCmpObstructionManager.cpp +++ b/source/simulation2/components/CCmpObstructionManager.cpp @@ -124,8 +124,8 @@ public: bool m_DebugOverlayDirty; std::vector m_DebugOverlayLines; - SpatialSubdivision m_UnitSubdivision; - SpatialSubdivision m_StaticSubdivision; + SpatialSubdivision m_UnitSubdivision; + SpatialSubdivision m_StaticSubdivision; // TODO: using std::map is a bit inefficient; is there a better way to store these? std::map m_UnitShapes; @@ -161,7 +161,7 @@ public: // Initialise with bogus values (these will get replaced when // SetBounds is called) - ResetSubdivisions(entity_pos_t::FromInt(1), entity_pos_t::FromInt(1)); + ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024)); } virtual void Deinit() @@ -171,8 +171,8 @@ public: template void SerializeCommon(S& serialize) { - SerializeSpatialSubdivision()(serialize, "unit subdiv", m_UnitSubdivision); - SerializeSpatialSubdivision()(serialize, "static subdiv", m_StaticSubdivision); + SerializeSpatialSubdivision()(serialize, "unit subdiv", m_UnitSubdivision); + SerializeSpatialSubdivision()(serialize, "static subdiv", m_StaticSubdivision); SerializeMap()(serialize, "unit shapes", m_UnitShapes); SerializeMap()(serialize, "static shapes", m_StaticShapes); @@ -544,8 +544,9 @@ bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti CFixedVector2D posMin (std::min(x0, x1) - r, std::min(z0, z1) - r); CFixedVector2D posMax (std::max(x0, x1) + r, std::max(z0, z1) + r); - std::vector unitShapes = m_UnitSubdivision.GetInRange(posMin, posMax); - for (size_t i = 0; i < unitShapes.size(); ++i) + SpatialQueryArray unitShapes; + m_UnitSubdivision.GetInRange(unitShapes, posMin, posMax); + for (int i = 0; i < unitShapes.size(); ++i) { std::map::iterator it = m_UnitShapes.find(unitShapes[i]); ENSURE(it != m_UnitShapes.end()); @@ -559,8 +560,9 @@ bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti return true; } - std::vector staticShapes = m_StaticSubdivision.GetInRange(posMin, posMax); - for (size_t i = 0; i < staticShapes.size(); ++i) + SpatialQueryArray staticShapes; + m_StaticSubdivision.GetInRange(staticShapes, posMin, posMax); + for (int i = 0; i < staticShapes.size(); ++i) { std::map::iterator it = m_StaticShapes.find(staticShapes[i]); ENSURE(it != m_StaticShapes.end()); @@ -880,8 +882,9 @@ void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter ENSURE(x0 <= x1 && z0 <= z1); - std::vector unitShapes = m_UnitSubdivision.GetInRange(CFixedVector2D(x0, z0), CFixedVector2D(x1, z1)); - for (size_t i = 0; i < unitShapes.size(); ++i) + SpatialQueryArray unitShapes; + m_UnitSubdivision.GetInRange(unitShapes, CFixedVector2D(x0, z0), CFixedVector2D(x1, z1)); + for (int i = 0; i < unitShapes.size(); ++i) { std::map::iterator it = m_UnitShapes.find(unitShapes[i]); ENSURE(it != m_UnitShapes.end()); @@ -901,8 +904,9 @@ void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter squares.push_back(s); } - std::vector staticShapes = m_StaticSubdivision.GetInRange(CFixedVector2D(x0, z0), CFixedVector2D(x1, z1)); - for (size_t i = 0; i < staticShapes.size(); ++i) + SpatialQueryArray staticShapes; + m_StaticSubdivision.GetInRange(staticShapes, CFixedVector2D(x0, z0), CFixedVector2D(x1, z1)); + for (int i = 0; i < staticShapes.size(); ++i) { std::map::iterator it = m_StaticShapes.find(staticShapes[i]); ENSURE(it != m_StaticShapes.end()); diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp index 64f16bf46b..5752315c0a 100644 --- a/source/simulation2/components/CCmpRangeManager.cpp +++ b/source/simulation2/components/CCmpRangeManager.cpp @@ -21,6 +21,7 @@ #include "ICmpRangeManager.h" #include "ICmpTerrain.h" +#include "simulation2/system/EntityMap.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerritoryManager.h" @@ -37,6 +38,8 @@ #include "ps/Overlay.h" #include "ps/Profile.h" #include "renderer/Scene.h" +#include "lib/ps_stl.h" + #define DEBUG_RANGE_MANAGER_BOUNDS 0 @@ -61,7 +64,7 @@ struct Query * Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players) * into a 32-bit mask for quick set-membership tests. */ -static u32 CalcOwnerMask(player_id_t owner) +static inline u32 CalcOwnerMask(player_id_t owner) { if (owner >= -1 && owner < 31) return 1 << (1+owner); @@ -72,7 +75,7 @@ static u32 CalcOwnerMask(player_id_t owner) /** * Returns LOS mask for given player. */ -static u32 CalcPlayerLosMask(player_id_t player) +static inline u32 CalcPlayerLosMask(player_id_t player) { if (player > 0 && player <= 16) return ICmpRangeManager::LOS_MASK << (2*(player-1)); @@ -210,7 +213,7 @@ struct SerializeEntityData */ struct EntityDistanceOrdering { - EntityDistanceOrdering(const std::map& entities, const CFixedVector2D& source) : + EntityDistanceOrdering(const EntityMap& entities, const CFixedVector2D& source) : m_EntityData(entities), m_Source(source) { } @@ -224,7 +227,7 @@ struct EntityDistanceOrdering return (vecA.CompareLength(vecB) < 0); } - const std::map& m_EntityData; + const EntityMap& m_EntityData; CFixedVector2D m_Source; private: @@ -271,8 +274,9 @@ public: // Range query state: tag_t m_QueryNext; // next allocated id std::map m_Queries; - std::map m_EntityData; - SpatialSubdivision m_Subdivision; // spatial index of m_EntityData + EntityMap m_EntityData; + + SpatialSubdivision m_Subdivision; // spatial index of m_EntityData // LOS state: @@ -319,7 +323,7 @@ public: // Initialise with bogus values (these will get replaced when // SetBounds is called) - ResetSubdivisions(entity_pos_t::FromInt(1), entity_pos_t::FromInt(1)); + ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024)); // The whole map should be visible to Gaia by default, else e.g. animals // will get confused when trying to run from enemies @@ -349,7 +353,7 @@ public: serialize.NumberU32_Unbounded("query next", m_QueryNext); SerializeMap()(serialize, "queries", m_Queries, GetSimContext()); - SerializeMap()(serialize, "entity data", m_EntityData); + SerializeEntityMap()(serialize, "entity data", m_EntityData); SerializeMap()(serialize, "los reveal all", m_LosRevealAll); serialize.Bool("los circular", m_LosCircular); @@ -411,8 +415,7 @@ public: } // Remember this entity - m_EntityData.insert(std::make_pair(ent, entdata)); - + m_EntityData.insert(ent, entdata); break; } case MT_PositionChanged: @@ -420,7 +423,7 @@ public: const CMessagePositionChanged& msgData = static_cast (msg); entity_id_t ent = msgData.entity; - std::map::iterator it = m_EntityData.find(ent); + EntityMap::iterator it = m_EntityData.find(ent); // Ignore if we're not already tracking this entity if (it == m_EntityData.end()) @@ -467,7 +470,7 @@ public: const CMessageOwnershipChanged& msgData = static_cast (msg); entity_id_t ent = msgData.entity; - std::map::iterator it = m_EntityData.find(ent); + EntityMap::iterator it = m_EntityData.find(ent); // Ignore if we're not already tracking this entity if (it == m_EntityData.end()) @@ -490,7 +493,7 @@ public: const CMessageDestroy& msgData = static_cast (msg); entity_id_t ent = msgData.entity; - std::map::iterator it = m_EntityData.find(ent); + EntityMap::iterator it = m_EntityData.find(ent); // Ignore if we're not already tracking this entity if (it == m_EntityData.end()) @@ -512,7 +515,7 @@ public: const CMessageVisionRangeChanged& msgData = static_cast (msg); entity_id_t ent = msgData.entity; - std::map::iterator it = m_EntityData.find(ent); + EntityMap::iterator it = m_EntityData.find(ent); // Ignore if we're not already tracking this entity if (it == m_EntityData.end()) @@ -576,7 +579,7 @@ public: std::vector > oldPlayerCounts = m_LosPlayerCounts; std::vector oldStateRevealed = m_LosStateRevealed; - SpatialSubdivision oldSubdivision = m_Subdivision; + SpatialSubdivision oldSubdivision = m_Subdivision; ResetDerivedData(true); @@ -637,7 +640,7 @@ public: m_LosStateRevealed.clear(); m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide); - for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) + for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) { if (it->second.inWorld) LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z)); @@ -663,7 +666,7 @@ public: // (TODO: find the optimal number instead of blindly guessing) m_Subdivision.Reset(x1, z1, entity_pos_t::FromInt(8*TERRAIN_TILE_SIZE)); - for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) + for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) { if (it->second.inWorld) m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z)); @@ -794,7 +797,7 @@ public: u32 ownerMask = CalcOwnerMask(player); - for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) + for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) { // Check owner and add to list if it matches if (CalcOwnerMask(it->second.owner) & ownerMask) @@ -822,46 +825,54 @@ public: // Store a queue of all messages before sending any, so we can assume // no entities will move until we've finished checking all the ranges std::vector > messages; + std::vector results; + std::vector added; + std::vector removed; for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it) { - Query& q = it->second; + Query& query = it->second; - if (!q.enabled) + if (!query.enabled) continue; - CmpPtr cmpSourcePosition(q.source); + CmpPtr cmpSourcePosition(query.source); if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld()) continue; - std::vector r; - r.reserve(q.lastMatch.size()); - - PerformQuery(q, r); + results.clear(); + results.reserve(query.lastMatch.size()); + PerformQuery(query, results); + if (results.empty()) + continue; // Compute the changes vs the last match - std::vector added; - std::vector removed; - std::set_difference(r.begin(), r.end(), q.lastMatch.begin(), q.lastMatch.end(), std::back_inserter(added)); - std::set_difference(q.lastMatch.begin(), q.lastMatch.end(), r.begin(), r.end(), std::back_inserter(removed)); - - if (added.empty() && removed.empty()) - continue; + added.clear(); + removed.clear(); // Return the 'added' list sorted by distance from the entity // (Don't bother sorting 'removed' because they might not even have positions or exist any more) - CFixedVector2D pos = cmpSourcePosition->GetPosition2D(); - std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, pos)); + std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(), + std::back_inserter(added)); + std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(), + std::back_inserter(removed)); + if (added.empty() && removed.empty()) + continue; - messages.push_back(std::make_pair(q.source.GetId(), CMessageRangeUpdate(it->first))); - messages.back().second.added.swap(added); - messages.back().second.removed.swap(removed); + std::sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D())); - it->second.lastMatch.swap(r); + messages.resize(messages.size() + 1); + std::pair& back = messages.back(); + back.first = query.source.GetId(); + back.second.tag = it->first; + back.second.added.swap(added); + back.second.removed.swap(removed); + it->second.lastMatch.swap(results); } + CComponentManager& cmpMgr = GetSimContext().GetComponentManager(); for (size_t i = 0; i < messages.size(); ++i) - GetSimContext().GetComponentManager().PostMessage(messages[i].first, messages[i].second); + cmpMgr.PostMessage(messages[i].first, messages[i].second); } /** @@ -905,7 +916,7 @@ public: // Special case: range -1.0 means check all entities ignoring distance if (q.maxRange == entity_pos_t::FromInt(-1)) { - for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) + for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) { if (!TestEntityQuery(q, it->first, it->second)) continue; @@ -920,11 +931,12 @@ public: CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+ CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ; // Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange - std::vector ents = m_Subdivision.GetNear(pos, q.maxRange*2); + SpatialQueryArray ents; + m_Subdivision.GetNear(ents, pos, q.maxRange*2); - for (size_t i = 0; i < ents.size(); ++i) + for (int i = 0; i < ents.size(); ++i) { - std::map::const_iterator it = m_EntityData.find(ents[i]); + EntityMap::const_iterator it = m_EntityData.find(ents[i]); ENSURE(it != m_EntityData.end()); if (!TestEntityQuery(q, it->first, it->second)) @@ -950,18 +962,18 @@ public: } r.push_back(it->first); - } } // check a regular range (i.e. not the entire world, and not parabolic) else { // Get a quick list of entities that are potentially in range - std::vector ents = m_Subdivision.GetNear(pos, q.maxRange); - - for (size_t i = 0; i < ents.size(); ++i) + SpatialQueryArray ents; + m_Subdivision.GetNear(ents, pos, q.maxRange); + + for (int i = 0; i < ents.size(); ++i) { - std::map::const_iterator it = m_EntityData.find(ents[i]); + EntityMap::const_iterator it = m_EntityData.find(ents[i]); ENSURE(it != m_EntityData.end()); if (!TestEntityQuery(q, it->first, it->second)) @@ -980,7 +992,6 @@ public: } r.push_back(it->first); - } } } @@ -1135,9 +1146,10 @@ public: { if (!m_DebugOverlayEnabled) return; - CColor enabledRingColour(0, 1, 0, 1); - CColor disabledRingColour(1, 0, 0, 1); - CColor rayColour(1, 1, 0, 0.2f); + static CColor disabledRingColour(1, 0, 0, 1); // red + static CColor enabledRingColour(0, 1, 0, 1); // green + static CColor subdivColour(0, 0, 1, 1); // blue + static CColor rayColour(1, 1, 0, 0.2f); if (m_DebugOverlayDirty) { @@ -1244,6 +1256,24 @@ public: } } + // render subdivision grid + float divSize = m_Subdivision.GetDivisionSize().ToFloat(); + int width = m_Subdivision.GetWidth(); + int height = m_Subdivision.GetHeight(); + for (int x = 0; x < width; ++x) + { + for (int y = 0; y < height; ++y) + { + m_DebugOverlayLines.push_back(SOverlayLine()); + m_DebugOverlayLines.back().m_Color = subdivColour; + + float xpos = x*divSize + divSize/2; + float zpos = y*divSize + divSize/2; + SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f, + m_DebugOverlayLines.back(), false, 1.0f); + } + } + m_DebugOverlayDirty = false; } @@ -1264,7 +1294,7 @@ public: virtual void SetEntityFlag(entity_id_t ent, std::string identifier, bool value) { - std::map::iterator it = m_EntityData.find(ent); + EntityMap::iterator it = m_EntityData.find(ent); // We don't have this entity if (it == m_EntityData.end()) diff --git a/source/simulation2/helpers/Spatial.h b/source/simulation2/helpers/Spatial.h index 8ac264d004..7ff57c8466 100644 --- a/source/simulation2/helpers/Spatial.h +++ b/source/simulation2/helpers/Spatial.h @@ -20,6 +20,31 @@ #include "simulation2/serialization/SerializeTemplates.h" +/** + * A simple fixed-size array that works similar to an std::vector + * but is obviously limited in its max items + */ +struct SpatialQueryArray +{ + enum { MAX_COUNT = 1024 }; + int count; + uint32_t items[MAX_COUNT]; + + inline SpatialQueryArray() : count(0) {} + inline const uint32_t& operator[](int index) const { return items[index]; } + inline uint32_t& operator[](int index) { return items[index]; } + inline int size() const { return count; } + inline void clear() { count = 0; } + void make_unique() // removes any duplicate entries + { + if (count) + { + std::sort(items, items + count); // we need a sorted list for std::unique to work + count = int(std::unique(items, items + count) - items); + } + } +}; + /** * A very basic subdivision scheme for finding items in ranges. * Items are stored in lists in fixed-size divisions. @@ -34,13 +59,95 @@ * * (TODO: maybe an adaptive quadtree would be better than fixed sizes?) */ -template class SpatialSubdivision { -public: - SpatialSubdivision() : - m_DivisionsW(0), m_DivisionsH(0) + struct SubDivisionGrid { + std::vector items; + + inline void push_back(uint32_t value) + { + items.push_back(value); + } + + inline void erase(int index) + { + // Delete by swapping with the last element then popping + if ((int)items.size() > 1) // but only if we have more than 1 elements + items[index] = items.back(); + items.pop_back(); + } + + void copy_items(SpatialQueryArray& out) + { + if (items.empty()) + return; // nothing to copy + + int dsti = out.count; // the index in [out] where to start copying + int count = (int)items.size(); + if ((dsti + count) > SpatialQueryArray::MAX_COUNT) + count = SpatialQueryArray::MAX_COUNT - dsti; // silently fail to copy overflowing items + + uint32_t* dst = &out.items[dsti]; + uint32_t* src = &items[0]; + for (int i = 0; i < count; ++i) // copy all items + dst[i] = src[i]; + out.count += count; + } + }; + + + entity_pos_t m_DivisionSize; + SubDivisionGrid* m_Divisions; + uint32_t m_DivisionsW; + uint32_t m_DivisionsH; + + friend struct SerializeSubDivisionGrid; + friend struct SerializeSpatialSubdivision; + +public: + SpatialSubdivision() : m_Divisions(NULL), m_DivisionsW(0), m_DivisionsH(0) + { + } + ~SpatialSubdivision() + { + delete[] m_Divisions; + } + SpatialSubdivision(const SpatialSubdivision& rhs) + { + m_DivisionSize = rhs.m_DivisionSize; + m_DivisionsW = rhs.m_DivisionsW; + m_DivisionsH = rhs.m_DivisionsH; + size_t n = m_DivisionsW * m_DivisionsH; + m_Divisions = new SubDivisionGrid[n]; + for (size_t i = 0; i < n; ++i) + m_Divisions[i] = rhs.m_Divisions[i]; // just fall back to piecemeal copy + } + SpatialSubdivision& operator=(const SpatialSubdivision& rhs) + { + if (this != &rhs) + { + m_DivisionSize = rhs.m_DivisionSize; + size_t n = rhs.m_DivisionsW * rhs.m_DivisionsH; + if (m_DivisionsW != rhs.m_DivisionsW || m_DivisionsH != rhs.m_DivisionsH) + Create(n); // size changed, recreate + + m_DivisionsW = rhs.m_DivisionsW; + m_DivisionsH = rhs.m_DivisionsH; + for (size_t i = 0; i < n; ++i) + m_Divisions[i] = rhs.m_Divisions[i]; // just fall back to piecemeal copy + } + return *this; + } + + inline entity_pos_t GetDivisionSize() const { return m_DivisionSize; } + inline uint32_t GetWidth() const { return m_DivisionsW; } + inline uint32_t GetHeight() const { return m_DivisionsH; } + + void Create(size_t count) + { + if (m_Divisions) delete[] m_Divisions; + m_Divisions = new SubDivisionGrid[count]; } /** @@ -51,23 +158,24 @@ public: if (m_DivisionSize != rhs.m_DivisionSize || m_DivisionsW != rhs.m_DivisionsW || m_DivisionsH != rhs.m_DivisionsH) return false; - for (u32 j = 0; j < m_DivisionsH; ++j) + uint32_t n = m_DivisionsH * m_DivisionsW; + for (uint32_t i = 0; i < n; ++i) { - for (u32 i = 0; i < m_DivisionsW; ++i) - { - std::vector div1 = m_Divisions.at(i + j*m_DivisionsW); - std::vector div2 = rhs.m_Divisions.at(i + j*m_DivisionsW); - std::sort(div1.begin(), div1.end()); - std::sort(div2.begin(), div2.end()); - if (div1 != div2) - return false; - } - } + if (m_Divisions[i].items.size() != rhs.m_Divisions[i].items.size()) + return false; + // don't bother optimizing this, this is only used in the TESTING SUITE + std::vector a = m_Divisions[i].items; + std::vector b = rhs.m_Divisions[i].items; + std::sort(a.begin(), a.end()); + std::sort(b.begin(), b.end()); + if (a != b) + return false; + } return true; } - bool operator!=(const SpatialSubdivision& rhs) + inline bool operator!=(const SpatialSubdivision& rhs) { return !(*this == rhs); } @@ -77,15 +185,16 @@ public: m_DivisionSize = divisionSize; m_DivisionsW = (maxX / m_DivisionSize).ToInt_RoundToInfinity(); m_DivisionsH = (maxZ / m_DivisionSize).ToInt_RoundToInfinity(); - m_Divisions.clear(); - m_Divisions.resize(m_DivisionsW * m_DivisionsH); + + Create(m_DivisionsW * m_DivisionsH); } + /** * Add an item with the given 'to' size. * The item must not already be present. */ - void Add(T item, CFixedVector2D toMin, CFixedVector2D toMax) + void Add(uint32_t item, CFixedVector2D toMin, CFixedVector2D toMax) { ENSURE(toMin.X <= toMax.X && toMin.Y <= toMax.Y); @@ -97,8 +206,7 @@ public: { for (u32 i = i0; i <= i1; ++i) { - std::vector& div = m_Divisions.at(i + j*m_DivisionsW); - div.push_back(item); + m_Divisions[i + j*m_DivisionsW].push_back(item); } } } @@ -108,7 +216,7 @@ public: * The item should already be present. * The size must match the size that was last used when adding the item. */ - void Remove(T item, CFixedVector2D fromMin, CFixedVector2D fromMax) + void Remove(uint32_t item, CFixedVector2D fromMin, CFixedVector2D fromMax) { ENSURE(fromMin.X <= fromMax.X && fromMin.Y <= fromMax.Y); @@ -120,15 +228,13 @@ public: { for (u32 i = i0; i <= i1; ++i) { - std::vector& div = m_Divisions.at(i + j*m_DivisionsW); - - for (u32 n = 0; n < div.size(); ++n) + SubDivisionGrid& div = m_Divisions[i + j*m_DivisionsW]; + int size = div.items.size(); + for (int n = 0; n < size; ++n) { - if (div[n] == item) + if (div.items[n] == item) { - // Delete by swapping with the last element then popping - div[n] = div.back(); - div.pop_back(); + div.erase(n); break; } } @@ -139,7 +245,7 @@ public: /** * Equivalent to Remove() then Add(), but potentially faster. */ - void Move(T item, CFixedVector2D fromMin, CFixedVector2D fromMax, CFixedVector2D toMin, CFixedVector2D toMax) + void Move(uint32_t item, CFixedVector2D fromMin, CFixedVector2D fromMax, CFixedVector2D toMin, CFixedVector2D toMax) { // Skip the work if we're staying in the same divisions if (GetIndex0(fromMin) == GetIndex0(toMin) && GetIndex1(fromMax) == GetIndex1(toMax)) @@ -153,7 +259,7 @@ public: * Convenience function for Add() of individual points. * (Note that points on a boundary may occupy multiple divisions.) */ - void Add(T item, CFixedVector2D to) + void Add(uint32_t item, CFixedVector2D to) { Add(item, to, to); } @@ -161,7 +267,7 @@ public: /** * Convenience function for Remove() of individual points. */ - void Remove(T item, CFixedVector2D from) + void Remove(uint32_t item, CFixedVector2D from) { Remove(item, from, from); } @@ -169,7 +275,7 @@ public: /** * Convenience function for Move() of individual points. */ - void Move(T item, CFixedVector2D from, CFixedVector2D to) + void Move(uint32_t item, CFixedVector2D from, CFixedVector2D to) { Move(item, from, from, to, to); } @@ -178,10 +284,9 @@ public: * Returns a sorted list of unique items that includes all items * within the given axis-aligned square range. */ - std::vector GetInRange(CFixedVector2D posMin, CFixedVector2D posMax) + void GetInRange(SpatialQueryArray& out, CFixedVector2D posMin, CFixedVector2D posMax) { - std::vector ret; - + out.clear(); ENSURE(posMin.X <= posMax.X && posMin.Y <= posMax.Y); u32 i0 = GetI0(posMin.X); @@ -192,28 +297,23 @@ public: { for (u32 i = i0; i <= i1; ++i) { - std::vector& div = m_Divisions.at(i + j*m_DivisionsW); - ret.insert(ret.end(), div.begin(), div.end()); + m_Divisions[i + j*m_DivisionsW].copy_items(out); } } - - // Remove duplicates - std::sort(ret.begin(), ret.end()); - ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); - - return ret; + // some buildings span several tiles, so we need to make it unique + out.make_unique(); } /** * Returns a sorted list of unique items that includes all items * within the given circular distance of the given point. */ - std::vector GetNear(CFixedVector2D pos, entity_pos_t range) + void GetNear(SpatialQueryArray& out, CFixedVector2D pos, entity_pos_t range) { // TODO: be cleverer and return a circular pattern of divisions, // not this square over-approximation - - return GetInRange(pos - CFixedVector2D(range, range), pos + CFixedVector2D(range, range)); + CFixedVector2D r(range, range); + GetInRange(out, pos - r, pos + r); } private: @@ -221,70 +321,63 @@ private: // (avoiding out-of-bounds accesses, and rounding correctly so that // points precisely between divisions are counted in both): - u32 GetI0(entity_pos_t x) + uint32_t GetI0(entity_pos_t x) { return Clamp((x / m_DivisionSize).ToInt_RoundToInfinity()-1, 0, (int)m_DivisionsW-1); } - u32 GetJ0(entity_pos_t z) + uint32_t GetJ0(entity_pos_t z) { return Clamp((z / m_DivisionSize).ToInt_RoundToInfinity()-1, 0, (int)m_DivisionsH-1); } - u32 GetI1(entity_pos_t x) + uint32_t GetI1(entity_pos_t x) { return Clamp((x / m_DivisionSize).ToInt_RoundToNegInfinity(), 0, (int)m_DivisionsW-1); } - u32 GetJ1(entity_pos_t z) + uint32_t GetJ1(entity_pos_t z) { return Clamp((z / m_DivisionSize).ToInt_RoundToNegInfinity(), 0, (int)m_DivisionsH-1); } - u32 GetIndex0(CFixedVector2D pos) + uint32_t GetIndex0(CFixedVector2D pos) { return GetI0(pos.X) + GetJ0(pos.Y)*m_DivisionsW; } - u32 GetIndex1(CFixedVector2D pos) + uint32_t GetIndex1(CFixedVector2D pos) { return GetI1(pos.X) + GetJ1(pos.Y)*m_DivisionsW; } - - entity_pos_t m_DivisionSize; - std::vector > m_Divisions; - u32 m_DivisionsW; - u32 m_DivisionsH; - - template friend struct SerializeSpatialSubdivision; }; /** * Serialization helper template for SpatialSubdivision */ -template struct SerializeSpatialSubdivision { - template - void operator()(ISerializer& serialize, const char* UNUSED(name), SpatialSubdivision& value) + void operator()(ISerializer& serialize, const char* UNUSED(name), SpatialSubdivision& value) { serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize); - SerializeVector >()(serialize, "divs", value.m_Divisions); serialize.NumberU32_Unbounded("divs w", value.m_DivisionsW); serialize.NumberU32_Unbounded("divs h", value.m_DivisionsH); + + size_t count = value.m_DivisionsH * value.m_DivisionsW; + for (size_t i = 0; i < count; ++i) + SerializeVector()(serialize, "subdiv items", value.m_Divisions[i].items); } - template - void operator()(IDeserializer& serialize, const char* UNUSED(name), SpatialSubdivision& value) + void operator()(IDeserializer& serialize, const char* UNUSED(name), SpatialSubdivision& value) { serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize); - SerializeVector >()(serialize, "divs", value.m_Divisions); + serialize.NumberU32_Unbounded("divs w", value.m_DivisionsW); + serialize.NumberU32_Unbounded("divs h", value.m_DivisionsH); - u32 w, h; - serialize.NumberU32_Unbounded("divs w", w); - serialize.NumberU32_Unbounded("divs h", h); - value.m_DivisionsW = w; - value.m_DivisionsH = h; + size_t count = value.m_DivisionsW * value.m_DivisionsH; + value.Create(count); + for (size_t i = 0; i < count; ++i) + SerializeVector()(serialize, "subdiv items", value.m_Divisions[i].items); } }; diff --git a/source/simulation2/system/EntityMap.h b/source/simulation2/system/EntityMap.h new file mode 100644 index 0000000000..6438098b3d --- /dev/null +++ b/source/simulation2/system/EntityMap.h @@ -0,0 +1,269 @@ +/* Copyright (C) 2013 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_ENTITYMAP +#define INCLUDED_ENTITYMAP + +#include "Entity.h" + +/** + * A fast replacement for map. + * We make the following assumptions: + * - entity id's (keys) are unique and are inserted in increasing order + * - an entity id that was removed is never added again + * - modifications (add / delete) are far less frequent then look-ups + * - preformance for iteration is important + */ +template class EntityMap +{ +private: + EntityMap(const EntityMap&); // non-copyable + EntityMap& operator=(const EntityMap&); // non-copyable + +public: + typedef entity_id_t key_type; + typedef T mapped_type; + template struct key_val { + typedef K first_type; + typedef V second_type; + K first; + V second; + }; + typedef key_val value_type; + +private: + size_t m_BufferSize; // number of elements in the buffer + size_t m_BufferCapacity; // capacity of the buffer + value_type* m_Buffer; // vector with all the mapped key-value pairs + + size_t m_Count; // number of 'valid' entity id's + +public: + + inline EntityMap() : m_BufferSize(1), m_BufferCapacity(4096), m_Count(0) + { + // for entitymap we allocate the buffer right away + // with first element in buffer being the Invalid Entity + m_Buffer = (value_type*)malloc(sizeof(value_type) * (m_BufferCapacity + 1)); + + // create the first element: + m_Buffer[0].first = INVALID_ENTITY; + m_Buffer[1].first = 0xFFFFFFFF; // ensure end() always has 0xFFFFFFFF + } + inline ~EntityMap() + { + free(m_Buffer); + } + + // Iterators + template struct _iter : public std::iterator + { + U* val; + inline _iter(U* init) : val(init) {} + inline U& operator*() { return *val; } + inline U* operator->() { return val; } + inline _iter& operator++() // ++it + { + ++val; + while (val->first == INVALID_ENTITY) ++val; // skip any invalid entities + return *this; + } + inline _iter& operator++(int) // it++ + { + U* ptr = val; + ++val; + while (val->first == INVALID_ENTITY) ++val; // skip any invalid entities + return ptr; + } + inline bool operator==(_iter other) { return val == other.val; } + inline bool operator!=(_iter other) { return val != other.val; } + inline operator _iter() const { return _iter(val); } + }; + + typedef _iter iterator; + typedef _iter const_iterator; + + inline iterator begin() + { + value_type* ptr = m_Buffer + 1; // skip the first INVALID_ENTITY + while (ptr->first == INVALID_ENTITY) ++ptr; // skip any other invalid entities + return ptr; + } + inline iterator end() + { + return iterator(m_Buffer + m_BufferSize); + } + inline const_iterator begin() const + { + value_type* ptr = m_Buffer + 1; // skip the first INVALID_ENTITY + while (ptr->first == INVALID_ENTITY) ++ptr; // skip any other invalid entities + return ptr; + } + inline const_iterator end() const + { + return const_iterator(m_Buffer + m_BufferSize); + } + + // Size + inline bool empty() const { return m_Count == 0; } + inline size_t size() const { return m_Count; } + + // Modification + void insert(const key_type key, const mapped_type& value) + { + if (key >= m_BufferCapacity) // do we need to resize buffer? + { + do { m_BufferCapacity += 4096; } while (key >= m_BufferCapacity); + + // always allocate +1 behind the scenes, because end() must have a 0xFFFFFFFF key + m_Buffer = (value_type*)realloc(m_Buffer, sizeof(value_type) * (m_BufferCapacity + 1)); + + goto fill_gaps; + } + else if (key > m_BufferSize) // weird insert far beyond the end + { +fill_gaps: + // set all entity id's to INVALID_ENTITY inside the new range + for (size_t i = m_BufferSize; i <= key; ++i) + m_Buffer[i].first = INVALID_ENTITY; + m_BufferSize = key; // extend the new size + } + + value_type& item = m_Buffer[key]; + item.first = key; + if (key == m_BufferSize) // push_back + { + ++m_BufferSize; // expand + ++m_Count; + new (&item.second) mapped_type(value); // copy ctor to init + m_Buffer[m_BufferSize].first = 0xFFFFFFFF; // ensure end() always has 0xFFFFFFFF + } + else if(!item.first) // insert new to middle + { + ++m_Count; + new (&item.second) mapped_type(value); // copy ctor to init + } + else // set existing value + { + item.second = value; // overwrite existing + } + } + + void erase(iterator it) + { + value_type* ptr = it.val; + if (ptr->first != INVALID_ENTITY) + { + ptr->first = INVALID_ENTITY; + ptr->second.~T(); // call dtor + --m_Count; + } + } + void erase(const entity_id_t key) + { + if (key < m_BufferSize) + { + value_type* ptr = m_Buffer + key; + if (ptr->first != INVALID_ENTITY) + { + ptr->first = INVALID_ENTITY; + ptr->second.~T(); // call dtor + --m_Count; + } + } + } + inline void clear() + { + // orphan whole range + value_type* ptr = m_Buffer; + value_type* end = m_Buffer + m_BufferSize; + for (; ptr != end; ++ptr) + { + if (ptr->first != INVALID_ENTITY) + { + ptr->first = INVALID_ENTITY; + ptr->second.~T(); // call dtor + } + } + m_Count = 0; // no more valid entities + } + + // Operations + inline iterator find(const entity_id_t key) + { + if (key < m_BufferSize) // is this key in the range of existing entitites? + { + value_type* ptr = m_Buffer + key; + if (ptr->first != INVALID_ENTITY) + return ptr; + } + return m_Buffer + m_BufferSize; // return iterator end() + } + inline const_iterator find(const entity_id_t key) const + { + if (key < m_BufferSize) // is this key in the range of existing entitites? + { + const value_type* ptr = m_Buffer + key; + if (ptr->first != INVALID_ENTITY) + return ptr; + } + return m_Buffer + m_BufferSize; // return iterator end() + } + inline size_t count(const entity_id_t key) const + { + if (key < m_BufferSize) + { + if (m_Buffer[key].first != INVALID_ENTITY) + return 1; + } + return 0; + } +}; + +template +struct SerializeEntityMap +{ + template + void operator()(ISerializer& serialize, const char* UNUSED(name), EntityMap& value) + { + size_t len = value.size(); + serialize.NumberU32_Unbounded("length", (u32)len); + for (typename EntityMap::iterator it = value.begin(); it != value.end(); ++it) + { + serialize.NumberI32_Unbounded("key", it->first); + VSerializer()(serialize, "value", it->second); + } + } + + template + void operator()(IDeserializer& deserialize, const char* UNUSED(name), EntityMap& value) + { + value.clear(); + uint32_t len; + deserialize.NumberU32_Unbounded("length", len); + for (size_t i = 0; i < len; ++i) + { + entity_id_t k; + V v; + deserialize.NumberU32_Unbounded("key", k); + VSerializer()(deserialize, "value", v); + value.insert(k, v); + } + } +}; + + +#endif diff --git a/source/simulation2/system/SimContext.cpp b/source/simulation2/system/SimContext.cpp index e5a84b2cc6..33cbb68154 100644 --- a/source/simulation2/system/SimContext.cpp +++ b/source/simulation2/system/SimContext.cpp @@ -34,7 +34,6 @@ CSimContext::~CSimContext() CComponentManager& CSimContext::GetComponentManager() const { - ENSURE(m_ComponentManager); return *m_ComponentManager; }