Various minor optimisations.

Enable SpiderMonkey method JIT in Release mode.
Add Engine.ProfileStart/Engine.ProfileStop functions for scripts.
Fix AI to clone initial entity data and shared metadata.

This was SVN commit r9003.
This commit is contained in:
Ykkrosh 2011-03-03 00:16:14 +00:00
parent 8d2d4a8505
commit 16a4eb36dd
24 changed files with 384 additions and 98 deletions

View File

@ -8,6 +8,8 @@ function BaseAI(settings)
Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false});
Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false});
this._ownEntities = {};
this._entityMetadata = {};
}
@ -51,9 +53,29 @@ BaseAI.prototype.GetTemplate = function(name)
BaseAI.prototype.HandleMessage = function(state)
{
if (!this._rawEntities)
this._rawEntities = state.entities;
{
// Do a (shallow) clone of all the initial entity properties (in order
// to copy into our own script context and minimise cross-context
// weirdness), and remember the entities owned by our player
this._rawEntities = {};
for (var id in state.entities)
{
var ent = state.entities[id];
this._rawEntities[id] = {};
for (var prop in ent)
this._rawEntities[id][prop] = ent[prop];
if (ent.owner === this._player)
this._ownEntities[id] = this._rawEntities[id];
}
}
else
{
this.ApplyEntitiesDelta(state);
}
Engine.ProfileStart("HandleMessage setup");
this.entities = new EntityCollection(this, this._rawEntities);
this.player = this._player;
@ -63,6 +85,8 @@ BaseAI.prototype.HandleMessage = function(state)
this.map = state.map;
this.passabilityClasses = state.passabilityClasses;
Engine.ProfileStop();
this.OnUpdate();
// Clean up temporary properties, so they don't disturb the serializer
@ -77,6 +101,8 @@ BaseAI.prototype.HandleMessage = function(state)
BaseAI.prototype.ApplyEntitiesDelta = function(state)
{
Engine.ProfileStart("ApplyEntitiesDelta");
for each (var evt in state.events)
{
if (evt.type == "Create")
@ -87,23 +113,40 @@ BaseAI.prototype.ApplyEntitiesDelta = function(state)
{
delete this._rawEntities[evt.msg.entity];
delete this._entityMetadata[evt.msg.entity];
delete this._ownEntities[evt.msg.entity];
}
else if (evt.type == "TrainingFinished")
{
// Apply metadata stored in training queues, but only if they
// look like they were added by us
if (evt.msg.owner == this._player)
if (evt.msg.owner === this._player)
for each (var ent in evt.msg.entities)
this._entityMetadata[ent] = evt.msg.metadata;
this._entityMetadata[ent] = ShallowClone(evt.msg.metadata);
}
}
for (var id in state.entities)
{
var changes = state.entities[id];
if ("owner" in changes)
{
var wasOurs = (this._rawEntities[id].owner !== undefined
&& this._rawEntities[id].owner === this._player);
var isOurs = (changes.owner === this._player);
if (wasOurs && !isOurs)
delete this._ownEntities[id];
else if (!wasOurs && isOurs)
this._ownEntities[id] = this._rawEntities[id];
}
for (var prop in changes)
this._rawEntities[id][prop] = changes[prop];
}
Engine.ProfileStop();
};
BaseAI.prototype.OnUpdate = function(state)

View File

@ -3,10 +3,20 @@ function EntityCollection(baseAI, entities)
this._ai = baseAI;
this._entities = entities;
var length = 0;
for (var id in entities)
++length;
this.length = length;
// Compute length lazily on demand, since it can be
// expensive for large collections
var length = undefined;
Object.defineProperty(this, "length", {
get: function () {
if (length === undefined)
{
length = 0;
for (var id in entities)
++length;
}
return length;
}
});
}
EntityCollection.prototype.toIdArray = function()
@ -68,6 +78,18 @@ EntityCollection.prototype.filter = function(callback, thisp)
return new EntityCollection(this._ai, ret);
};
EntityCollection.prototype.filter_raw = function(callback, thisp)
{
var ret = {};
for (var id in this._entities)
{
var ent = this._entities[id];
if (callback.call(thisp, ent, id, this))
ret[id] = ent;
}
return new EntityCollection(this._ai, ret);
};
EntityCollection.prototype.forEach = function(callback, thisp)
{
for (var id in this._entities)

View File

@ -22,3 +22,11 @@ function Memoize(funcname, func)
return ret;
};
}
function ShallowClone(obj)
{
var ret = {};
for (var k in obj)
ret[k] = obj[k];
return ret;
}

