diff --git a/binaries/data/mods/public/simulation/ai/common-api/base.js b/binaries/data/mods/public/simulation/ai/common-api/base.js
new file mode 100644
index 0000000000..128ce8a5c8
--- /dev/null
+++ b/binaries/data/mods/public/simulation/ai/common-api/base.js
@@ -0,0 +1,54 @@
+function BaseAI(settings)
+{
+ if (!settings)
+ return;
+
+ // Make some properties non-enumerable, so they won't be serialised
+ Object.defineProperty(this, "_player", {value: settings.player, enumerable: false});
+ Object.defineProperty(this, "_templates", {value: settings.templates, enumerable: false});
+}
+
+BaseAI.prototype.HandleMessage = function(state)
+{
+ if (!this._rawEntities)
+ this._rawEntities = state.entities;
+ else
+ this.ApplyEntitiesDelta(state);
+
+//print("### "+uneval(state)+"\n\n");
+//print("@@@ "+uneval(this._rawEntities)+"\n\n");
+
+ this.entities = new EntityCollection(this, this._rawEntities);
+
+ this.OnUpdate();
+
+ // Clean up temporary properties, so they don't disturb the serializer
+ delete this.entities;
+};
+
+BaseAI.prototype.ApplyEntitiesDelta = function(state)
+{
+ for each (var evt in state.events)
+ {
+ if (evt.type == "Destroy")
+ {
+ delete this._rawEntities[evt.msg.entity];
+ }
+ }
+
+ for (var id in state.entities)
+ {
+ var changes = state.entities[id];
+ for (var prop in changes)
+ this._rawEntities[id][prop] = changes[prop];
+ }
+};
+
+BaseAI.prototype.OnUpdate = function(state)
+{
+};
+
+BaseAI.prototype.chat = function(message)
+{
+ Engine.PostCommand({"type": "chat", "message": message});
+};
diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js
new file mode 100644
index 0000000000..db745ac50d
--- /dev/null
+++ b/binaries/data/mods/public/simulation/ai/common-api/entity.js
@@ -0,0 +1,123 @@
+function Entity(baseAI, entity)
+{
+ this._ai = baseAI;
+ this._entity = entity;
+ this._template = baseAI._templates[entity.template];
+}
+
+Entity.prototype = {
+ get rank() {
+ if (!this._template.Identity)
+ return undefined;
+ return this._template.Identity.Rank;
+ },
+
+ get classes() {
+ if (!this._template.Identity || !this._template.Identity.Classes)
+ return undefined;
+ return this._template.Identity.Classes._string.split(/\s+/);
+ },
+
+ get civ() {
+ if (!this._template.Identity)
+ return undefined;
+ return this._template.Identity.Civ;
+ },
+
+
+ get position() { return this._entity.position; },
+
+
+ get hitpoints() { return this._entity.hitpoints; },
+ get maxHitpoints() { return this._template.Health.Max; },
+ get isHurt() { return this.hitpoints < this.maxHitpoints; },
+ get needsHeal() { return this.isHurt && (this._template.Health.Healable == "true"); },
+ get needsRepair() { return this.isHurt && (this._template.Health.Repairable == "true"); },
+
+
+ // TODO: attack, armour
+
+
+ get buildableEntities() {
+ if (!this._template.Builder)
+ return undefined;
+ var templates = this._template.Builder.Entities._string.replace(/\{civ\}/g, this.civ).split(/\s+/);
+ return templates; // TODO: map to Entity?
+ },
+
+ get trainableEntities() {
+ if (!this._template.TrainingQueue)
+ return undefined;
+ var templates = this._template.TrainingQueue.Entities._string.replace(/\{civ\}/g, this.civ).split(/\s+/);
+ return templates;
+ },
+
+
+ get trainingQueue() { return this._entity.trainingQueue; },
+
+ get foundationProgress() { return this._entities.foundationProgress; },
+
+
+ get owner() { return this._entity.owner; },
+ get isOwn() { return this._entity.owner == this._ai._player; },
+ get isFriendly() { return this.isOwn; }, // TODO: diplomacy
+ get isEnemy() { return !this.isOwn; }, // TODO: diplomacy
+
+
+ get resourceSupplyType() {
+ if (!this._template.ResourceSupply)
+ return undefined;
+ var [type, subtype] = this._template.ResourceSupply.Type.split('.');
+ return { "generic": type, "specific": subtype };
+ },
+
+ get resourceSupplyMax() {
+ if (!this._template.ResourceSupply)
+ return undefined;
+ return +this._template.ResourceSupply.Amount;
+ },
+
+ get resourceSupplyAmount() { return this._entity.resourceSupplyAmount; },
+
+
+ get resourceGatherRates() {
+ if (!this._template.ResourceGatherer)
+ return undefined;
+ var ret = {};
+ for (var r in this._template.ResourceGatherer.Rates)
+ ret[r] = this._template.ResourceGatherer.Rates[r] * this._template.ResourceGatherer.BaseSpeed;
+ return ret;
+ },
+
+ get resourceCarrying() { return this._entity.resourceCarrying; },
+
+
+ get resourceDropsiteTypes() {
+ if (!this._template.ResourceDropsite)
+ return undefined;
+ return this._template.ResourceDropsite.Types.split(/\s+/);
+ },
+
+
+ get garrisoned() { return new EntityCollection(this._ai, this._entity.garrisoned); },
+
+ get garrisonableClasses() {
+ if (!this._template.GarrisonHolder)
+ return undefined;
+ return this._template.GarrisonHolder.List._string.split(/\s+/);
+ },
+
+
+ // TODO: visibility
+
+
+ move: function(x, z) {
+ Engine.PostCommand({"type": "walk", "entities": [this.entity.id], "x": x, "z": z, "queued": false});
+ return this;
+ },
+
+ destroy: function() {
+ Engine.PostCommand({"type": "delete-entities", "entities": [this.entity.id]});
+ return this;
+ },
+};
diff --git a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
new file mode 100644
index 0000000000..ed57ee6311
--- /dev/null
+++ b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js
@@ -0,0 +1,38 @@
+function EntityCollection(baseAI, entities)
+{
+ this._ai = baseAI;
+ this._entities = entities;
+}
+
+EntityCollection.prototype.ToIdArray = function()
+{
+ var ret = [];
+ for (var id in this._entities)
+ ret.push(+id);
+ return ret;
+};
+
+EntityCollection.prototype.filter = function(callback, thisp)
+{
+ var ret = {};
+ for (var id in this._entities)
+ {
+ var ent = this._entities[id];
+ var val = new Entity(this._ai, ent);
+ if (callback.call(thisp, val, id, this))
+ ret[id] = ent;
+ }
+ return new EntityCollection(this._ai, ret);
+};
+
+EntityCollection.prototype.move = function(x, z)
+{
+ Engine.PostCommand({"type": "walk", "entities": this.ToIdArray(), "x": x, "z": z, "queued": false});
+ return this;
+};
+
+EntityCollection.prototype.destroy = function()
+{
+ Engine.PostCommand({"type": "delete-entities", "entities": this.ToIdArray()});
+ return this;
+};
diff --git a/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js b/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js
index 553b89ba2a..671ae9304f 100644
--- a/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js
+++ b/binaries/data/mods/public/simulation/ai/scaredybot/scaredybot.js
@@ -1,31 +1,27 @@
+Engine.IncludeModule("common-api");
+
function ScaredyBotAI(settings)
{
warn("Constructing ScaredyBotAI for player "+settings.player);
- this.player = settings.player;
+ BaseAI.call(this, settings);
+
this.turn = 0;
this.suicideTurn = 20;
}
-ScaredyBotAI.prototype.HandleMessage = function(state)
-{
-// print("### HandleMessage("+uneval(state)+")\n\n");
-// print(uneval(this)+"\n\n");
+ScaredyBotAI.prototype = new BaseAI();
+ScaredyBotAI.prototype.OnUpdate = function()
+{
if (this.turn == 0)
- {
- Engine.PostCommand({"type": "chat", "message": "Good morning."});
- }
+ this.chat("Good morning.");
if (this.turn == this.suicideTurn)
{
- Engine.PostCommand({"type": "chat", "message": "I quake in my boots! My troops cannot hope to survive against a power such as yours."});
+ this.chat("I quake in my boots! My troops cannot hope to survive against a power such as yours.");
- var myEntities = [];
- for (var ent in state.entities)
- if (state.entities[ent].player == this.player)
- myEntities.push(+ent);
- Engine.PostCommand({"type": "delete-entities", "entities": myEntities});
+ this.entities.filter(function(ent) { return ent.isOwn; }).destroy();
}
this.turn++;
diff --git a/binaries/data/mods/public/simulation/components/AIInterface.js b/binaries/data/mods/public/simulation/components/AIInterface.js
index 6e8ed3971f..8c4573af80 100644
--- a/binaries/data/mods/public/simulation/components/AIInterface.js
+++ b/binaries/data/mods/public/simulation/components/AIInterface.js
@@ -21,6 +21,15 @@ AIInterface.prototype.GetRepresentation = function()
// Reset the event list for the next turn
this.events = [];
+ // Add entity representations
+ state.entities = {};
+ for each (var proxy in Engine.GetComponentsWithInterface(IID_AIProxy))
+ {
+ var rep = proxy.GetRepresentation();
+ if (rep !== null)
+ state.entities[proxy.entity] = rep;
+ }
+
return state;
};
diff --git a/binaries/data/mods/public/simulation/components/AIProxy.js b/binaries/data/mods/public/simulation/components/AIProxy.js
index dedee134f2..3ee14ce428 100644
--- a/binaries/data/mods/public/simulation/components/AIProxy.js
+++ b/binaries/data/mods/public/simulation/components/AIProxy.js
@@ -3,18 +3,161 @@ function AIProxy() {}
AIProxy.prototype.Schema =
"";
+/**
+ * AIProxy passes its entity's state data to AI scripts.
+ *
+ * Efficiency is critical: there can be many thousands of entities,
+ * and the data returned by this component is serialized and copied to
+ * the AI thread every turn, so it can be quite expensive.
+ *
+ * We omit all data that can be derived statically from the template XML
+ * files - the AI scripts can parse the templates themselves.
+ * This violates the component interface abstraction and is potentially
+ * fragile if the template formats change (since both the component code
+ * and the AI will have to be updated in sync), but it's not *that* bad
+ * really and it helps performance significantly.
+ *
+ * We also add an optimisation to avoid copying non-changing values.
+ * The first call to GetRepresentation calls GetFullRepresentation,
+ * which constructs the complete entity state representation.
+ * After that, we simply listen to events from the rest of the gameplay code,
+ * and store the changed data in this.changes.
+ * Properties in this.changes will override those previously returned
+ * from GetRepresentation; if a property isn't overridden then the AI scripts
+ * will keep its old value.
+ *
+ * The event handlers should set this.changes.whatever to exactly the
+ * same as GetFullRepresentation would set.
+ */
+
AIProxy.prototype.Init = function()
{
+ this.changes = null;
+ this.needsFullGet = true;
};
AIProxy.prototype.GetRepresentation = function()
{
- // Currently we'll just return the same data that the GUI uses.
- // Maybe we should add/remove things (or make it more efficient)
- // later.
+ // Return the full representation the first time we're called
+ var ret;
+ if (this.needsFullGet)
+ {
+ ret = this.GetFullRepresentation();
+ this.needsFullGet = false;
+ }
+ else
+ {
+ ret = this.changes;
+ }
- var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
- return cmpGuiInterface.GetEntityState(-1, this.entity);
+ // Initialise changes to null instead of {}, to avoid memory allocations in the
+ // common case where there will be no changes; event handlers should each reset
+ // it to {} if needed
+ this.changes = null;
+
+ return ret;
+};
+
+AIProxy.prototype.OnPositionChanged = function(msg)
+{
+ if (!this.changes)
+ this.changes = {};
+
+ if (msg.inWorld)
+ this.changes.position = [msg.x, msg.z];
+ else
+ this.changes.position = undefined;
+};
+
+AIProxy.prototype.OnHealthChanged = function(msg)
+{
+ if (!this.changes)
+ this.changes = {};
+
+ this.changes.hitpoints = msg.to;
+};
+
+AIProxy.prototype.OnOwnershipChanged = function(msg)
+{
+ if (!this.changes)
+ this.changes = {};
+
+ this.changes.owner = msg.to;
+};
+
+// TODO: event handlers for all the other things
+
+AIProxy.prototype.GetFullRepresentation = function()
+{
+ var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+
+ var ret = {
+ // These properties are constant and won't need to be updated
+ "id": this.entity,
+ "template": cmpTemplateManager.GetCurrentTemplateName(this.entity)
+ }
+
+ var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (cmpPosition)
+ {
+ // Updated by OnPositionChanged
+
+ if (cmpPosition.IsInWorld())
+ {
+ var pos = cmpPosition.GetPosition2D();
+ ret.position = [pos.x, pos.y];
+ }
+ else
+ {
+ ret.position = undefined;
+ }
+ }
+
+ var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+ if (cmpHealth)
+ {
+ // Updated by OnHealthChanged
+ ret.hitpoints = cmpHealth.GetHitpoints();
+ }
+
+ var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (cmpOwnership)
+ {
+ // Updated by OnOwnershipChanged
+ ret.owner = cmpOwnership.GetOwner();
+ }
+
+ var cmpTrainingQueue = Engine.QueryInterface(this.entity, IID_TrainingQueue);
+ if (cmpTrainingQueue)
+ {
+ ret.trainingQueue = cmpTrainingQueue.GetQueue();
+ }
+
+ var cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation);
+ if (cmpFoundation)
+ {
+ ret.foundationProgress = cmpFoundation.GetBuildPercentage();
+ }
+
+ var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
+ if (cmpResourceSupply)
+ {
+ ret.resourceSupplyAmount = cmpResourceSupply.GetCurrentAmount();
+ }
+
+ var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
+ if (cmpResourceGatherer)
+ {
+ ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
+ }
+
+ var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
+ if (cmpGarrisonHolder)
+ {
+ ret.garrisoned = cmpGarrisonHolder.GetEntities();
+ }
+
+ return ret;
};
Engine.RegisterComponentType(IID_AIProxy, "AIProxy", AIProxy);
diff --git a/binaries/data/mods/public/simulation/components/GarrisonHolder.js b/binaries/data/mods/public/simulation/components/GarrisonHolder.js
index 322283dc74..db08aa7594 100644
--- a/binaries/data/mods/public/simulation/components/GarrisonHolder.js
+++ b/binaries/data/mods/public/simulation/components/GarrisonHolder.js
@@ -22,7 +22,7 @@ GarrisonHolder.prototype.Schema =
*/
GarrisonHolder.prototype.Init = function()
{
- //Garrisoned Units
+ // Garrisoned Units
this.entities = [];
this.spaceOccupied = 0;
this.timer = undefined;
diff --git a/binaries/data/mods/public/simulation/components/interfaces/AIProxy.js b/binaries/data/mods/public/simulation/components/interfaces/AIProxy.js
new file mode 100644
index 0000000000..9fec75f69f
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/interfaces/AIProxy.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("AIProxy");
diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp
index ac399cf71a..95b5ab3e00 100644
--- a/source/network/NetServer.cpp
+++ b/source/network/NetServer.cpp
@@ -179,7 +179,7 @@ void CNetServerWorker::Run()
{
// To avoid the need for JS_SetContextThread, we create and use and destroy
// the script interface entirely within this network thread
- m_ScriptInterface = new ScriptInterface("Engine", "Net server");
+ m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime());
while (true)
{
diff --git a/source/network/tests/test_Net.h b/source/network/tests/test_Net.h
index 9090bb8d0c..9555b5337b 100644
--- a/source/network/tests/test_Net.h
+++ b/source/network/tests/test_Net.h
@@ -136,7 +136,7 @@ public:
// This doesn't actually test much, it just runs a very quick multiplayer game
// and prints a load of debug output so you can see if anything funny's going on
- ScriptInterface scriptInterface("Engine");
+ ScriptInterface scriptInterface("Engine", "Test", ScriptInterface::CreateRuntime());
TestStdoutLogger logger;
std::vector clients;
diff --git a/source/network/tests/test_NetMessage.h b/source/network/tests/test_NetMessage.h
index b21e3584a6..f25a2d84ef 100644
--- a/source/network/tests/test_NetMessage.h
+++ b/source/network/tests/test_NetMessage.h
@@ -26,7 +26,7 @@ class TestNetMessage : public CxxTest::TestSuite
public:
void test_sim()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
CScriptValRooted val;
script.Eval("[4]", val);
CSimulationMessage msg(script, 1, 2, 3, val.get());
diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp
index 4dd39c4777..7d1b6fa43f 100644
--- a/source/ps/Replay.cpp
+++ b/source/ps/Replay.cpp
@@ -189,7 +189,7 @@ void CReplayPlayer::Replay()
// std::string hash;
// bool ok = game.GetSimulation2()->ComputeStateHash(hash);
// debug_assert(ok);
-// debug_printf(L"%hs\n", Hexify(hash).c_str());
+// debug_printf(L"%hs", Hexify(hash).c_str());
debug_printf(L"\n");
diff --git a/source/scripting/ScriptingHost.cpp b/source/scripting/ScriptingHost.cpp
index c64668900d..01bf71d24b 100644
--- a/source/scripting/ScriptingHost.cpp
+++ b/source/scripting/ScriptingHost.cpp
@@ -29,7 +29,7 @@
ScriptingHost::ScriptingHost()
{
- m_ScriptInterface = new ScriptInterface("Engine", "GUI");
+ m_ScriptInterface = new ScriptInterface("Engine", "GUI", ScriptInterface::CreateRuntime());
m_Context = m_ScriptInterface->GetContext();
diff --git a/source/scriptinterface/ScriptConversions.cpp b/source/scriptinterface/ScriptConversions.cpp
index 8e291b7796..716497ba0c 100644
--- a/source/scriptinterface/ScriptConversions.cpp
+++ b/source/scriptinterface/ScriptConversions.cpp
@@ -261,3 +261,9 @@ VECTOR(u32)
VECTOR(std::string)
VECTOR(std::wstring)
VECTOR(CScriptValRooted)
+
+class IComponent;
+template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val)
+{
+ return ToJSVal_vector(cx, val);
+}
diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp
index aa209cfa3f..05ac61aa5e 100644
--- a/source/scriptinterface/ScriptInterface.cpp
+++ b/source/scriptinterface/ScriptInterface.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -48,18 +48,94 @@ const int STACK_CHUNK_SIZE = 8192;
////////////////////////////////////////////////////////////////
+class ScriptRuntime
+{
+public:
+ ScriptRuntime() :
+ m_rooter(NULL)
+ {
+ m_rt = JS_NewRuntime(RUNTIME_SIZE);
+ debug_assert(m_rt); // TODO: error handling
+
+#if ENABLE_SCRIPT_PROFILING
+ // Profiler isn't thread-safe, so only enable this on the main thread
+ if (ThreadUtil::IsMainThread())
+ {
+ if (CProfileManager::IsInitialised())
+ {
+ JS_SetExecuteHook(m_rt, jshook_script, this);
+ JS_SetCallHook(m_rt, jshook_function, this);
+ }
+ }
+#endif
+
+ JS_SetExtraGCRoots(m_rt, jshook_trace, this);
+ }
+
+ ~ScriptRuntime()
+ {
+ JS_DestroyRuntime(m_rt);
+ }
+
+ JSRuntime* m_rt;
+ AutoGCRooter* m_rooter;
+
+private:
+
+#if ENABLE_SCRIPT_PROFILING
+ static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure)
+ {
+ if (before)
+ g_Profiler.StartScript("script invocation");
+ else
+ g_Profiler.Stop();
+
+ return closure;
+ }
+
+ static void* jshook_function(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure)
+ {
+ JSFunction* fn = JS_GetFrameFunction(cx, fp);
+ if (before)
+ {
+ if (fn)
+ g_Profiler.StartScript(JS_GetFunctionName(fn));
+ else
+ g_Profiler.StartScript("function invocation");
+ }
+ else
+ g_Profiler.Stop();
+
+ return closure;
+ }
+#endif
+
+ static void jshook_trace(JSTracer* trc, void* data)
+ {
+ ScriptRuntime* m = static_cast(data);
+
+ if (m->m_rooter)
+ m->m_rooter->Trace(trc);
+ }
+};
+
+shared_ptr ScriptInterface::CreateRuntime()
+{
+ return shared_ptr(new ScriptRuntime);
+}
+
+////////////////////////////////////////////////////////////////
+
struct ScriptInterface_impl
{
- ScriptInterface_impl(const char* nativeScopeName, JSContext* cx);
+ ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime);
~ScriptInterface_impl();
void Register(const char* name, JSNative fptr, uintN nargs);
- JSRuntime* m_rt; // NULL if m_cx is shared; non-NULL if we own m_cx
+ shared_ptr m_runtime;
JSContext* m_cx;
JSObject* m_glob; // global scope object
JSObject* m_nativeScope; // native function scope object
-
- AutoGCRooter* m_rooter;
};
namespace
@@ -202,103 +278,41 @@ JSBool Math_random(JSContext* cx, uintN UNUSED(argc), jsval* vp)
} // anonymous namespace
-#if ENABLE_SCRIPT_PROFILING
-static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure)
-{
- if (before)
- g_Profiler.StartScript("script invocation");
- else
- g_Profiler.Stop();
-
- return closure;
-}
-
-static void* jshook_function(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure)
-{
- JSFunction* fn = JS_GetFrameFunction(cx, fp);
- if (before)
- {
- if (fn)
- g_Profiler.StartScript(JS_GetFunctionName(fn));
- else
- g_Profiler.StartScript("function invocation");
- }
- else
- g_Profiler.Stop();
-
- return closure;
-}
-#endif
-
-void jshook_trace(JSTracer* trc, void* data)
-{
- ScriptInterface_impl* m = static_cast(data);
-
- if (m->m_rooter)
- m->m_rooter->Trace(trc);
-}
-
-ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, JSContext* cx) :
- m_rooter(NULL)
+ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime) :
+ m_runtime(runtime)
{
JSBool ok;
- if (cx)
- {
- m_rt = NULL;
- m_cx = cx;
- m_glob = JS_GetGlobalObject(m_cx);
- }
- else
- {
- m_rt = JS_NewRuntime(RUNTIME_SIZE);
- debug_assert(m_rt); // TODO: error handling
+ m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE);
+ debug_assert(m_cx);
-#if ENABLE_SCRIPT_PROFILING
- // Profiler isn't thread-safe, so only enable this on the main thread
- if (ThreadUtil::IsMainThread())
- {
- if (CProfileManager::IsInitialised())
- {
- JS_SetExecuteHook(m_rt, jshook_script, this);
- JS_SetCallHook(m_rt, jshook_function, this);
- }
- }
-#endif
+ // For GC debugging:
+ // JS_SetGCZeal(m_cx, 2);
- m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE);
- debug_assert(m_cx);
+ JS_SetContextPrivate(m_cx, NULL);
- // For GC debugging:
- // JS_SetGCZeal(m_cx, 2);
+ JS_SetErrorReporter(m_cx, ErrorReporter);
- JS_SetContextPrivate(m_cx, NULL);
+ 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)
- JS_SetErrorReporter(m_cx, ErrorReporter);
+ // Enable all the JIT features:
+// | JSOPTION_JIT
+// | JSOPTION_METHODJIT
+// | JSOPTION_PROFILING
+ );
- 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)
+ JS_SetVersion(m_cx, JSVERSION_LATEST);
- // Enable all the JIT features:
-// | JSOPTION_JIT
-// | JSOPTION_METHODJIT
-// | JSOPTION_PROFILING
- );
+ // Threadsafe SpiderMonkey requires that we have a request before doing anything much
+ JS_BeginRequest(m_cx);
- JS_SetVersion(m_cx, JSVERSION_LATEST);
+ m_glob = JS_NewGlobalObject(m_cx, &global_class);
+ ok = JS_InitStandardClasses(m_cx, m_glob);
- JS_SetExtraGCRoots(m_rt, jshook_trace, this);
-
- // Threadsafe SpiderMonkey requires that we have a request before doing anything much
- JS_BeginRequest(m_cx);
-
- m_glob = JS_NewGlobalObject(m_cx, &global_class);
- ok = JS_InitStandardClasses(m_cx, m_glob);
-
- JS_DefineProperty(m_cx, m_glob, "global", OBJECT_TO_JSVAL(m_glob), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
- | JSPROP_PERMANENT);
- }
+ JS_DefineProperty(m_cx, m_glob, "global", OBJECT_TO_JSVAL(m_glob), NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
+ | JSPROP_PERMANENT);
m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
| JSPROP_PERMANENT);
@@ -311,12 +325,8 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, JSContex
ScriptInterface_impl::~ScriptInterface_impl()
{
- if (m_rt) // if we own the context:
- {
- JS_EndRequest(m_cx);
- JS_DestroyContext(m_cx);
- JS_DestroyRuntime(m_rt);
- }
+ JS_EndRequest(m_cx);
+ JS_DestroyContext(m_cx);
}
void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs)
@@ -324,8 +334,8 @@ void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs
JS_DefineFunction(m_cx, m_nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
}
-ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName) :
- m(new ScriptInterface_impl(nativeScopeName, NULL))
+ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime) :
+ m(new ScriptInterface_impl(nativeScopeName, runtime))
{
// Profiler stats table isn't thread-safe, so only enable this on the main thread
if (ThreadUtil::IsMainThread())
@@ -391,14 +401,13 @@ JSContext* ScriptInterface::GetContext() const
JSRuntime* ScriptInterface::GetRuntime() const
{
- return m->m_rt;
+ return m->m_runtime->m_rt;
}
AutoGCRooter* ScriptInterface::ReplaceAutoGCRooter(AutoGCRooter* rooter)
{
- debug_assert(m->m_rt); // this class must own the runtime, else the rooter won't work
- AutoGCRooter* ret = m->m_rooter;
- m->m_rooter = rooter;
+ AutoGCRooter* ret = m->m_runtime->m_rooter;
+ m->m_runtime->m_rooter = rooter;
return ret;
}
@@ -821,9 +830,9 @@ void ScriptInterface::DumpHeap()
#ifdef DEBUG
JS_DumpHeap(m->m_cx, stderr, NULL, 0, NULL, (size_t)-1, NULL);
#endif
- fprintf(stderr, "# Bytes allocated: %d\n", JS_GetGCParameter(m->m_rt, JSGC_BYTES));
+ fprintf(stderr, "# Bytes allocated: %d\n", JS_GetGCParameter(GetRuntime(), JSGC_BYTES));
JS_GC(m->m_cx);
- fprintf(stderr, "# Bytes allocated after GC: %d\n", JS_GetGCParameter(m->m_rt, JSGC_BYTES));
+ fprintf(stderr, "# Bytes allocated after GC: %d\n", JS_GetGCParameter(GetRuntime(), JSGC_BYTES));
}
void ScriptInterface::MaybeGC()
@@ -948,3 +957,36 @@ jsval ScriptInterface::CloneValueFromOtherContext(ScriptInterface& otherContext,
ValueCloner cloner(otherContext, *this);
return cloner.GetOrClone(val);
}
+
+ScriptInterface::StructuredClone::StructuredClone() :
+ m_Data(NULL), m_Size(0)
+{
+}
+
+ScriptInterface::StructuredClone::~StructuredClone()
+{
+ if (m_Data)
+ js_free(m_Data);
+}
+
+shared_ptr ScriptInterface::WriteStructuredClone(jsval v)
+{
+ uint64* data = NULL;
+ size_t nbytes = 0;
+ if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes))
+ return shared_ptr();
+ // TODO: should we have better error handling?
+ // Currently we'll probably continue and then crash in ReadStructuredClone
+
+ shared_ptr ret (new StructuredClone);
+ ret->m_Data = data;
+ ret->m_Size = nbytes;
+ return ret;
+}
+
+jsval ScriptInterface::ReadStructuredClone(const shared_ptr& ptr)
+{
+ jsval ret = JSVAL_VOID;
+ JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, &ret);
+ return ret;
+}
diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h
index 6fc6471f22..6116fccb23 100644
--- a/source/scriptinterface/ScriptInterface.h
+++ b/source/scriptinterface/ScriptInterface.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -48,6 +48,8 @@ namespace boost { class rand48; }
struct ScriptInterface_impl;
+class ScriptRuntime;
+
/**
* Abstraction around a SpiderMonkey JSContext.
*
@@ -60,14 +62,20 @@ class ScriptInterface
{
public:
+ /**
+ * Returns a runtime, which can used to initialise any number of
+ * ScriptInterfaces contexts. Values created in one context may be used
+ * in any other context from the same runtime (but not any other runtime).
+ * Each runtime should only ever be used on a single thread.
+ */
+ static shared_ptr CreateRuntime();
+
/**
* Constructor.
* @param nativeScopeName Name of global object that functions (via RegisterFunction) will
* be placed into, as a scoping mechanism; typically "Engine"
- * @param cx NULL if the object should create and manage its own context; otherwise
- * an existing context which it will share
*/
- ScriptInterface(const char* nativeScopeName, const char* debugName = "Unknown");
+ ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime);
~ScriptInterface();
@@ -263,6 +271,26 @@ public:
void MaybeGC();
+ /**
+ * Structured clones are a way to serialize 'simple' JS values into a buffer
+ * that can safely be passed between contexts and runtimes and threads.
+ * A StructuredClone can be stored and read multiple times if desired.
+ * We wrap them in shared_ptr so memory management is automatic and
+ * thread-safe.
+ */
+ class StructuredClone
+ {
+ NONCOPYABLE(StructuredClone);
+ public:
+ StructuredClone();
+ ~StructuredClone();
+ uint64* m_Data;
+ size_t m_Size;
+ };
+
+ shared_ptr WriteStructuredClone(jsval v);
+ jsval ReadStructuredClone(const shared_ptr& ptr);
+
private:
bool CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret);
bool Eval_(const char* code, jsval& ret);
diff --git a/source/scriptinterface/ScriptVal.h b/source/scriptinterface/ScriptVal.h
index 0efa57e322..d992d43d90 100644
--- a/source/scriptinterface/ScriptVal.h
+++ b/source/scriptinterface/ScriptVal.h
@@ -40,7 +40,7 @@ public:
/**
* Returns whether the value is JSVAL_VOID.
*/
- bool undefined() const { return JSVAL_IS_VOID(m_Val); }
+ bool undefined() const { return JSVAL_IS_VOID(m_Val) ? true : false; }
private:
jsval m_Val;
diff --git a/source/scriptinterface/tests/test_ScriptConversions.h b/source/scriptinterface/tests/test_ScriptConversions.h
index 3d2827d8a0..99cded81ad 100644
--- a/source/scriptinterface/tests/test_ScriptConversions.h
+++ b/source/scriptinterface/tests/test_ScriptConversions.h
@@ -30,7 +30,7 @@ class TestScriptConversions : public CxxTest::TestSuite
template
void convert_to(const T& value, const std::string& expected)
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
JSContext* cx = script.GetContext();
jsval v1 = ScriptInterface::ToJSVal(cx, value);
@@ -46,7 +46,7 @@ class TestScriptConversions : public CxxTest::TestSuite
template
void roundtrip(const T& value, const char* expected)
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
JSContext* cx = script.GetContext();
jsval v1 = ScriptInterface::ToJSVal(cx, value);
@@ -121,7 +121,7 @@ public:
void test_integers()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
JSContext* cx = script.GetContext();
TS_ASSERT(JSVAL_IS_INT(ScriptInterface::ToJSVal(cx, 0)));
@@ -144,7 +144,7 @@ public:
roundtrip(-INFINITY, "-Infinity");
convert_to(NAN, "NaN"); // can't use roundtrip since nan != nan
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
JSContext* cx = script.GetContext();
float f = 0;
diff --git a/source/scriptinterface/tests/test_ScriptInterface.h b/source/scriptinterface/tests/test_ScriptInterface.h
index 4008631390..e9ff869518 100644
--- a/source/scriptinterface/tests/test_ScriptInterface.h
+++ b/source/scriptinterface/tests/test_ScriptInterface.h
@@ -29,7 +29,7 @@ class TestScriptInterface : public CxxTest::TestSuite
public:
void test_loadscript_basic()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
TestLogger logger;
TS_ASSERT(script.LoadScript(L"test.js", L"var x = 1+1;"));
TS_ASSERT_WSTR_NOT_CONTAINS(logger.GetOutput(), L"JavaScript error");
@@ -38,7 +38,7 @@ public:
void test_loadscript_error()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
TestLogger logger;
TS_ASSERT(!script.LoadScript(L"test.js", L"1+"));
TS_ASSERT_WSTR_CONTAINS(logger.GetOutput(), L"JavaScript error: test.js line 1\nSyntaxError: syntax error");
@@ -46,7 +46,7 @@ public:
void test_loadscript_strict_warning()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
TestLogger logger;
TS_ASSERT(script.LoadScript(L"test.js", L"1+1;"));
TS_ASSERT_WSTR_CONTAINS(logger.GetOutput(), L"JavaScript warning: test.js line 1\nuseless expression");
@@ -54,7 +54,7 @@ public:
void test_loadscript_strict_error()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
TestLogger logger;
TS_ASSERT(!script.LoadScript(L"test.js", L"with(1){}"));
TS_ASSERT_WSTR_CONTAINS(logger.GetOutput(), L"JavaScript error: test.js line 1\nSyntaxError: strict mode code may not contain \'with\' statements");
@@ -62,8 +62,8 @@ public:
void test_clone_basic()
{
- ScriptInterface script1("Test");
- ScriptInterface script2("Test");
+ ScriptInterface script1("Test", "Test", ScriptInterface::CreateRuntime());
+ ScriptInterface script2("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj1;
TS_ASSERT(script1.Eval("({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})", obj1));
@@ -79,8 +79,8 @@ public:
{
// The tests should be run with JS_SetGCZeal so this can try to find GC bugs
- ScriptInterface script1("Test");
- ScriptInterface script2("Test");
+ ScriptInterface script1("Test", "Test", ScriptInterface::CreateRuntime());
+ ScriptInterface script2("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj1;
TS_ASSERT(script1.Eval("var s = '?'; var v = ({get x() { return 123 }, 'y': {'w':{get z() { delete v.y; delete v.n; v = null; s += s; return 4 }}}, 'n': 100}); v", obj1));
@@ -94,8 +94,8 @@ public:
void test_clone_cyclic()
{
- ScriptInterface script1("Test");
- ScriptInterface script2("Test");
+ ScriptInterface script1("Test", "Test", ScriptInterface::CreateRuntime());
+ ScriptInterface script2("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj1;
TS_ASSERT(script1.Eval("var x = []; x[0] = x; ({'a': x, 'b': x})", obj1));
@@ -109,7 +109,7 @@ public:
void test_random()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
double d1, d2;
TS_ASSERT(script.Eval("Math.random()", d1));
@@ -129,7 +129,7 @@ public:
void test_json()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::string input = "({'x':1,'z':[2,'3\\u263A\\ud800'],\"y\":true})";
CScriptValRooted val;
diff --git a/source/scriptinterface/tests/test_ScriptVal.h b/source/scriptinterface/tests/test_ScriptVal.h
index 915d4b6200..7d1a7271c4 100644
--- a/source/scriptinterface/tests/test_ScriptVal.h
+++ b/source/scriptinterface/tests/test_ScriptVal.h
@@ -27,7 +27,7 @@ class TestScriptVal : public CxxTest::TestSuite
public:
void test_rooting()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
JSContext* cx = script.GetContext();
JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
index 00af7da37a..cda8b3febd 100644
--- a/source/simulation2/TypeList.h
+++ b/source/simulation2/TypeList.h
@@ -63,9 +63,6 @@ COMPONENT(AIInterfaceScripted)
INTERFACE(AIManager)
COMPONENT(AIManager)
-INTERFACE(AIProxy)
-COMPONENT(AIProxyScripted)
-
INTERFACE(CommandQueue)
COMPONENT(CommandQueue)
diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp
index 6b67f1e36b..fbe61791f6 100644
--- a/source/simulation2/components/CCmpAIManager.cpp
+++ b/source/simulation2/components/CCmpAIManager.cpp
@@ -24,7 +24,6 @@
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "simulation2/components/ICmpAIInterface.h"
-#include "simulation2/components/ICmpAIProxy.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/serialization/DebugSerializer.h"
@@ -50,6 +49,8 @@
* will block until it's actually completed, so the rest of the engine should avoid
* reading it for as long as possible.
*
+ * JS values are passed between the game and AI threads using ScriptInterface::StructuredClone.
+ *
* TODO: actually the thread isn't implemented yet, because performance hasn't been
* sufficiently problematic to justify the complexity yet, but the CAIWorker interface
* is designed to hopefully support threading when we want it.
@@ -58,37 +59,157 @@
class CAIWorker
{
private:
- struct SAIPlayer
+ class CAIPlayer
{
- std::wstring aiName;
- player_id_t player;
- CScriptValRooted obj;
- };
+ NONCOPYABLE(CAIPlayer);
+ public:
+ CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player,
+ const shared_ptr& runtime, boost::rand48& rng) :
+ m_Worker(worker), m_AIName(aiName), m_Player(player), m_ScriptInterface("Engine", "AI", runtime)
+ {
+ m_ScriptInterface.SetCallbackData(static_cast (this));
- struct SCommands
- {
- player_id_t player;
- std::vector commands;
+ m_ScriptInterface.ReplaceNondeterministicFunctions(rng);
+
+ m_ScriptInterface.RegisterFunction("IncludeModule");
+ m_ScriptInterface.RegisterFunction("PostCommand");
+ }
+
+ ~CAIPlayer()
+ {
+ // Clean up rooted objects before destroying their script context
+ m_Obj = CScriptValRooted();
+ }
+
+ static void IncludeModule(void* cbdata, std::wstring name)
+ {
+ CAIPlayer* self = static_cast (cbdata);
+
+ self->LoadScripts(name);
+ }
+
+ static void PostCommand(void* cbdata, CScriptValRooted cmd)
+ {
+ CAIPlayer* self = static_cast (cbdata);
+
+ self->m_Commands.push_back(self->m_ScriptInterface.WriteStructuredClone(cmd.get()));
+ }
+
+ bool LoadScripts(const std::wstring& moduleName)
+ {
+ // Ignore modules that are already loaded
+ if (m_LoadedModules.find(moduleName) != m_LoadedModules.end())
+ return true;
+
+ // Mark this as loaded, to prevent it recursively loading itself
+ m_LoadedModules.insert(moduleName);
+
+ // Load and execute *.js
+ VfsPaths pathnames;
+ fs_util::GetPathnames(g_VFS, L"simulation/ai/" + moduleName + L"/", L"*.js", pathnames);
+ for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
+ {
+ if (!m_ScriptInterface.LoadGlobalScriptFile(*it))
+ {
+ LOGERROR(L"Failed to load script %ls", it->string().c_str());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool Initialise(bool callConstructor)
+ {
+ if (!LoadScripts(m_AIName))
+ return false;
+
+ std::wstring path = L"simulation/ai/" + m_AIName + L"/data.json";
+ CScriptValRooted metadata = m_Worker.LoadMetadata(path);
+ if (metadata.uninitialised())
+ {
+ LOGERROR(L"Failed to create AI player: can't find %ls", path.c_str());
+ return false;
+ }
+
+ // Get the constructor name from the metadata
+ std::string constructor;
+ if (!m_ScriptInterface.GetProperty(metadata.get(), "constructor", constructor))
+ {
+ LOGERROR(L"Failed to create AI player: %ls: missing 'constructor'", path.c_str());
+ return false;
+ }
+
+ // Get the constructor function from the loaded scripts
+ CScriptVal ctor;
+ if (!m_ScriptInterface.GetProperty(m_ScriptInterface.GetGlobalObject(), constructor.c_str(), ctor)
+ || ctor.undefined())
+ {
+ LOGERROR(L"Failed to create AI player: %ls: can't find constructor '%hs'", path.c_str(), constructor.c_str());
+ return false;
+ }
+
+ CScriptVal obj;
+
+ if (callConstructor)
+ {
+ // Set up the data to pass as the constructor argument
+ CScriptVal settings;
+ m_ScriptInterface.Eval(L"({})", settings);
+ m_ScriptInterface.SetProperty(settings.get(), "player", m_Player, false);
+ m_ScriptInterface.SetProperty(settings.get(), "templates", m_Worker.m_EntityTemplates, false);
+
+ obj = m_ScriptInterface.CallConstructor(ctor.get(), settings.get());
+ }
+ else
+ {
+ // For deserialization, we want to create the object with the correct prototype
+ // but don't want to actually run the constructor again
+ obj = m_ScriptInterface.NewObjectFromConstructor(ctor.get());
+ }
+
+ if (obj.undefined())
+ {
+ LOGERROR(L"Failed to create AI player: %ls: error calling constructor '%hs'", path.c_str(), constructor.c_str());
+ return false;
+ }
+
+ m_Obj = CScriptValRooted(m_ScriptInterface.GetContext(), obj);
+ return true;
+ }
+
+ void Run(CScriptVal state)
+ {
+ m_Commands.clear();
+ m_ScriptInterface.CallFunctionVoid(m_Obj.get(), "HandleMessage", state);
+ }
+
+ CAIWorker& m_Worker;
+ std::wstring m_AIName;
+ player_id_t m_Player;
+
+ ScriptInterface m_ScriptInterface;
+ CScriptValRooted m_Obj;
+ std::vector > m_Commands;
+ std::set m_LoadedModules;
};
public:
- struct SReturnedCommands
+ struct SCommandSets
{
player_id_t player;
- std::vector commands;
+ std::vector > commands;
};
CAIWorker() :
- m_ScriptInterface("Engine", "AI"),
- m_CommandsComputed(true),
- m_CurrentlyComputingPlayer(-1)
+ m_ScriptRuntime(ScriptInterface::CreateRuntime()),
+ m_ScriptInterface("Engine", "AI", m_ScriptRuntime),
+ m_CommandsComputed(true)
{
m_ScriptInterface.SetCallbackData(static_cast (this));
// TODO: ought to seed the RNG (in a network-synchronised way) before we use it
m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG);
-
- m_ScriptInterface.RegisterFunction("PostCommand");
}
~CAIWorker()
@@ -97,70 +218,20 @@ public:
m_EntityTemplates = CScriptValRooted();
m_PlayerMetadata.clear();
m_Players.clear();
- m_Commands.clear();
}
bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor)
{
- std::wstring path = L"simulation/ai/" + aiName + L"/data.json";
- CScriptValRooted metadata = LoadPlayerFiles(aiName, path);
- if (metadata.uninitialised())
- {
- LOGERROR(L"Failed to create AI player: can't find %ls", path.c_str());
+ shared_ptr ai(new CAIPlayer(*this, aiName, player, m_ScriptRuntime, m_RNG));
+ if (!ai->Initialise(callConstructor))
return false;
- }
- // Get the constructor name from the metadata
- std::string constructor;
- if (!m_ScriptInterface.GetProperty(metadata.get(), "constructor", constructor))
- {
- LOGERROR(L"Failed to create AI player: %ls: missing 'constructor'", path.c_str());
- return false;
- }
-
- // Get the constructor function from the loaded scripts
- CScriptVal ctor;
- if (!m_ScriptInterface.GetProperty(m_ScriptInterface.GetGlobalObject(), constructor.c_str(), ctor)
- || ctor.undefined())
- {
- LOGERROR(L"Failed to create AI player: %ls: can't find constructor '%hs'", path.c_str(), constructor.c_str());
- return false;
- }
-
- CScriptVal obj;
-
- if (callConstructor)
- {
- // Set up the data to pass as the constructor argument
- CScriptVal settings;
- m_ScriptInterface.Eval(L"({})", settings);
- m_ScriptInterface.SetProperty(settings.get(), "player", player, false);
- m_ScriptInterface.SetProperty(settings.get(), "templates", m_EntityTemplates, false);
-
- obj = m_ScriptInterface.CallConstructor(ctor.get(), settings.get());
- }
- else
- {
- // For deserialization, we want to create the object with the correct prototype
- // but don't want to actually run the constructor again
- obj = m_ScriptInterface.NewObjectFromConstructor(ctor.get());
- }
-
- if (obj.undefined())
- {
- LOGERROR(L"Failed to create AI player: %ls: error calling constructor '%hs'", path.c_str(), constructor.c_str());
- return false;
- }
-
- SAIPlayer ai;
- ai.aiName = aiName;
- ai.player = player;
- ai.obj = CScriptValRooted(m_ScriptInterface.GetContext(), obj);
m_Players.push_back(ai);
+
return true;
}
- void StartComputation(const std::string& gameState)
+ void StartComputation(const shared_ptr& gameState)
{
debug_assert(m_CommandsComputed);
@@ -178,26 +249,16 @@ public:
}
}
- void GetCommands(std::vector& commands)
+ void GetCommands(std::vector& commands)
{
WaitToFinishComputation();
commands.clear();
- commands.resize(m_Commands.size());
- for (size_t i = 0; i < m_Commands.size(); ++i)
+ commands.resize(m_Players.size());
+ for (size_t i = 0; i < m_Players.size(); ++i)
{
- commands[i].player = m_Commands[i].player;
- commands[i].commands.resize(m_Commands[i].commands.size());
- for (size_t j = 0; j < m_Commands[i].commands.size(); ++j)
- {
- // Serialize the returned command, so that it's safe to transfer
- // across threads (in the future when we actually run AI in a
- // background thread)
- std::stringstream stream;
- CStdSerializer serializer(m_ScriptInterface, stream);
- serializer.ScriptVal("command", m_Commands[i].commands[j]);
- commands[i].commands[j] = stream.str();
- }
+ commands[i].player = m_Players[i]->m_Player;
+ commands[i].commands = m_Players[i]->m_Commands;
}
}
@@ -239,17 +300,16 @@ public:
for (size_t i = 0; i < m_Players.size(); ++i)
{
- serializer.String("name", m_Players[i].aiName, 0, 256);
- serializer.NumberI32_Unbounded("player", m_Players[i].player);
- serializer.ScriptVal("data", m_Players[i].obj);
- }
+ serializer.String("name", m_Players[i]->m_AIName, 0, 256);
+ serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player);
+ serializer.ScriptVal("data", m_Players[i]->m_Obj);
- serializer.NumberU32_Unbounded("num ai commands", m_Commands.size());
-
- for (size_t i = 0; i < m_Commands.size(); ++i)
- {
- serializer.NumberI32_Unbounded("player", m_Commands[i].player);
- SerializeVector()(serializer, "commands", m_Commands[i].commands);
+ serializer.NumberU32_Unbounded("num commands", m_Players[i]->m_Commands.size());
+ for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j)
+ {
+ CScriptVal val = m_ScriptInterface.ReadStructuredClone(m_Players[i]->m_Commands[j]);
+ serializer.ScriptVal("command", val);
+ }
}
}
@@ -261,7 +321,6 @@ public:
m_PlayerMetadata.clear();
m_Players.clear();
- m_Commands.clear();
uint32_t numAis;
deserializer.NumberU32_Unbounded("num ais", numAis);
@@ -277,37 +336,27 @@ public:
// Use ScriptObjectAppend so we don't lose the carefully-constructed
// prototype/parent of this object
- deserializer.ScriptObjectAppend("data", m_Players.back().obj.getRef());
- }
+ deserializer.ScriptObjectAppend("data", m_Players.back()->m_Obj.getRef());
- uint32_t numCommands;
- deserializer.NumberU32_Unbounded("num ai commands", numCommands);
-
- m_Commands.resize(numCommands);
- for (size_t i = 0; i < numCommands; ++i)
- {
- deserializer.NumberI32_Unbounded("player", m_Commands[i].player);
- SerializeVector()(deserializer, "commands", m_Commands[i].commands);
+ uint32_t numCommands;
+ deserializer.NumberU32_Unbounded("num commands", numCommands);
+ m_Players.back()->m_Commands.reserve(numCommands);
+ for (size_t j = 0; j < numCommands; ++j)
+ {
+ CScriptVal val;
+ deserializer.ScriptVal("command", val);
+ m_Players.back()->m_Commands.push_back(m_ScriptInterface.WriteStructuredClone(val.get()));
+ }
}
}
private:
- CScriptValRooted LoadPlayerFiles(const std::wstring& aiName, const std::wstring& path)
+ CScriptValRooted LoadMetadata(const std::wstring& path)
{
if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end())
{
// Load and cache the AI player metadata
m_PlayerMetadata[path] = m_ScriptInterface.ReadJSONFile(path);
-
- // TODO: includes
-
- // Load and execute *.js
- VfsPaths pathnames;
- fs_util::GetPathnames(g_VFS, L"simulation/ai/" + aiName + L"/", L"*.js", pathnames);
- for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
- {
- m_ScriptInterface.LoadGlobalScriptFile(*it);
- }
}
return m_PlayerMetadata[path];
@@ -317,55 +366,30 @@ private:
{
PROFILE("AI compute");
- m_Commands.clear();
-
// Deserialize the game state, to pass to the AI's HandleMessage
- CScriptVal state;
+ CScriptVal state = m_ScriptInterface.ReadStructuredClone(m_GameState);
- {
-// TIMER(L"deserialize AI game state");
- std::stringstream stream(m_GameState);
- CStdDeserializer deserializer(m_ScriptInterface, stream);
- deserializer.ScriptVal("state", state);
- }
-
- m_ScriptInterface.FreezeObject(state.get(), true);
-
- m_Commands.resize(m_Players.size());
+ // It would be nice to do
+ // m_ScriptInterface.FreezeObject(state.get(), true);
+ // to prevent AI scripts accidentally modifying the state and
+ // 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_Commands[i].player = m_Players[i].player;
-
- m_CurrentlyComputingPlayer = i;
- m_ScriptInterface.CallFunctionVoid(m_Players[i].obj.get(), "HandleMessage", state);
- // (This script will probably call PostCommand)
- }
-
- m_CurrentlyComputingPlayer = -1;
- }
-
- static void PostCommand(void* cbdata, CScriptValRooted cmd)
- {
- CAIWorker* self = static_cast (cbdata);
-
- debug_assert(self->m_CurrentlyComputingPlayer >= 0); // called outside of PerformComputation somehow
-
- self->m_Commands[self->m_CurrentlyComputingPlayer].commands.push_back(cmd);
+ m_Players[i]->Run(state);
}
+ shared_ptr m_ScriptRuntime;
ScriptInterface m_ScriptInterface;
boost::rand48 m_RNG;
CScriptValRooted m_EntityTemplates;
std::map m_PlayerMetadata;
- std::vector m_Players;
+ std::vector > m_Players; // use shared_ptr just to avoid copying
- std::string m_GameState;
+ shared_ptr m_GameState;
bool m_CommandsComputed;
- std::vector m_Commands;
- int m_CurrentlyComputingPlayer; // used so PostCommand knows what player the command is for
};
@@ -424,45 +448,17 @@ public:
CmpPtr cmpAIInterface(GetSimContext(), SYSTEM_ENTITY);
debug_assert(!cmpAIInterface.null());
- // Get most of the game state from AIInterface
+ // Get the game state from AIInterface
CScriptVal state = cmpAIInterface->GetRepresentation();
- // Get entity state from each entity:
-
- CScriptVal entities;
- scriptInterface.Eval(L"({})", entities);
-
- const std::map& ents = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_AIProxy);
-
- for (std::map::const_iterator it = ents.begin(); it != ents.end(); ++it)
- {
- // Skip local entities
- if (ENTITY_IS_LOCAL(it->first))
- continue;
-
- CScriptVal rep = static_cast(it->second)->GetRepresentation();
-
- scriptInterface.SetPropertyInt(entities.get(), it->first, rep, true);
- }
-
- // Add the entities state into the object returned by AIInterface
- scriptInterface.SetProperty(state.get(), "entities", entities, true);
-
- // Serialize the state representation, so that it's safe to transfer
- // across threads (in the future when we actually run AI in a
- // background thread)
- std::stringstream stream;
- CStdSerializer serializer(scriptInterface, stream);
- serializer.ScriptVal("state", state);
-
- m_Worker.StartComputation(stream.str());
+ m_Worker.StartComputation(scriptInterface.WriteStructuredClone(state.get()));
}
virtual void PushCommands()
{
ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface();
- std::vector commands;
+ std::vector commands;
m_Worker.GetCommands(commands);
CmpPtr cmpCommandQueue(GetSimContext(), SYSTEM_ENTITY);
@@ -473,11 +469,8 @@ public:
{
for (size_t j = 0; j < commands[i].commands.size(); ++j)
{
- std::stringstream stream(commands[i].commands[j]);
- CStdDeserializer deserializer(scriptInterface, stream);
- CScriptVal cmd;
- deserializer.ScriptVal("command", cmd);
- cmpCommandQueue->PushLocalCommand(commands[i].player, cmd);
+ cmpCommandQueue->PushLocalCommand(commands[i].player,
+ scriptInterface.ReadStructuredClone(commands[i].commands[j]));
}
}
}
diff --git a/source/simulation2/components/CCmpTerrain.cpp b/source/simulation2/components/CCmpTerrain.cpp
index a5f8bdc883..b4977f68d6 100644
--- a/source/simulation2/components/CCmpTerrain.cpp
+++ b/source/simulation2/components/CCmpTerrain.cpp
@@ -69,6 +69,8 @@ public:
virtual entity_pos_t GetGroundLevel(entity_pos_t x, entity_pos_t z)
{
+ // TODO: this can crash if the terrain heightmap isn't initialised yet
+
return m_Terrain->GetExactGroundLevelFixed(x, z);
}
diff --git a/source/simulation2/components/ICmpAIProxy.cpp b/source/simulation2/components/ICmpAIProxy.cpp
deleted file mode 100644
index 20707ea026..0000000000
--- a/source/simulation2/components/ICmpAIProxy.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Copyright (C) 2011 Wildfire Games.
- * This file is part of 0 A.D.
- *
- * 0 A.D. is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * 0 A.D. is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with 0 A.D. If not, see .
- */
-
-#include "precompiled.h"
-
-#include "ICmpAIProxy.h"
-
-#include "simulation2/system/InterfaceScripted.h"
-#include "simulation2/scripting/ScriptComponent.h"
-
-BEGIN_INTERFACE_WRAPPER(AIProxy)
-END_INTERFACE_WRAPPER(AIProxy)
-
-class CCmpAIProxyScripted : public ICmpAIProxy
-{
-public:
- DEFAULT_SCRIPT_WRAPPER(AIProxyScripted)
-
- virtual CScriptVal GetRepresentation()
- {
- return m_Script.Call ("GetRepresentation");
- }
-};
-
-REGISTER_COMPONENT_SCRIPT_WRAPPER(AIProxyScripted)
diff --git a/source/simulation2/components/ICmpAIProxy.h b/source/simulation2/components/ICmpAIProxy.h
deleted file mode 100644
index cc570c8c80..0000000000
--- a/source/simulation2/components/ICmpAIProxy.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Copyright (C) 2011 Wildfire Games.
- * This file is part of 0 A.D.
- *
- * 0 A.D. is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * 0 A.D. is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with 0 A.D. If not, see .
- */
-
-#ifndef INCLUDED_ICMPAIPROXY
-#define INCLUDED_ICMPAIPROXY
-
-#include "simulation2/system/Interface.h"
-
-class ICmpAIProxy : public IComponent
-{
-public:
- /**
- * Returns a script object that represents the current entity state,
- * to be passed to AI scripts.
- */
- virtual CScriptVal GetRepresentation() = 0;
-
- DECLARE_INTERFACE_TYPE(AIProxy)
-};
-
-#endif // INCLUDED_ICMPAIPROXY
diff --git a/source/simulation2/components/ICmpPosition.cpp b/source/simulation2/components/ICmpPosition.cpp
index e0da2a8224..a5d2d651d7 100644
--- a/source/simulation2/components/ICmpPosition.cpp
+++ b/source/simulation2/components/ICmpPosition.cpp
@@ -30,6 +30,7 @@ DEFINE_INTERFACE_METHOD_1("SetHeightOffset", void, ICmpPosition, SetHeightOffset
DEFINE_INTERFACE_METHOD_0("GetHeightOffset", entity_pos_t, ICmpPosition, GetHeightOffset)
DEFINE_INTERFACE_METHOD_0("IsFloating", bool, ICmpPosition, IsFloating)
DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector3D, ICmpPosition, GetPosition)
+DEFINE_INTERFACE_METHOD_0("GetPosition2D", CFixedVector2D, ICmpPosition, GetPosition2D)
DEFINE_INTERFACE_METHOD_1("SetYRotation", void, ICmpPosition, SetYRotation, entity_angle_t)
DEFINE_INTERFACE_METHOD_2("SetXZRotation", void, ICmpPosition, SetXZRotation, entity_angle_t, entity_angle_t)
DEFINE_INTERFACE_METHOD_0("GetRotation", CFixedVector3D, ICmpPosition, GetRotation)
diff --git a/source/simulation2/scripting/EngineScriptConversions.cpp b/source/simulation2/scripting/EngineScriptConversions.cpp
index 4ce007fcdf..9378bcd980 100644
--- a/source/simulation2/scripting/EngineScriptConversions.cpp
+++ b/source/simulation2/scripting/EngineScriptConversions.cpp
@@ -192,3 +192,18 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval
return true;
}
+
+template<> jsval ScriptInterface::ToJSVal(JSContext* cx, const CFixedVector2D& val)
+{
+ JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
+ if (!obj)
+ return JSVAL_VOID;
+
+ jsval x = ToJSVal(cx, val.X);
+ jsval y = ToJSVal(cx, val.Y);
+
+ JS_SetProperty(cx, obj, "x", &x);
+ JS_SetProperty(cx, obj, "y", &y);
+
+ return OBJECT_TO_JSVAL(obj);
+}
diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp
index 8491ceb34d..ccbf008030 100644
--- a/source/simulation2/scripting/MessageTypeConversions.cpp
+++ b/source/simulation2/scripting/MessageTypeConversions.cpp
@@ -172,14 +172,26 @@ CMessage* CMessageOwnershipChanged::FromJSVal(ScriptInterface& scriptInterface,
////////////////////////////////
-jsval CMessagePositionChanged::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
+jsval CMessagePositionChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
- return JSVAL_VOID;
+ TOJSVAL_SETUP();
+ SET_MSG_PROPERTY(entity);
+ SET_MSG_PROPERTY(inWorld);
+ SET_MSG_PROPERTY(x);
+ SET_MSG_PROPERTY(z);
+ SET_MSG_PROPERTY(a);
+ return OBJECT_TO_JSVAL(obj);
}
-CMessage* CMessagePositionChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
+CMessage* CMessagePositionChanged::FromJSVal(ScriptInterface& scriptInterface, jsval val)
{
- return NULL;
+ FROMJSVAL_SETUP();
+ GET_MSG_PROPERTY(entity_id_t, entity);
+ GET_MSG_PROPERTY(bool, inWorld);
+ GET_MSG_PROPERTY(entity_pos_t, x);
+ GET_MSG_PROPERTY(entity_pos_t, z);
+ GET_MSG_PROPERTY(entity_angle_t, a);
+ return new CMessagePositionChanged(entity, inWorld, x, z, a);
}
////////////////////////////////
diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp
index ac05518109..d1eb4c1acc 100644
--- a/source/simulation2/system/ComponentManager.cpp
+++ b/source/simulation2/system/ComponentManager.cpp
@@ -52,7 +52,8 @@ public:
};
CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFunctions) :
- m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine", "Simulation"),
+ m_NextScriptComponentTypeId(CID__LastNative),
+ m_ScriptInterface("Engine", "Simulation", ScriptInterface::CreateRuntime()),
m_SimContext(context), m_CurrentlyHotloading(false)
{
context.SetComponentManager(this);
@@ -72,6 +73,7 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
m_ScriptInterface.RegisterFunction ("RegisterGlobal");
m_ScriptInterface.RegisterFunction ("QueryInterface");
m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface");
+ m_ScriptInterface.RegisterFunction, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface");
m_ScriptInterface.RegisterFunction ("PostMessage");
m_ScriptInterface.RegisterFunction ("BroadcastMessage");
m_ScriptInterface.RegisterFunction ("AddEntity");
@@ -349,7 +351,18 @@ std::vector CComponentManager::Script_GetEntitiesWithInterface(void* cbdata
std::vector ret;
const std::map& ents = componentManager->GetEntitiesWithInterface(iid);
for (std::map::const_iterator it = ents.begin(); it != ents.end(); ++it)
- ret.push_back(it->first);
+ ret.push_back(it->first); // TODO: maybe we should exclude local entities
+ return ret;
+}
+
+std::vector CComponentManager::Script_GetComponentsWithInterface(void* cbdata, int iid)
+{
+ 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)
+ ret.push_back(it->second); // TODO: maybe we should exclude local entities
return ret;
}
diff --git a/source/simulation2/system/ComponentManager.h b/source/simulation2/system/ComponentManager.h
index 63b56171d4..61ea6cf9cf 100644
--- a/source/simulation2/system/ComponentManager.h
+++ b/source/simulation2/system/ComponentManager.h
@@ -221,6 +221,7 @@ private:
static void Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value);
static IComponent* Script_QueryInterface(void* cbdata, int ent, int iid);
static std::vector Script_GetEntitiesWithInterface(void* cbdata, int iid);
+ static std::vector Script_GetComponentsWithInterface(void* cbdata, int iid);
static void Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data);
static void Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data);
static int Script_AddEntity(void* cbdata, std::string templateName);
diff --git a/source/simulation2/tests/test_Serializer.h b/source/simulation2/tests/test_Serializer.h
index ae0e826e8f..7d1b0e8ef2 100644
--- a/source/simulation2/tests/test_Serializer.h
+++ b/source/simulation2/tests/test_Serializer.h
@@ -70,7 +70,7 @@ public:
void test_Debug_basic()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.NumberI32_Unbounded("x", -123);
@@ -81,7 +81,7 @@ public:
void test_Debug_floats()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.NumberFloat_Unbounded("x", 1e4f);
@@ -112,7 +112,7 @@ public:
void test_Debug_types()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::stringstream stream;
CDebugSerializer serialize(script, stream);
@@ -140,7 +140,7 @@ public:
void test_Std_basic()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::stringstream stream;
CStdSerializer serialize(script, stream);
@@ -166,7 +166,7 @@ public:
void test_Std_types()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::stringstream stream;
CStdSerializer serialize(script, stream);
@@ -223,7 +223,7 @@ public:
void test_Hash_basic()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
CHashSerializer serialize(script);
serialize.NumberI32_Unbounded("x", -123);
@@ -237,7 +237,7 @@ public:
void test_bounds()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.NumberI32("x", 16, -16, 16);
@@ -250,7 +250,7 @@ public:
void test_script_basic()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj;
TS_ASSERT(script.Eval("({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})", obj));
@@ -309,7 +309,7 @@ public:
void helper_script_roundtrip(const char* msg, const char* input, const char* expected, size_t expstreamlen = 0, const char* expstream = NULL)
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj;
TSM_ASSERT(msg, script.Eval(input, obj));
@@ -392,7 +392,7 @@ public:
void test_script_exceptions()
{
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj;
std::stringstream stream;
@@ -427,7 +427,7 @@ public:
{
const char* input = "var x = {}; for (var i=0;i<256;++i) x[i]=Math.pow(i, 2); x";
- ScriptInterface script("Test");
+ ScriptInterface script("Test", "Test", ScriptInterface::CreateRuntime());
CScriptVal obj;
TS_ASSERT(script.Eval(input, obj));