diff --git a/binaries/data/mods/public/simulation/ai/common-api/base.js b/binaries/data/mods/public/simulation/ai/common-api/base.js index 208e30218c..0d50e50346 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/base.js +++ b/binaries/data/mods/public/simulation/ai/common-api/base.js @@ -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) diff --git a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js index 7848b3472b..0d35e07de6 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js @@ -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) diff --git a/binaries/data/mods/public/simulation/ai/common-api/utils.js b/binaries/data/mods/public/simulation/ai/common-api/utils.js index 3703e9ff6f..bb5d8d167f 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/utils.js +++ b/binaries/data/mods/public/simulation/ai/common-api/utils.js @@ -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; +} diff --git a/binaries/data/mods/public/simulation/ai/testbot/economy.js b/binaries/data/mods/public/simulation/ai/testbot/economy.js index 759ccfe438..236739c042 100644 --- a/binaries/data/mods/public/simulation/ai/testbot/economy.js +++ b/binaries/data/mods/public/simulation/ai/testbot/economy.js @@ -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(); }, }); diff --git a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js index bf28611441..73af98ad61 100644 --- a/binaries/data/mods/public/simulation/ai/testbot/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/testbot/gamestate.js @@ -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) diff --git a/binaries/data/mods/public/simulation/ai/testbot/military.js b/binaries/data/mods/public/simulation/ai/testbot/military.js index c96615b304..fbb3a03929 100644 --- a/binaries/data/mods/public/simulation/ai/testbot/military.js +++ b/binaries/data/mods/public/simulation/ai/testbot/military.js @@ -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(); }, }); diff --git a/binaries/data/mods/public/simulation/ai/testbot/testbot.js b/binaries/data/mods/public/simulation/ai/testbot/testbot.js index 9502e3536f..e47d4a3c08 100644 --- a/binaries/data/mods/public/simulation/ai/testbot/testbot.js +++ b/binaries/data/mods/public/simulation/ai/testbot/testbot.js @@ -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"); diff --git a/binaries/data/mods/public/simulation/components/AIInterface.js b/binaries/data/mods/public/simulation/components/AIInterface.js index 9f8b9a37e3..546ae123be 100644 --- a/binaries/data/mods/public/simulation/components/AIInterface.js +++ b/binaries/data/mods/public/simulation/components/AIInterface.js @@ -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. diff --git a/binaries/data/mods/public/simulation/components/AIProxy.js b/binaries/data/mods/public/simulation/components/AIProxy.js index 9ef7ad577c..7fc2312f79 100644 --- a/binaries/data/mods/public/simulation/components/AIProxy.js +++ b/binaries/data/mods/public/simulation/components/AIProxy.js @@ -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(); diff --git a/source/graphics/MapWriter.cpp b/source/graphics/MapWriter.cpp index 36363f885a..0025e76e8c 100644 --- a/source/graphics/MapWriter.cpp +++ b/source/graphics/MapWriter.cpp @@ -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; diff --git a/source/graphics/ObjectManager.cpp b/source/graphics/ObjectManager.cpp index 73d7ace31a..576a2781c2 100644 --- a/source/graphics/ObjectManager.cpp +++ b/source/graphics/ObjectManager.cpp @@ -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& cmps = m_Simulation.GetEntitiesWithInterface(IID_Visual); - for (std::map::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(eit->second)->Hotload(it->first); } } diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp index ec3635f399..a431d36a89 100644 --- a/source/gui/MiniMap.cpp +++ b/source/gui/MiniMap.cpp @@ -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 vertexArray; vertexArray.reserve(ents.size()); diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 87d93c44a4..fc4917e337 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -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(); diff --git a/source/scriptinterface/ScriptConversions.cpp b/source/scriptinterface/ScriptConversions.cpp index 9183a1b3b0..1660bc5b4d 100644 --- a/source/scriptinterface/ScriptConversions.cpp +++ b/source/scriptinterface/ScriptConversions.cpp @@ -220,7 +220,7 @@ template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CStr8& val template static jsval ToJSVal_vector(JSContext* cx, const std::vector& 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) diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index b290aa1aa0..a12d6f2e0e 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -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() diff --git a/source/simulation2/Simulation2.cpp b/source/simulation2/Simulation2.cpp index 0775ec1913..d04e0a548a 100644 --- a/source/simulation2/Simulation2.cpp +++ b/source/simulation2/Simulation2.cpp @@ -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; diff --git a/source/simulation2/Simulation2.h b/source/simulation2/Simulation2.h index 59c2aa5156..1a870d3e36 100644 --- a/source/simulation2/Simulation2.h +++ b/source/simulation2/Simulation2.h @@ -25,6 +25,8 @@ #include "lib/file/vfs/vfs_path.h" +#include + #include 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 InterfaceList; - const InterfaceList& GetEntitiesWithInterface(int iid); + typedef std::vector > InterfaceList; + typedef boost::unordered_map 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; diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp index f5b907f3cf..b969887ca6 100644 --- a/source/simulation2/components/CCmpAIManager.cpp +++ b/source/simulation2/components/CCmpAIManager.cpp @@ -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 (this)); @@ -277,12 +278,20 @@ public: return true; } - void StartComputation(const shared_ptr& gameState) + void StartComputation(const shared_ptr& gameState, const Grid& 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 m_ScriptRuntime; ScriptInterface m_ScriptInterface; boost::rand48 m_RNG; + size_t m_TurnNum; CScriptValRooted m_EntityTemplates; std::map m_PlayerMetadata; std::vector > m_Players; // use shared_ptr just to avoid copying shared_ptr m_GameState; + Grid 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 dummyGrid; + const Grid* map = &dummyGrid; + CmpPtr 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 cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); - if (cmpPathfinder.null()) - return; - - const Grid& 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 cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index a5950af849..08c8333384 100644 --- a/source/simulation2/components/CCmpObstructionManager.cpp +++ b/source/simulation2/components/CCmpObstructionManager.cpp @@ -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. diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index f4b2edd916..643292270a 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -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; } } diff --git a/source/simulation2/helpers/Grid.h b/source/simulation2/helpers/Grid.h index eda4531d37..147edc02c1 100644 --- a/source/simulation2/helpers/Grid.h +++ b/source/simulation2/helpers/Grid.h @@ -34,14 +34,41 @@ template 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; diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp index 2d8d9b9838..b4365765a5 100644 --- a/source/simulation2/helpers/Selection.cpp +++ b/source/simulation2/helpers/Selection.cpp @@ -37,8 +37,8 @@ std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simu std::vector > 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 EntitySelection::PickEntitiesInRect(CSimulation2& simul std::vector 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; diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index e5f0c262dd..4eda5dc974 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -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 CComponentManager::Script_GetEntitiesWithInterface(void* cbdata CComponentManager* componentManager = static_cast (cbdata); std::vector ret; - const std::map& ents = componentManager->GetEntitiesWithInterface(iid); - for (std::map::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 CComponentManager::Script_GetComponentsWithInterface(vo CComponentManager* componentManager = static_cast (cbdata); std::vector ret; - const std::map& ents = componentManager->GetEntitiesWithInterface(iid); - for (std::map::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 >::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& emap1 = m_ComponentsByInterface[ct.iid]; + debug_assert((size_t)ct.iid < m_ComponentsByInterface.size()); + + boost::unordered_map& 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& emap1 = m_ComponentsByInterface[iid]; + boost::unordered_map& 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 >::iterator ifcit = m_ComponentsByInterface.begin(); + std::vector >::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 >::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::const_iterator eit = iit->second.find(ent); - if (eit == iit->second.end()) + boost::unordered_map::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 g_EmptyEntityMap; -const std::map& CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const +CComponentManager::InterfaceList CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { - std::map >::const_iterator iit = m_ComponentsByInterface.find(iid); - if (iit == m_ComponentsByInterface.end()) + std::vector > 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::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 diff --git a/source/simulation2/system/ComponentManager.h b/source/simulation2/system/ComponentManager.h index 61ea6cf9cf..f1e3dc9ec7 100644 --- a/source/simulation2/system/ComponentManager.h +++ b/source/simulation2/system/ComponentManager.h @@ -24,6 +24,7 @@ #include "simulation2/helpers/Player.h" #include +#include #include @@ -180,7 +181,11 @@ public: IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; - const std::map& GetEntitiesWithInterface(InterfaceId iid) const; + typedef std::vector > InterfaceList; + typedef boost::unordered_map 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 m_ComponentTypesById; - std::map > m_ComponentsByInterface; + std::vector > m_ComponentsByInterface; // indexed by InterfaceId std::map > m_ComponentsByTypeId; std::map > m_LocalMessageSubscriptions; std::map > m_GlobalMessageSubscriptions;