View File

@ -209,6 +209,8 @@ var EconomyManager = Class({
update: function(gameState, planGroups)
{
Engine.ProfileStart("economy update");
this.reassignRolelessUnits(gameState);
this.buildMoreBuildings(gameState, planGroups);
@ -218,6 +220,8 @@ var EconomyManager = Class({
this.reassignIdleWorkers(gameState, planGroups);
this.assignToFoundations(gameState, planGroups);
Engine.ProfileStop();
},
});

View File

@ -49,16 +49,28 @@ var GameState = Class({
return this.ai.passabilityClasses[name];
},
getOwnEntities: Memoize('getOwnEntities', function()
getOwnEntities: (function()
{
return this.entities.filter(function(ent) { return ent.isOwn(); });
return new EntityCollection(this.ai, this.ai._ownEntities);
}),
getOwnEntitiesWithRole: Memoize('getOwnEntitiesWithRole', function(role)
{
return this.getOwnEntities().filter(function(ent) {
return (ent.getMetadata("role") === role);
});
var metas = this.ai._entityMetadata;
if (role === undefined)
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return true;
return (metadata.role === undefined);
});
else
return this.getOwnEntities().filter_raw(function(ent) {
var metadata = metas[ent.id];
if (!metadata || !('role' in metadata))
return false;
return (metadata.role === role);
});
}),
countEntitiesWithType: function(type)

View File

@ -45,6 +45,8 @@ var MilitaryAttackManager = Class({
if (gameState.getTimeElapsed() < 60*1000)
return;
Engine.ProfileStart("military update");
// Continually try training new units, in batches of 5
planGroups.militaryPersonnel.addPlan(100,
new UnitTrainingPlan(gameState,
@ -85,6 +87,8 @@ var MilitaryAttackManager = Class({
pending.move(targetPos[0], targetPos[1]);
}
}
Engine.ProfileStop();
},
});

View File

@ -108,12 +108,17 @@ TestBotAI.prototype.OnUpdate = function()
for each (var planGroup in this.planGroups)
remainingResources.subtract(planGroup.getEscrow());
Engine.ProfileStart("plan setup");
// Compute plans from each module
for each (var module in this.modules)
module.update(gameState, this.planGroups);
// print(uneval(this.planGroups)+"\n");
Engine.ProfileStop();
Engine.ProfileStart("plan execute");
// Execute as many plans as possible, and keep a record of
// which ones we can't afford yet
var unaffordablePlans = [];
@ -124,6 +129,8 @@ TestBotAI.prototype.OnUpdate = function()
unaffordablePlans.push({"group": planGroup, "priority": plan.priority, "plan": plan.plan});
}
Engine.ProfileStop();
this.ShareResources(remainingResources, unaffordablePlans);
// print(uneval(this.planGroups)+"\n");

View File

@ -6,6 +6,7 @@ AIInterface.prototype.Schema =
AIInterface.prototype.Init = function()
{
this.events = [];
this.changedEntities = {};
};
AIInterface.prototype.GetRepresentation = function()
@ -22,17 +23,25 @@ AIInterface.prototype.GetRepresentation = function()
this.events = [];
// Add entity representations
Engine.ProfileStart("proxy representations");
state.entities = {};
for each (var proxy in Engine.GetComponentsWithInterface(IID_AIProxy))
for (var id in this.changedEntities)
{
var rep = proxy.GetRepresentation();
if (rep !== null)
state.entities[proxy.entity] = rep;
var aiProxy = Engine.QueryInterface(+id, IID_AIProxy);
if (aiProxy)
state.entities[id] = aiProxy.GetRepresentation();
}
this.changedEntities = {};
Engine.ProfileStop();
return state;
};
AIInterface.prototype.ChangedEntity = function(ent)
{
this.changedEntities[ent] = 1;
};
// AIProxy sets up a load of event handlers to capture interesting things going on
// in the world, which we will report to AI. Handle those, and add a few more handlers
// for events that AIProxy won't capture.

View File

@ -34,6 +34,9 @@ AIProxy.prototype.Init = function()
{
this.changes = null;
this.needsFullGet = true;
// Let the AIInterface know that we exist and that it should query us
this.NotifyChange();
};
AIProxy.prototype.GetRepresentation = function()
@ -58,12 +61,22 @@ AIProxy.prototype.GetRepresentation = function()
return ret;
};
AIProxy.prototype.NotifyChange = function()
{
if (!this.changes)
{
this.changes = {};
var cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface);
cmpAIInterface.ChangedEntity(this.entity);
}
};
// AI representation-updating event handlers:
AIProxy.prototype.OnPositionChanged = function(msg)
{
if (!this.changes)
this.changes = {};
this.NotifyChange();
if (msg.inWorld)
this.changes.position = [msg.x, msg.z];
@ -73,32 +86,28 @@ AIProxy.prototype.OnPositionChanged = function(msg)
AIProxy.prototype.OnHealthChanged = function(msg)
{
if (!this.changes)
this.changes = {};
this.NotifyChange();
this.changes.hitpoints = msg.to;
};
AIProxy.prototype.OnOwnershipChanged = function(msg)
{
if (!this.changes)
this.changes = {};
this.NotifyChange();
this.changes.owner = msg.to;
};
AIProxy.prototype.OnUnitIdleChanged = function(msg)
{
if (!this.changes)
this.changes = {};
this.NotifyChange();
this.changes.idle = msg.idle;
};
AIProxy.prototype.OnTrainingQueueChanged = function(msg)
{
if (!this.changes)
this.changes = {};
this.NotifyChange();
var cmpTrainingQueue = Engine.QueryInterface(this.entity, IID_TrainingQueue);
this.changes.trainingQueue = cmpTrainingQueue.GetQueue();

View File

@ -297,7 +297,7 @@ void CMapWriter::WriteXML(const VfsPath& filename,
// This will probably need to be changed in the future, but for now we'll
// just save all entities that have a position
const CSimulation2::InterfaceList& ents = sim.GetEntitiesWithInterface(IID_Position);
CSimulation2::InterfaceList ents = sim.GetEntitiesWithInterface(IID_Position);
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
entity_id_t ent = it->first;

View File

@ -188,8 +188,8 @@ LibError CObjectManager::ReloadChangedFile(const VfsPath& path)
// object with all correct variations, and we don't want to waste space storing it just for the
// rare occurrence of hotloading, so we'll tell the component (which does preserve the information)
// to do the reloading itself
const std::map<entity_id_t, IComponent*>& cmps = m_Simulation.GetEntitiesWithInterface(IID_Visual);
for (std::map<entity_id_t, IComponent*>::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
const CSimulation2::InterfaceListUnordered& cmps = m_Simulation.GetEntitiesWithInterfaceUnordered(IID_Visual);
for (CSimulation2::InterfaceListUnordered::const_iterator eit = cmps.begin(); eit != cmps.end(); ++eit)
static_cast<ICmpVisual*>(eit->second)->Hotload(it->first);
}
}

View File

@ -404,7 +404,7 @@ void CMiniMap::Draw()
float sy = (float)m_Height / ((m_MapSize - 1) * CELL_SIZE);
CSimulation2* sim = g_Game->GetSimulation2();
const CSimulation2::InterfaceList& ents = sim->GetEntitiesWithInterface(IID_Minimap);
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap);
std::vector<MinimapUnitVertex> vertexArray;
vertexArray.reserve(ents.size());

View File

@ -20,6 +20,7 @@
#include "Replay.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/file/file_system.h"
#include "lib/tex/tex.h"
@ -223,6 +224,8 @@ void CReplayPlayer::Replay()
debug_assert(ok);
debug_printf(L"# Final state: %hs\n", Hexify(hash).c_str());
timer_DisplayClientTotals();
// Clean up
delete &g_TexMan;
tex_codec_unregister_all();

View File

@ -220,7 +220,7 @@ template<> jsval ScriptInterface::ToJSVal<CStr8>(JSContext* cx, const CStr8& val
template<typename T> static jsval ToJSVal_vector(JSContext* cx, const std::vector<T>& val)
{
JSObject* obj = JS_NewArrayObject(cx, 0, NULL);
JSObject* obj = JS_NewArrayObject(cx, val.size(), NULL);
if (!obj)
return JSVAL_VOID;
for (size_t i = 0; i < val.size(); ++i)

View File

@ -348,6 +348,45 @@ JSBool error(JSContext* cx, uintN argc, jsval* vp)
return JS_TRUE;
}
JSBool ProfileStart(JSContext* cx, uintN argc, jsval* vp)
{
if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
{
const char* name = "(ProfileStart)";
if (argc >= 1)
{
std::string str;
if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str))
return JS_FALSE;
typedef boost::flyweight<
std::string,
boost::flyweights::no_tracking,
boost::flyweights::no_locking
> StringFlyweight;
name = StringFlyweight(str).get().c_str();
}
g_Profiler.StartScript(name);
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
JSBool ProfileStop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* vp)
{
if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread())
{
g_Profiler.Stop();
}
JS_SET_RVAL(cx, vp, JSVAL_VOID);
return JS_TRUE;
}
// Math override functions:
JSBool Math_random(JSContext* cx, uintN UNUSED(argc), jsval* vp)
@ -386,15 +425,22 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh
JS_SetErrorReporter(m_cx, ErrorReporter);
JS_SetOptions(m_cx, JSOPTION_STRICT // "warn on dubious practice"
| JSOPTION_XML // "ECMAScript for XML support: parse <!-- --> as a token"
| JSOPTION_VAROBJFIX // "recommended" (fixes variable scoping)
uint32 options = 0;
options |= JSOPTION_STRICT; // "warn on dubious practice"
options |= JSOPTION_XML; // "ECMAScript for XML support: parse <!-- --> as a token"
options |= JSOPTION_VAROBJFIX; // "recommended" (fixes variable scoping)
// Enable all the JIT features:
// | JSOPTION_JIT
// | JSOPTION_METHODJIT
// | JSOPTION_PROFILING
);
// Enable method JIT, unless script profiling is enabled (since profiling
// hooks are incompatible with the JIT)
#if !ENABLE_SCRIPT_PROFILING
options |= JSOPTION_METHODJIT;
#endif
// Some other JIT flags to experiment with:
// options |= JSOPTION_JIT;
// options |= JSOPTION_PROFILING;
JS_SetOptions(m_cx, options);
JS_SetVersion(m_cx, JSVERSION_LATEST);
@ -414,6 +460,9 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh
JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
Register("ProfileStart", ::ProfileStart, 1);
Register("ProfileStop", ::ProfileStop, 0);
}
ScriptInterface_impl::~ScriptInterface_impl()

