forked from 0ad/0ad
As per ticket #1707. Some minor improvements to spatial subdivision. Makes way for future changes in RangeManager.
Right now merely improves memory usage. This was SVN commit r13854.
This commit is contained in:
parent
6644f224cd
commit
9694eec8b8
101
source/lib/ps_stl.h
Normal file
101
source/lib/ps_stl.h
Normal file
@ -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<class Container, class T>
|
||||
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<class Container, class T>
|
||||
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<class Container, class T>
|
||||
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
|
@ -314,10 +314,7 @@ class CMessageRangeUpdate : public CMessage
|
||||
public:
|
||||
DEFAULT_MESSAGE_IMPL(RangeUpdate)
|
||||
|
||||
CMessageRangeUpdate(u32 tag, const std::vector<entity_id_t>& added, const std::vector<entity_id_t>& removed) :
|
||||
tag(tag), added(added), removed(removed)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
u32 tag;
|
||||
std::vector<entity_id_t> 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<entity_id_t>& added, const std::vector<entity_id_t>& 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;
|
||||
|
@ -124,8 +124,8 @@ public:
|
||||
bool m_DebugOverlayDirty;
|
||||
std::vector<SOverlayLine> m_DebugOverlayLines;
|
||||
|
||||
SpatialSubdivision<u32> m_UnitSubdivision;
|
||||
SpatialSubdivision<u32> 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<u32, UnitShape> 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<typename S>
|
||||
void SerializeCommon(S& serialize)
|
||||
{
|
||||
SerializeSpatialSubdivision<SerializeU32_Unbounded>()(serialize, "unit subdiv", m_UnitSubdivision);
|
||||
SerializeSpatialSubdivision<SerializeU32_Unbounded>()(serialize, "static subdiv", m_StaticSubdivision);
|
||||
SerializeSpatialSubdivision()(serialize, "unit subdiv", m_UnitSubdivision);
|
||||
SerializeSpatialSubdivision()(serialize, "static subdiv", m_StaticSubdivision);
|
||||
|
||||
SerializeMap<SerializeU32_Unbounded, SerializeUnitShape>()(serialize, "unit shapes", m_UnitShapes);
|
||||
SerializeMap<SerializeU32_Unbounded, SerializeStaticShape>()(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<u32> 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<u32, UnitShape>::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<u32> 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<u32, StaticShape>::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<u32> 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<u32, UnitShape>::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<u32> 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<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]);
|
||||
ENSURE(it != m_StaticShapes.end());
|
||||
|
@ -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<entity_id_t, EntityData>& entities, const CFixedVector2D& source) :
|
||||
EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source) :
|
||||
m_EntityData(entities), m_Source(source)
|
||||
{
|
||||
}
|
||||
@ -224,7 +227,7 @@ struct EntityDistanceOrdering
|
||||
return (vecA.CompareLength(vecB) < 0);
|
||||
}
|
||||
|
||||
const std::map<entity_id_t, EntityData>& m_EntityData;
|
||||
const EntityMap<EntityData>& m_EntityData;
|
||||
CFixedVector2D m_Source;
|
||||
|
||||
private:
|
||||
@ -271,8 +274,9 @@ public:
|
||||
// Range query state:
|
||||
tag_t m_QueryNext; // next allocated id
|
||||
std::map<tag_t, Query> m_Queries;
|
||||
std::map<entity_id_t, EntityData> m_EntityData;
|
||||
SpatialSubdivision<entity_id_t> m_Subdivision; // spatial index of m_EntityData
|
||||
EntityMap<EntityData> 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<SerializeU32_Unbounded, SerializeQuery>()(serialize, "queries", m_Queries, GetSimContext());
|
||||
SerializeMap<SerializeU32_Unbounded, SerializeEntityData>()(serialize, "entity data", m_EntityData);
|
||||
SerializeEntityMap<SerializeEntityData>()(serialize, "entity data", m_EntityData);
|
||||
|
||||
SerializeMap<SerializeI32_Unbounded, SerializeBool>()(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<const CMessagePositionChanged&> (msg);
|
||||
entity_id_t ent = msgData.entity;
|
||||
|
||||
std::map<entity_id_t, EntityData>::iterator it = m_EntityData.find(ent);
|
||||
EntityMap<EntityData>::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<const CMessageOwnershipChanged&> (msg);
|
||||
entity_id_t ent = msgData.entity;
|
||||
|
||||
std::map<entity_id_t, EntityData>::iterator it = m_EntityData.find(ent);
|
||||
EntityMap<EntityData>::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<const CMessageDestroy&> (msg);
|
||||
entity_id_t ent = msgData.entity;
|
||||
|
||||
std::map<entity_id_t, EntityData>::iterator it = m_EntityData.find(ent);
|
||||
EntityMap<EntityData>::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<const CMessageVisionRangeChanged&> (msg);
|
||||
entity_id_t ent = msgData.entity;
|
||||
|
||||
std::map<entity_id_t, EntityData>::iterator it = m_EntityData.find(ent);
|
||||
EntityMap<EntityData>::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<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
|
||||
std::vector<u32> oldStateRevealed = m_LosStateRevealed;
|
||||
SpatialSubdivision<entity_id_t> 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<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
||||
for (EntityMap<EntityData>::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<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
||||
for (EntityMap<EntityData>::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<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
||||
for (EntityMap<EntityData>::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<std::pair<entity_id_t, CMessageRangeUpdate> > messages;
|
||||
std::vector<entity_id_t> results;
|
||||
std::vector<entity_id_t> added;
|
||||
std::vector<entity_id_t> removed;
|
||||
|
||||
for (std::map<tag_t, Query>::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<ICmpPosition> cmpSourcePosition(q.source);
|
||||
CmpPtr<ICmpPosition> cmpSourcePosition(query.source);
|
||||
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
|
||||
continue;
|
||||
|
||||
std::vector<entity_id_t> 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<entity_id_t> added;
|
||||
std::vector<entity_id_t> 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<entity_id_t, CMessageRangeUpdate>& 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<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
|
||||
for (EntityMap<EntityData>::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<entity_id_t> 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<entity_id_t, EntityData>::const_iterator it = m_EntityData.find(ents[i]);
|
||||
EntityMap<EntityData>::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<entity_id_t> 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<entity_id_t, EntityData>::const_iterator it = m_EntityData.find(ents[i]);
|
||||
EntityMap<EntityData>::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<entity_id_t, EntityData>::iterator it = m_EntityData.find(ent);
|
||||
EntityMap<EntityData>::iterator it = m_EntityData.find(ent);
|
||||
|
||||
// We don't have this entity
|
||||
if (it == m_EntityData.end())
|
||||
|
@ -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<typename T>
|
||||
class SpatialSubdivision
|
||||
{
|
||||
public:
|
||||
SpatialSubdivision() :
|
||||
m_DivisionsW(0), m_DivisionsH(0)
|
||||
struct SubDivisionGrid
|
||||
{
|
||||
std::vector<uint32_t> 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<T> div1 = m_Divisions.at(i + j*m_DivisionsW);
|
||||
std::vector<T> 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<uint32_t> a = m_Divisions[i].items;
|
||||
std::vector<uint32_t> 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<T>& 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<T>& 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<T> GetInRange(CFixedVector2D posMin, CFixedVector2D posMax)
|
||||
void GetInRange(SpatialQueryArray& out, CFixedVector2D posMin, CFixedVector2D posMax)
|
||||
{
|
||||
std::vector<T> 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<T>& 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<T> 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<std::vector<T> > m_Divisions;
|
||||
u32 m_DivisionsW;
|
||||
u32 m_DivisionsH;
|
||||
|
||||
template<typename ELEM> friend struct SerializeSpatialSubdivision;
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialization helper template for SpatialSubdivision
|
||||
*/
|
||||
template<typename ELEM>
|
||||
struct SerializeSpatialSubdivision
|
||||
{
|
||||
template<typename T>
|
||||
void operator()(ISerializer& serialize, const char* UNUSED(name), SpatialSubdivision<T>& value)
|
||||
void operator()(ISerializer& serialize, const char* UNUSED(name), SpatialSubdivision& value)
|
||||
{
|
||||
serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize);
|
||||
SerializeVector<SerializeVector<ELEM> >()(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<SerializeU32_Unbounded>()(serialize, "subdiv items", value.m_Divisions[i].items);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void operator()(IDeserializer& serialize, const char* UNUSED(name), SpatialSubdivision<T>& value)
|
||||
void operator()(IDeserializer& serialize, const char* UNUSED(name), SpatialSubdivision& value)
|
||||
{
|
||||
serialize.NumberFixed_Unbounded("div size", value.m_DivisionSize);
|
||||
SerializeVector<SerializeVector<ELEM> >()(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<SerializeU32_Unbounded>()(serialize, "subdiv items", value.m_Divisions[i].items);
|
||||
}
|
||||
};
|
||||
|
||||
|
269
source/simulation2/system/EntityMap.h
Normal file
269
source/simulation2/system/EntityMap.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef INCLUDED_ENTITYMAP
|
||||
#define INCLUDED_ENTITYMAP
|
||||
|
||||
#include "Entity.h"
|
||||
|
||||
/**
|
||||
* A fast replacement for map<entity_id_t, T>.
|
||||
* 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 T> 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<class K, class V> struct key_val {
|
||||
typedef K first_type;
|
||||
typedef V second_type;
|
||||
K first;
|
||||
V second;
|
||||
};
|
||||
typedef key_val<entity_id_t, T> 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<class U> struct _iter : public std::iterator<std::forward_iterator_tag, U>
|
||||
{
|
||||
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<U const>() const { return _iter<U const>(val); }
|
||||
};
|
||||
|
||||
typedef _iter<value_type> iterator;
|
||||
typedef _iter<value_type const> 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<class VSerializer>
|
||||
struct SerializeEntityMap
|
||||
{
|
||||
template<class V>
|
||||
void operator()(ISerializer& serialize, const char* UNUSED(name), EntityMap<V>& value)
|
||||
{
|
||||
size_t len = value.size();
|
||||
serialize.NumberU32_Unbounded("length", (u32)len);
|
||||
for (typename EntityMap<V>::iterator it = value.begin(); it != value.end(); ++it)
|
||||
{
|
||||
serialize.NumberI32_Unbounded("key", it->first);
|
||||
VSerializer()(serialize, "value", it->second);
|
||||
}
|
||||
}
|
||||
|
||||
template<class V>
|
||||
void operator()(IDeserializer& deserialize, const char* UNUSED(name), EntityMap<V>& 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
|
@ -34,7 +34,6 @@ CSimContext::~CSimContext()
|
||||
|
||||
CComponentManager& CSimContext::GetComponentManager() const
|
||||
{
|
||||
ENSURE(m_ComponentManager);
|
||||
return *m_ComponentManager;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user