1
0
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:
Jorma Rebane 2013-09-15 14:03:53 +00:00
parent 6644f224cd
commit 9694eec8b8
7 changed files with 647 additions and 149 deletions

101
source/lib/ps_stl.h Normal file
View 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

View File

@ -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;

View File

@ -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());

View File

@ -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())

View File

@ -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);
}
};

View 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

View File

@ -34,7 +34,6 @@ CSimContext::~CSimContext()
CComponentManager& CSimContext::GetComponentManager() const
{
ENSURE(m_ComponentManager);
return *m_ComponentManager;
}