View File

@ -337,11 +337,16 @@ void CSimulation2::BroadcastMessage(const CMessage& msg) const
m->m_ComponentManager.BroadcastMessage(msg);
}
const CSimulation2::InterfaceList& CSimulation2::GetEntitiesWithInterface(int iid)
CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid)
{
return m->m_ComponentManager.GetEntitiesWithInterface(iid);
}
const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid)
{
return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid);
}
const CSimContext& CSimulation2::GetSimContext() const
{
return m->m_SimContext;

View File

@ -25,6 +25,8 @@
#include "lib/file/vfs/vfs_path.h"
#include <boost/unordered_map.hpp>
#include <map>
class CSimulation2Impl;
@ -170,8 +172,20 @@ public:
void PostMessage(entity_id_t ent, const CMessage& msg) const;
void BroadcastMessage(const CMessage& msg) const;
typedef std::map<entity_id_t, IComponent*> InterfaceList;
const InterfaceList& GetEntitiesWithInterface(int iid);
typedef std::vector<std::pair<entity_id_t, IComponent*> > InterfaceList;
typedef boost::unordered_map<entity_id_t, IComponent*> InterfaceListUnordered;
/**
* Returns a list of components implementing the given interface, and their
* associated entities, sorted by entity ID.
*/
InterfaceList GetEntitiesWithInterface(int iid);
/**
* Returns a list of components implementing the given interface, and their
* associated entities, as an unordered map.
*/
const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(int iid);
const CSimContext& GetSimContext() const;
ScriptInterface& GetScriptInterface() const;

View File

@ -250,6 +250,7 @@ public:
CAIWorker() :
m_ScriptRuntime(ScriptInterface::CreateRuntime()),
m_ScriptInterface("Engine", "AI", m_ScriptRuntime),
m_TurnNum(0),
m_CommandsComputed(true)
{
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
@ -277,12 +278,20 @@ public:
return true;
}
void StartComputation(const shared_ptr<ScriptInterface::StructuredClone>& gameState)
void StartComputation(const shared_ptr<ScriptInterface::StructuredClone>& gameState, const Grid<u16>& map)
{
debug_assert(m_CommandsComputed);
m_GameState = gameState;
if (map.m_DirtyID != m_GameStateMap.m_DirtyID)
{
m_GameStateMap = map;
JSContext* cx = m_ScriptInterface.GetContext();
m_GameStateMapVal = CScriptValRooted(cx, ScriptInterface::ToJSVal(cx, m_GameStateMap));
}
m_CommandsComputed = false;
}
@ -410,10 +419,13 @@ private:
void PerformComputation()
{
PROFILE("AI compute");
// Deserialize the game state, to pass to the AI's HandleMessage
CScriptVal state = m_ScriptInterface.ReadStructuredClone(m_GameState);
CScriptVal state;
{
PROFILE("AI compute read state");
state = m_ScriptInterface.ReadStructuredClone(m_GameState);
m_ScriptInterface.SetProperty(state.get(), "map", m_GameStateMapVal, true);
}
// It would be nice to do
// m_ScriptInterface.FreezeObject(state.get(), true);
@ -421,19 +433,34 @@ private:
// affecting other AI scripts they share it with. But the performance
// cost is far too high, so we won't do that.
for (size_t i = 0; i < m_Players.size(); ++i)
m_Players[i]->Run(state);
{
PROFILE("AI compute scripts");
for (size_t i = 0; i < m_Players.size(); ++i)
m_Players[i]->Run(state);
}
// Run the GC every so often.
// (This isn't particularly necessary, but it makes profiling clearer
// since it avoids random GC delays while running other scripts)
if (m_TurnNum++ % 25 == 0)
{
PROFILE("AI compute GC");
m_ScriptInterface.MaybeGC();
}
}
shared_ptr<ScriptRuntime> m_ScriptRuntime;
ScriptInterface m_ScriptInterface;
boost::rand48 m_RNG;
size_t m_TurnNum;
CScriptValRooted m_EntityTemplates;
std::map<std::wstring, CScriptValRooted> m_PlayerMetadata;
std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying
shared_ptr<ScriptInterface::StructuredClone> m_GameState;
Grid<u16> m_GameStateMap;
CScriptValRooted m_GameStateMapVal;
bool m_CommandsComputed;
};
@ -470,14 +497,17 @@ public:
// directly. So we'll just grab the ISerializer's stream and write to it
// with an independent serializer.
m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
// TODO: make the serialization/deserialization actually work, and not really slowly
// m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug());
UNUSED2(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
m_Worker.Deserialize(deserialize.GetStream());
// m_Worker.Deserialize(deserialize.GetStream());
UNUSED2(deserialize);
}
virtual void AddPlayer(std::wstring id, player_id_t player)
@ -497,11 +527,16 @@ public:
// Get the game state from AIInterface
CScriptVal state = cmpAIInterface->GetRepresentation();
LoadTerrainData(state);
// Get the map data
Grid<u16> dummyGrid;
const Grid<u16>* map = &dummyGrid;
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (!cmpPathfinder.null())
map = &cmpPathfinder->GetPassabilityGrid();
LoadPathfinderClasses(state);
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()));
m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()), *map);
}
virtual void PushCommands()
@ -548,22 +583,6 @@ private:
m_Worker.LoadEntityTemplates(templates);
}
void LoadTerrainData(CScriptVal state)
{
PROFILE("LoadTerrainData");
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (cmpPathfinder.null())
return;
const Grid<u16>& grid = cmpPathfinder->GetPassabilityGrid();
ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
scriptInterface.SetProperty(state.get(), "map", grid, true);
// (If this is slow, maybe we should only bother uploading the data
// if it's changed since last turn)
}
void LoadPathfinderClasses(CScriptVal state)
{
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);

