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;
}