0ad/source/simulation2/components/CCmpRangeManager.cpp
Ykkrosh 423b31c130 Fix units trying to move outside themselves when ordered to their current location.
Remove redundant comparisons in pathfinder.
Simplify range query result ordering.
Avoid throwing exception on script errors in debug serializer.

This was SVN commit r7828.
2010-07-31 21:22:39 +00:00

530 lines
14 KiB
C++

/* Copyright (C) 2010 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/>.
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpRangeManager.h"
#include "ICmpPosition.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/helpers/Render.h"
#include "graphics/Overlay.h"
#include "maths/FixedVector2D.h"
#include "ps/CLogger.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
/**
* Representation of a range query.
*/
struct Query
{
bool enabled;
entity_id_t source;
entity_pos_t maxRange;
u32 ownersMask;
int interface;
std::vector<entity_id_t> lastMatch;
};
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
* into a 31-bit mask for quick set-membership tests.
*/
static u32 CalcOwnerMask(i32 owner)
{
if (owner >= -1 && owner < 30)
return 1 << (1+owner);
else
return 0; // owner was invalid
}
/**
* Representation of an entity, with the data needed for queries.
*/
struct EntityData
{
EntityData() : ownerMask(CalcOwnerMask(-1)), inWorld(0) { }
entity_pos_t x, z;
u32 ownerMask : 31;
u32 inWorld : 1;
};
cassert(sizeof(EntityData) == 12);
/**
* Functor for sorting entities by distance from a source point.
* It must only be passed entities that are in 'entities'
* and are currently in the world.
*/
struct EntityDistanceOrdering
{
EntityDistanceOrdering(const std::map<entity_id_t, EntityData>& entities, const CFixedVector2D& source) :
m_EntityData(entities), m_Source(source)
{
}
bool operator()(entity_id_t a, entity_id_t b)
{
const EntityData& da = m_EntityData.find(a)->second;
const EntityData& db = m_EntityData.find(b)->second;
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
return (vecA.CompareLength(vecB) < 0);
}
const std::map<entity_id_t, EntityData>& m_EntityData;
CFixedVector2D m_Source;
private:
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
};
/**
* Basic range manager implementation.
* Maintains a list of all entities (and their positions and owners), which is used for
* queries.
*
* TODO: Ideally this would use a quadtree or something for more efficient spatial queries,
* since it's about O(n^2) in the total number of entities on the map.
*/
class CCmpRangeManager : public ICmpRangeManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_Create);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
}
DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
bool m_DebugOverlayEnabled;
bool m_DebugOverlayDirty;
std::vector<SOverlayLine> m_DebugOverlayLines;
tag_t m_QueryNext; // next allocated id
std::map<tag_t, Query> m_Queries;
std::map<entity_id_t, EntityData> m_EntityData;
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
{
m_QueryNext = 1;
m_DebugOverlayEnabled = false;
m_DebugOverlayDirty = true;
}
virtual void Deinit(const CSimContext& UNUSED(context))
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// TODO
}
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(context, paramNode);
}
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Create:
{
const CMessageCreate& msgData = static_cast<const CMessageCreate&> (msg);
entity_id_t ent = msgData.entity;
// Ignore local entities - we shouldn't let them influence anything
if (ENTITY_IS_LOCAL(ent))
break;
// Ignore non-positional entities
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent);
if (cmpPosition.null())
break;
// The newly-created entity will have owner -1 and position out-of-world
// (any initialisation of those values will happen later), so we can just
// use the default-constructed EntityData here
EntityData entdata;
// Remember this entity
m_EntityData.insert(std::make_pair(ent, entdata));
break;
}
case MT_PositionChanged:
{
const CMessagePositionChanged& msgData = static_cast<const CMessagePositionChanged&> (msg);
entity_id_t ent = msgData.entity;
std::map<u32, EntityData>::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (msgData.inWorld)
{
it->second.inWorld = 1;
it->second.x = msgData.x;
it->second.z = msgData.z;
}
else
{
it->second.inWorld = 0;
it->second.x = entity_pos_t::Zero();
it->second.z = entity_pos_t::Zero();
}
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
entity_id_t ent = msgData.entity;
std::map<u32, EntityData>::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
it->second.ownerMask = CalcOwnerMask(msgData.to);
break;
}
case MT_Destroy:
{
const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
entity_id_t ent = msgData.entity;
m_EntityData.erase(ent);
break;
}
case MT_Update:
{
m_DebugOverlayDirty = true;
ExecuteActiveQueries();
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t maxRange,
std::vector<int> owners, int requiredInterface)
{
size_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, maxRange, owners, requiredInterface);
return id;
}
virtual void DestroyActiveQuery(tag_t tag)
{
if (m_Queries.find(tag) == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: DestroyActiveQuery called with invalid tag %d", tag);
return;
}
m_Queries.erase(tag);
}
virtual void EnableActiveQuery(tag_t tag)
{
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: EnableActiveQuery called with invalid tag %d", tag);
return;
}
Query& q = it->second;
q.enabled = true;
}
virtual void DisableActiveQuery(tag_t tag)
{
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: DisableActiveQuery called with invalid tag %d", tag);
return;
}
Query& q = it->second;
q.enabled = false;
}
virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source, entity_pos_t maxRange,
std::vector<int> owners, int requiredInterface)
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, maxRange, owners, requiredInterface);
std::vector<entity_id_t> r;
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
return r;
}
PerformQuery(q, r);
// Return the list sorted by distance from the entity
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag)
{
PROFILE("ResetActiveQuery");
std::vector<entity_id_t> r;
std::map<tag_t, Query>::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: ResetActiveQuery called with invalid tag %d", tag);
return r;
}
Query& q = it->second;
q.enabled = true;
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
q.lastMatch = r;
return r;
}
PerformQuery(q, r);
q.lastMatch = r;
// Return the list sorted by distance from the entity
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;
m_DebugOverlayDirty = true;
if (!enabled)
m_DebugOverlayLines.clear();
}
private:
/**
* Update all currently-enabled active queries.
*/
void ExecuteActiveQueries()
{
PROFILE("ExecuteActiveQueries");
// 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;
for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& q = it->second;
if (!q.enabled)
continue;
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
continue;
std::vector<entity_id_t> r;
r.reserve(q.lastMatch.size());
PerformQuery(q, r);
// 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;
// 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));
messages.push_back(std::make_pair(q.source, CMessageRangeUpdate(it->first)));
messages.back().second.added.swap(added);
messages.back().second.removed.swap(removed);
it->second.lastMatch.swap(r);
}
for (size_t i = 0; i < messages.size(); ++i)
GetSimContext().GetComponentManager().PostMessage(messages[i].first, messages[i].second);
}
/**
* Returns a list of distinct entity IDs that match the given query, sorted by ID.
*/
void PerformQuery(const Query& q, std::vector<entity_id_t>& r)
{
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
return;
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
for (std::map<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
// Quick filter to ignore entities with the wrong owner
if (!(it->second.ownerMask & q.ownersMask))
continue;
// Restrict based on location
// (TODO: this bit ought to use a quadtree or something)
if (!it->second.inWorld)
continue;
int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
if (distVsMax > 0)
continue;
// Ignore self
if (it->first == q.source)
continue;
// Ignore if it's missing the required interface
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(it->first, q.interface))
continue;
r.push_back(it->first);
}
}
Query ConstructQuery(entity_id_t source, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface)
{
Query q;
q.enabled = false;
q.source = source;
q.maxRange = maxRange;
q.ownersMask = 0;
for (size_t i = 0; i < owners.size(); ++i)
q.ownersMask |= CalcOwnerMask(owners[i]);
q.interface = requiredInterface;
return q;
}
void RenderSubmit(SceneCollector& collector)
{
if (!m_DebugOverlayEnabled)
return;
CColor enabledRingColour(0, 1, 0, 1);
CColor disabledRingColour(1, 0, 0, 1);
CColor rayColour(1, 1, 0, 0.2f);
if (m_DebugOverlayDirty)
{
m_DebugOverlayLines.clear();
for (std::map<tag_t, Query>::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& q = it->second;
CmpPtr<ICmpPosition> cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
continue;
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
// Draw the range circle
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToDouble(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
// Draw a ray from the source to each matched entity
for (size_t i = 0; i < q.lastMatch.size(); ++i)
{
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
continue;
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
std::vector<float> coords;
coords.push_back(pos.X.ToFloat());
coords.push_back(pos.Y.ToFloat());
coords.push_back(targetPos.X.ToFloat());
coords.push_back(targetPos.Y.ToFloat());
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = rayColour;
SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
}
}
m_DebugOverlayDirty = false;
}
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
collector.Submit(&m_DebugOverlayLines[i]);
}
};
REGISTER_COMPONENT_TYPE(RangeManager)