View File

@ -351,6 +351,8 @@ public:
shape.flags |= FLAG_MOVING;
else
shape.flags &= ~FLAG_MOVING;
MakeDirtyDebug();
}
}
@ -453,6 +455,15 @@ private:
m_DebugOverlayDirty = true;
}
/**
* Mark the debug display as dirty.
* Call this when nothing has changed except a unit's 'moving' flag.
*/
void MakeDirtyDebug()
{
m_DebugOverlayDirty = true;
}
/**
* Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
* Call this when a static shape has changed.

View File

@ -325,6 +325,8 @@ void CCmpPathfinder::UpdateGrid()
t &= ~2;
}
}
++m_Grid->m_DirtyID;
}
else if (obstructionsDirty || m_TerrainDirty)
{
@ -388,6 +390,8 @@ void CCmpPathfinder::UpdateGrid()
}
m_TerrainDirty = false;
++m_Grid->m_DirtyID;
}
}

View File

@ -34,14 +34,41 @@
template<typename T>
class Grid
{
NONCOPYABLE(Grid);
public:
Grid(u16 w, u16 h) : m_W(w), m_H(h), m_DirtyID(0)
Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0)
{
m_Data = new T[m_W * m_H];
}
Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0)
{
if (m_W || m_H)
m_Data = new T[m_W * m_H];
reset();
}
Grid(const Grid& g)
{
*this = g;
}
Grid& operator=(const Grid& g)
{
if (this != &g)
{
m_W = g.m_W;
m_H = g.m_H;
m_DirtyID = g.m_DirtyID;
if (g.m_Data)
{
m_Data = new T[m_W * m_H];
memcpy(m_Data, g.m_Data, m_W*m_H*sizeof(T));
}
else
m_Data = NULL;
}
return *this;
}
~Grid()
{
delete[] m_Data;
@ -49,7 +76,8 @@ public:
void reset()
{
memset(m_Data, 0, m_W*m_H*sizeof(T));
if (m_Data)
memset(m_Data, 0, m_W*m_H*sizeof(T));
}
void set(size_t i, size_t j, const T& value)
@ -99,6 +127,8 @@ class SparseGrid
public:
SparseGrid(u16 w, u16 h) : m_W(w), m_H(h), m_DirtyID(0)
{
debug_assert(m_W && m_H);
m_BW = (m_W + BucketSize-1) >> BucketBits;
m_BH = (m_H + BucketSize-1) >> BucketBits;

View File

@ -37,8 +37,8 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
std::vector<std::pair<float, entity_id_t> > hits; // (dist^2, entity) pairs
const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable);
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable);
for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
entity_id_t ent = it->first;
@ -91,8 +91,8 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simul
std::vector<entity_id_t> hitEnts;
const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable);
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable);
for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
entity_id_t ent = it->first;

View File

@ -97,6 +97,8 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
m_ScriptInterface.SetGlobal("INVALID_ENTITY", (int)INVALID_ENTITY);
m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY);
m_ComponentsByInterface.resize(IID__LastNative);
ResetState();
}
@ -306,6 +308,7 @@ void CComponentManager::Script_RegisterInterface(void* cbdata, std::string name)
// IIDs start at 1, so size+1 is the next unused one
size_t id = componentManager->m_InterfaceIdsByName.size() + 1;
componentManager->m_InterfaceIdsByName[name] = (InterfaceId)id;
componentManager->m_ComponentsByInterface.resize(id+1); // add one so we can index by InterfaceId
componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id);
}
@ -350,9 +353,10 @@ std::vector<int> CComponentManager::Script_GetEntitiesWithInterface(void* cbdata
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
std::vector<int> ret;
const std::map<entity_id_t, IComponent*>& ents = componentManager->GetEntitiesWithInterface(iid);
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
const InterfaceListUnordered& ents = componentManager->GetEntitiesWithInterfaceUnordered(iid);
for (InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it)
ret.push_back(it->first); // TODO: maybe we should exclude local entities
std::sort(ret.begin(), ret.end());
return ret;
}
@ -361,8 +365,8 @@ std::vector<IComponent*> CComponentManager::Script_GetComponentsWithInterface(vo
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
std::vector<IComponent*> ret;
const std::map<entity_id_t, IComponent*>& ents = componentManager->GetEntitiesWithInterface(iid);
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
InterfaceList ents = componentManager->GetEntitiesWithInterface(iid);
for (InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
ret.push_back(it->second); // TODO: maybe we should exclude local entities
return ret;
}
@ -454,7 +458,10 @@ void CComponentManager::ResetState()
}
}
m_ComponentsByInterface.clear();
std::vector<boost::unordered_map<entity_id_t, IComponent*> >::iterator ifcit = m_ComponentsByInterface.begin();
for (; ifcit != m_ComponentsByInterface.end(); ++ifcit)
ifcit->clear();
m_ComponentsByTypeId.clear();
m_DestructionQueue.clear();
@ -583,7 +590,9 @@ IComponent* CComponentManager::ConstructComponent(entity_id_t ent, ComponentType
const ComponentType& ct = it->second;
std::map<entity_id_t, IComponent*>& emap1 = m_ComponentsByInterface[ct.iid];
debug_assert((size_t)ct.iid < m_ComponentsByInterface.size());
boost::unordered_map<entity_id_t, IComponent*>& emap1 = m_ComponentsByInterface[ct.iid];
if (emap1.find(ent) != emap1.end())
{
LOGERROR(L"Multiple components for interface %d", ct.iid);
@ -628,7 +637,7 @@ void CComponentManager::AddMockComponent(entity_id_t ent, InterfaceId iid, IComp
// Just add it into the by-interface map, not the by-component-type map,
// so it won't be considered for messages or deletion etc
std::map<entity_id_t, IComponent*>& emap1 = m_ComponentsByInterface[iid];
boost::unordered_map<entity_id_t, IComponent*>& emap1 = m_ComponentsByInterface.at(iid);
if (emap1.find(ent) != emap1.end())
debug_warn(L"Multiple components for interface");
emap1.insert(std::make_pair(ent, &component));
@ -712,25 +721,24 @@ void CComponentManager::FlushDestroyedComponents()
}
// Remove from m_ComponentsByInterface
std::map<InterfaceId, std::map<entity_id_t, IComponent*> >::iterator ifcit = m_ComponentsByInterface.begin();
std::vector<boost::unordered_map<entity_id_t, IComponent*> >::iterator ifcit = m_ComponentsByInterface.begin();
for (; ifcit != m_ComponentsByInterface.end(); ++ifcit)
{
ifcit->second.erase(ent);
ifcit->erase(ent);
}
}
}
IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const
{
std::map<InterfaceId, std::map<entity_id_t, IComponent*> >::const_iterator iit = m_ComponentsByInterface.find(iid);
if (iit == m_ComponentsByInterface.end())
if ((size_t)iid >= m_ComponentsByInterface.size())
{
// Invalid iid, or no entities implement this interface
// Invalid iid
return NULL;
}
std::map<entity_id_t, IComponent*>::const_iterator eit = iit->second.find(ent);
if (eit == iit->second.end())
boost::unordered_map<entity_id_t, IComponent*>::const_iterator eit = m_ComponentsByInterface[iid].find(ent);
if (eit == m_ComponentsByInterface[iid].end())
{
// This entity doesn't implement this interface
return NULL;
@ -739,17 +747,37 @@ IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid)
return eit->second;
}
static std::map<entity_id_t, IComponent*> g_EmptyEntityMap;
const std::map<entity_id_t, IComponent*>& CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const
CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const
{
std::map<InterfaceId, std::map<entity_id_t, IComponent*> >::const_iterator iit = m_ComponentsByInterface.find(iid);
if (iit == m_ComponentsByInterface.end())
std::vector<std::pair<entity_id_t, IComponent*> > ret;
if ((size_t)iid >= m_ComponentsByInterface.size())
{
// Invalid iid, or no entities implement this interface
// Invalid iid
return ret;
}
ret.reserve(m_ComponentsByInterface[iid].size());
boost::unordered_map<entity_id_t, IComponent*>::const_iterator it = m_ComponentsByInterface[iid].begin();
for (; it != m_ComponentsByInterface[iid].end(); ++it)
ret.push_back(*it);
std::sort(ret.begin(), ret.end()); // lexicographic pair comparison means this'll sort by entity ID
return ret;
}
static CComponentManager::InterfaceListUnordered g_EmptyEntityMap;
const CComponentManager::InterfaceListUnordered& CComponentManager::GetEntitiesWithInterfaceUnordered(InterfaceId iid) const
{
if ((size_t)iid >= m_ComponentsByInterface.size())
{
// Invalid iid
return g_EmptyEntityMap;
}
return iit->second;
return m_ComponentsByInterface[iid];
}
void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const

View File

@ -24,6 +24,7 @@
#include "simulation2/helpers/Player.h"
#include <boost/random/linear_congruential.hpp>
#include <boost/unordered_map.hpp>
#include <map>
@ -180,7 +181,11 @@ public:
IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const;
const std::map<entity_id_t, IComponent*>& GetEntitiesWithInterface(InterfaceId iid) const;
typedef std::vector<std::pair<entity_id_t, IComponent*> > InterfaceList;
typedef boost::unordered_map<entity_id_t, IComponent*> InterfaceListUnordered;
InterfaceList GetEntitiesWithInterface(InterfaceId iid) const;
const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(InterfaceId iid) const;
/**
* Send a message, targeted at a particular entity. The message will be received by any
@ -242,7 +247,7 @@ private:
// TODO: some of these should be vectors
std::map<ComponentTypeId, ComponentType> m_ComponentTypesById;
std::map<InterfaceId, std::map<entity_id_t, IComponent*> > m_ComponentsByInterface;
std::vector<boost::unordered_map<entity_id_t, IComponent*> > m_ComponentsByInterface; // indexed by InterfaceId
std::map<ComponentTypeId, std::map<entity_id_t, IComponent*> > m_ComponentsByTypeId;
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_LocalMessageSubscriptions;
std::map<MessageTypeId, std::vector<ComponentTypeId> > m_GlobalMessageSubscriptions;