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:
parent
8d2d4a8505
commit
16a4eb36dd
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user