Index: binaries/data/mods/public/maps/scenarios/ress_gathering.js =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/scenarios/ress_gathering.js @@ -0,0 +1,133 @@ +const REG_UNIT_TEMPLATE = "units/athen/infantry_spearman_b"; +const TREE_TEMPLATE = "gaia/tree/acacia"; +const FARM_TEMPLATE = "structures/athen/field"; +const STOREHOUSE = "structures/athen/storehouse"; +const FARMSTEAD = "structures/athen/farmstead"; + +const ATTACKER = 2; + +var QuickSpawn = function(x, z, template, owner = 1) +{ + let ent = Engine.AddEntity(template); + + let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership); + if (cmpEntOwnership) + cmpEntOwnership.SetOwner(owner); + + let cmpEntPosition = Engine.QueryInterface(ent, IID_Position); + cmpEntPosition.JumpTo(x, z); + return ent; +}; + +var WalkTo = function(x, z, queued, ent, owner=1) +{ + ProcessCommand(owner, { + "type": "walk", + "entities": Array.isArray(ent) ? ent : [ent], + "x": x, + "z": z, + "queued": queued, + "force": true, + }); + return ent; +}; + +var Do = function(name, data, ent, owner = 1) +{ + let comm = { + "type": name, + "entities": Array.isArray(ent) ? ent : [ent], + "queued": false + }; + for (let k in data) + comm[k] = data[k]; + ProcessCommand(owner, comm); +}; + + +var farming = (gx, gy) => { + let dropsite = QuickSpawn(gx + 50, gy + 50, FARMSTEAD); + let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager); + cmpModifiersManager.AddModifiers("root", { + "TerritoryInfluence/Root": [{ "affects": ["Structure"], "replace": true }], + }, dropsite); + for (let i = 20; i < 80; i += 20) + for (let j = 20; j < 80; j += 20) + { + let farm = QuickSpawn(gx + i, gy + j, FARM_TEMPLATE); + Do("gather", { "target": farm }, QuickSpawn(gx + i, gy + j, REG_UNIT_TEMPLATE)); + Do("gather", { "target": farm }, QuickSpawn(gx + i, gy + j+2, REG_UNIT_TEMPLATE)); + Do("gather", { "target": farm }, QuickSpawn(gx + i, gy + j-2, REG_UNIT_TEMPLATE)); + } +}; + +var woodcutting = (gx, gy) => { + let dropsite = QuickSpawn(gx + 50, gy, STOREHOUSE); + let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager); + cmpModifiersManager.AddModifiers("root", { + "TerritoryInfluence/Root": [{ "affects": ["Structure"], "replace": true }], + }, dropsite); + for (let i = 10; i <= 90; i += 6) + for (let j = 10; j <= 90; j += 6) + QuickSpawn(gx + i, gy + j, TREE_TEMPLATE); + for (let i = 10; i <= 90; i += 5) + Do("gather-near-position", { "x": gx + i, "z": gy + 10, "resourceType": { "generic": "wood", "specific": "tree" }, "resourceTemplate": TREE_TEMPLATE }, + QuickSpawn(gx + i, gy, REG_UNIT_TEMPLATE)); +}; + +var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); + +Trigger.prototype.Setup = function() +{ + let start = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime(); + + let gx = 100; + let gy = 100; + for (let i = 0; i < 20; ++i) + { + farming(gx, gy); + gx += 100; + if (gx > 20*16*4-20) + { + gx = 100; + gy += 100; + } + } + for (let i = 0; i < 20; ++i) + { + woodcutting(gx, gy); + gx += 100; + if (gx > 20*16*4-20) + { + gx = 100; + gy += 100; + } + } +}; + +Trigger.prototype.EndGame = function() +{ + Engine.QueryInterface(4, IID_Player).SetState("defeated", "trigger"); + Engine.QueryInterface(3, IID_Player).SetState("won", "trigger"); +}; + +cmpTrigger.DoAfterDelay(2000, "Setup", {}); + +cmpTrigger.DoAfterDelay(300000, "EndGame", {}); + +/* +var cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager); + +// Reduce player 1 vision range (or patrolling units reacct) +cmpModifiersManager.AddModifiers("no_promotion", { + "Vision/Range": [{ "affects": ["Unit"], "replace": 5 }], +}, 3); // player 1 is ent 3 + +// Prevent promotions, messes up things. +cmpModifiersManager.AddModifiers("no_promotion_A", { + "Promotion/RequiredXp": [{ "affects": ["Unit"], "replace": 50000 }], +}, 3); +cmpModifiersManager.AddModifiers("no_promotion_B", { + "Promotion/RequiredXp": [{ "affects": ["Unit"], "replace": 50000 }], +}, 4); // player 2 is ent 4 +*/ Index: binaries/data/mods/public/maps/scenarios/ress_gathering.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/maps/scenarios/ress_gathering.xml @@ -0,0 +1,66 @@ + + + + + + default + + + + + + 0 + 0.5 + + + + + ocean + + + 5 + 4 + 0.45 + 0 + + + + 0 + 1 + 0.99 + 0.1999 + default + + + + + + + + + + + + Index: binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/entity.js +++ binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -683,24 +683,25 @@ }, "resourceSupplyAmount": function() { - return this._entity.resourceSupplyAmount; + return SimEngine.QueryInterface(this.id(), Sim.IID_ResourceSupply)?.GetCurrentAmount(); }, "resourceSupplyNumGatherers": function() { - return this._entity.resourceSupplyNumGatherers; + return SimEngine.QueryInterface(this.id(), Sim.IID_ResourceSupply)?.GetNumGatherers(); }, "isFull": function() { - if (this._entity.resourceSupplyNumGatherers !== undefined) - return this.maxGatherers() === this._entity.resourceSupplyNumGatherers; + let numGatherers = this.resourceSupplyNumGatherers(); + if (numGatherers) + return this.maxGatherers() === numGatherers; return undefined; }, "resourceCarrying": function() { - return this._entity.resourceCarrying; + return SimEngine.QueryInterface(this.id(), Sim.IID_ResourceGatherer)?.GetCarryingStatus(); }, "currentGatherRate": function() { Index: binaries/data/mods/public/simulation/components/AIProxy.js =================================================================== --- binaries/data/mods/public/simulation/components/AIProxy.js +++ binaries/data/mods/public/simulation/components/AIProxy.js @@ -179,27 +179,6 @@ this.cmpAIInterface.PushEvent("UnGarrison", { "entity": ent, "holder": this.entity }); }; -AIProxy.prototype.OnResourceSupplyChanged = function(msg) -{ - if (!this.NotifyChange()) - return; - this.changes.resourceSupplyAmount = msg.to; -}; - -AIProxy.prototype.OnResourceSupplyNumGatherersChanged = function(msg) -{ - if (!this.NotifyChange()) - return; - this.changes.resourceSupplyNumGatherers = msg.to; -}; - -AIProxy.prototype.OnResourceCarryingChanged = function(msg) -{ - if (!this.NotifyChange()) - return; - this.changes.resourceCarrying = msg.to; -}; - AIProxy.prototype.OnFoundationProgressChanged = function(msg) { if (!this.NotifyChange()) @@ -305,21 +284,6 @@ ret.foundationProgress = cmpFoundation.GetBuildPercentage(); } - let cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply); - if (cmpResourceSupply) - { - // Updated by OnResourceSupplyChanged - ret.resourceSupplyAmount = cmpResourceSupply.GetCurrentAmount(); - ret.resourceSupplyNumGatherers = cmpResourceSupply.GetNumGatherers(); - } - - let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); - if (cmpResourceGatherer) - { - // Updated by OnResourceCarryingChanged - ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus(); - } - let cmpResourceDropsite = Engine.QueryInterface(this.entity, IID_ResourceDropsite); if (cmpResourceDropsite) { Index: binaries/data/mods/public/simulation/components/ResourceGatherer.js =================================================================== --- binaries/data/mods/public/simulation/components/ResourceGatherer.js +++ binaries/data/mods/public/simulation/components/ResourceGatherer.js @@ -69,8 +69,6 @@ { for (let resource of resources) this.carrying[resource.type] = +resource.amount; - - Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); }; /** @@ -199,9 +197,6 @@ if (cmpStatisticsTracker) cmpStatisticsTracker.IncreaseResourceGatheredCounter(type.generic, status.amount, type.specific); - Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); - - return { "amount": status.amount, "exhausted": status.exhausted, @@ -316,17 +311,12 @@ return; let change = cmpResourceDropsite.ReceiveResources(this.carrying, this.entity); - let changed = false; for (let type in change) { this.carrying[type] -= change[type]; if (this.carrying[type] == 0) delete this.carrying[type]; - changed = true; } - - if (changed) - Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); }; /** @@ -337,8 +327,6 @@ ResourceGatherer.prototype.DropResources = function() { this.carrying = {}; - - Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() }); }; Index: binaries/data/mods/public/simulation/components/ResourceSupply.js =================================================================== --- binaries/data/mods/public/simulation/components/ResourceSupply.js +++ binaries/data/mods/public/simulation/components/ResourceSupply.js @@ -274,7 +274,6 @@ return true; this.gatherers.push(gathererID); - Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); return true; }; @@ -307,10 +306,7 @@ { let index = this.gatherers.indexOf(gathererID); if (index != -1) - { this.gatherers.splice(index, 1); - Engine.PostMessage(this.entity, MT_ResourceSupplyNumGatherersChanged, { "to": this.GetNumGatherers() }); - } index = this.activeGatherers.indexOf(gathererID); if (index == -1) Index: binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js +++ binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js @@ -1,7 +1 @@ Engine.RegisterInterface("ResourceGatherer"); - -/** - * Message of the form { "to": [{ "type": string, "amount": number, "max":number }] } - * sent from ResourceGatherer component whenever the amount of carried resources changes. - */ -Engine.RegisterMessageType("ResourceCarryingChanged"); Index: binaries/data/mods/public/simulation/components/interfaces/ResourceSupply.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/ResourceSupply.js +++ binaries/data/mods/public/simulation/components/interfaces/ResourceSupply.js @@ -5,9 +5,3 @@ * sent from ResourceSupply component whenever the supply level changes. */ Engine.RegisterMessageType("ResourceSupplyChanged"); - -/** - * Message of the form { "to": number } - * sent from ResourceSupply component whenever the number of gatherer changes. - */ -Engine.RegisterMessageType("ResourceSupplyNumGatherersChanged"); Index: source/scriptinterface/ScriptInterface.h =================================================================== --- source/scriptinterface/ScriptInterface.h +++ source/scriptinterface/ScriptInterface.h @@ -111,6 +111,16 @@ */ ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& context); + /** + * Alternate constructor. This creates the new Realm in the same Compartment as the neighbor scriptInterface. + * This means that data can be freely exchanged between these two script interfaces without cloning. + * @param nativeScopeName Name of global object that functions (via ScriptFunction::Register) will + * be placed into, as a scoping mechanism; typically "Engine" + * @param debugName Name of this interface for CScriptStats purposes. + * @param scriptInterface 'Neighbor' scriptInterface to share a compartment with. + */ + ScriptInterface(const char* nativeScopeName, const char* debugName, const ScriptInterface& neighbor); + ~ScriptInterface(); struct CmptPrivate Index: source/scriptinterface/ScriptInterface.cpp =================================================================== --- source/scriptinterface/ScriptInterface.cpp +++ source/scriptinterface/ScriptInterface.cpp @@ -54,7 +54,7 @@ struct ScriptInterface_impl { - ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& context); + ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& context, JS::Compartment* compartment); ~ScriptInterface_impl(); // Take care to keep this declaration before heap rooted members. Destructors of heap rooted @@ -288,7 +288,7 @@ return true; } -ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& context) : +ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& context, JS::Compartment* compartment) : m_context(context), m_cx(context->GetGeneralJSContext()), m_glob(context->GetGeneralJSContext()), m_nativeScope(context->GetGeneralJSContext()) { JS::RealmCreationOptions creationOpt; @@ -296,6 +296,13 @@ creationOpt.setPreserveJitCode(true); // Enable uneval creationOpt.setToSourceEnabled(true); + + if (compartment) + creationOpt.setExistingCompartment(compartment); + else + // This is the default behaviour. + creationOpt.setNewCompartmentAndZone(); + JS::RealmOptions opt(creationOpt, JS::RealmBehaviors{}); m_glob = JS_NewGlobalObject(m_cx, &global_class, nullptr, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt); @@ -329,7 +336,7 @@ } ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& context) : - m(std::make_unique(nativeScopeName, context)) + m(std::make_unique(nativeScopeName, context, nullptr)) { // Profiler stats table isn't thread-safe, so only enable this on the main thread if (Threading::IsMainThread()) @@ -340,7 +347,25 @@ ScriptRequest rq(this); m_CmptPrivate.pScriptInterface = this; - JS_SetCompartmentPrivate(js::GetObjectCompartment(rq.glob), (void*)&m_CmptPrivate); + JS::SetRealmPrivate(JS::GetObjectRealmOrNull(rq.glob), (void*)&m_CmptPrivate); +} + +ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const ScriptInterface& neighbor) +{ + ScriptRequest nrq(neighbor); + JS::Compartment* comp = JS::GetCompartmentForRealm(JS::GetCurrentRealmOrNull(nrq.cx)); + m = std::make_unique(nativeScopeName, neighbor.GetContext(), comp); + + // Profiler stats table isn't thread-safe, so only enable this on the main thread + if (Threading::IsMainThread()) + { + if (g_ScriptStatsTable) + g_ScriptStatsTable->Add(this, debugName); + } + + ScriptRequest rq(this); + m_CmptPrivate.pScriptInterface = this; + JS::SetRealmPrivate(JS::GetObjectRealmOrNull(rq.glob), (void*)&m_CmptPrivate); } ScriptInterface::~ScriptInterface() @@ -359,7 +384,7 @@ ScriptInterface::CmptPrivate* ScriptInterface::GetScriptInterfaceAndCBData(JSContext* cx) { - CmptPrivate* pCmptPrivate = (CmptPrivate*)JS_GetCompartmentPrivate(js::GetContextCompartment(cx)); + CmptPrivate* pCmptPrivate = (CmptPrivate*)JS::GetRealmPrivate(JS::GetCurrentRealmOrNull(cx)); return pCmptPrivate; } Index: source/simulation2/components/CCmpAIManager.cpp =================================================================== --- source/simulation2/components/CCmpAIManager.cpp +++ source/simulation2/components/CCmpAIManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -212,16 +212,32 @@ }; CAIWorker() : - m_ScriptInterface(new ScriptInterface("Engine", "AI", g_ScriptContext)), m_TurnNum(0), m_CommandsComputed(true), m_HasLoadedEntityTemplates(false), - m_HasSharedComponent(false), - m_EntityTemplates(g_ScriptContext->GetGeneralJSContext()), - m_SharedAIObj(g_ScriptContext->GetGeneralJSContext()), - m_PassabilityMapVal(g_ScriptContext->GetGeneralJSContext()), - m_TerritoryMapVal(g_ScriptContext->GetGeneralJSContext()) + m_HasSharedComponent(false) { + } + + ~CAIWorker() + { + JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); + } + + void Init(CComponentManager* compMan) + { + m_ComponentManager = compMan; + + // Create the script interface in the same compartment as the simulation interface. + // This will allow us to directly share data from the sim to the AI (and vice versa, should the need arise). + m_ScriptInterface = std::make_shared("Engine", "AI", compMan->GetScriptInterface()); + + ScriptRequest rq(m_ScriptInterface); + + m_EntityTemplates.init(rq.cx); + m_SharedAIObj.init(rq.cx); + m_PassabilityMapVal.init(rq.cx); + m_TerritoryMapVal.init(rq.cx); m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG); @@ -229,9 +245,17 @@ JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); - ScriptRequest rq(m_ScriptInterface); + { + ScriptRequest simrq(m_ComponentManager->GetScriptInterface()); + // Register the sim globals for easy & explicit access. Mark it replaceable for hotloading. + JS::RootedValue global(rq.cx, simrq.globalValue()); + m_ScriptInterface->SetGlobal("Sim", global, true); + JS::RootedValue scope(rq.cx, JS::ObjectValue(*simrq.nativeScope.get())); + m_ScriptInterface->SetGlobal("SimEngine", scope, true); + } + #define REGISTER_FUNC_NAME(func, name) \ - ScriptFunction::Register<&CAIWorker::func, ScriptFunction::ObjectFromCBData>(rq, name); +ScriptFunction::Register<&CAIWorker::func, ScriptFunction::ObjectFromCBData>(rq, name); REGISTER_FUNC_NAME(PostCommand, "PostCommand"); REGISTER_FUNC_NAME(LoadScripts, "IncludeModule"); @@ -248,11 +272,7 @@ // Globalscripts may use VFS script functions m_ScriptInterface->LoadGlobalScripts(); - } - ~CAIWorker() - { - JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); } bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; } @@ -808,14 +828,12 @@ } } - // Take care to keep this declaration before heap rooted members. Destructors of heap rooted - // members have to be called before the context destructor. - shared_ptr m_ScriptContext; - shared_ptr m_ScriptInterface; boost::rand48 m_RNG; u32 m_TurnNum; + CComponentManager* m_ComponentManager = nullptr; + JS::PersistentRootedValue m_EntityTemplates; bool m_HasLoadedEntityTemplates; @@ -864,6 +882,8 @@ virtual void Init(const CParamNode& UNUSED(paramNode)) { + m_Worker.Init(&GetSimContext().GetComponentManager()); + m_TerritoriesDirtyID = 0; m_TerritoriesDirtyBlinkingID = 0; m_JustDeserialized = false; Index: source/tools/profiler2/profiler2.js =================================================================== --- source/tools/profiler2/profiler2.js +++ source/tools/profiler2/profiler2.js @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Wildfire Games. +// Copyright (C) 2021 Wildfire Games. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -239,8 +239,9 @@ series_data[rep + "/" + g_active_elements[typeI]] = data.time_by_frame; else series_data[rep + "/" + g_active_elements[typeI]] = smooth_1D_array(data.time_by_frame, +document.getElementById('smooth').value); - if (data.max > y_scale) - y_scale = data.max; + let max = Math.max(...series_data[rep + "/" + g_active_elements[typeI]].map(x => isNaN(x) ? 0 : x)); + if (max > y_scale) + y_scale = max; if (use_log_scale === null && data.log_scale) use_log_scale = true; }