Add AI script code to provide a cleaner API around the engine interface.
Handle AIProxy entirely through scripts. Support structured clones of script values. Improve performance. Support multiple script contexts sharing a runtime. Use a separate context per AI player. This was SVN commit r8866.
This commit is contained in:
parent
dd501b2a5a
commit
f39f279132
54
binaries/data/mods/public/simulation/ai/common-api/base.js
Normal file
54
binaries/data/mods/public/simulation/ai/common-api/base.js
Normal file
@ -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});
|
||||
};
|
123
binaries/data/mods/public/simulation/ai/common-api/entity.js
Normal file
123
binaries/data/mods/public/simulation/ai/common-api/entity.js
Normal file
@ -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;
|
||||
},
|
||||
};
|
@ -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;
|
||||
};
|
@ -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++;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -3,18 +3,161 @@ function AIProxy() {}
|
||||
AIProxy.prototype.Schema =
|
||||
"<empty/>";
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
@ -22,7 +22,7 @@ GarrisonHolder.prototype.Schema =
|
||||
*/
|
||||
GarrisonHolder.prototype.Init = function()
|
||||
{
|
||||
//Garrisoned Units
|
||||
// Garrisoned Units
|
||||
this.entities = [];
|
||||
this.spaceOccupied = 0;
|
||||
this.timer = undefined;
|
||||
|
@ -0,0 +1 @@
|
||||
Engine.RegisterInterface("AIProxy");
|
@ -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)
|
||||
{
|
||||
|
@ -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<CNetClient*> clients;
|
||||
|
@ -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());
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -261,3 +261,9 @@ VECTOR(u32)
|
||||
VECTOR(std::string)
|
||||
VECTOR(std::wstring)
|
||||
VECTOR(CScriptValRooted)
|
||||
|
||||
class IComponent;
|
||||
template<> jsval ScriptInterface::ToJSVal<std::vector<IComponent*> >(JSContext* cx, const std::vector<IComponent*>& val)
|
||||
{
|
||||
return ToJSVal_vector(cx, val);
|
||||
}
|
||||
|
@ -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<ScriptRuntime*>(data);
|
||||
|
||||
if (m->m_rooter)
|
||||
m->m_rooter->Trace(trc);
|
||||
}
|
||||
};
|
||||
|
||||
shared_ptr<ScriptRuntime> ScriptInterface::CreateRuntime()
|
||||
{
|
||||
return shared_ptr<ScriptRuntime>(new ScriptRuntime);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ScriptInterface_impl
|
||||
{
|
||||
ScriptInterface_impl(const char* nativeScopeName, JSContext* cx);
|
||||
ScriptInterface_impl(const char* nativeScopeName, const shared_ptr<ScriptRuntime>& 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<ScriptRuntime> 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<ScriptInterface_impl*>(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<ScriptRuntime>& 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<ScriptRuntime>& 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::StructuredClone> ScriptInterface::WriteStructuredClone(jsval v)
|
||||
{
|
||||
uint64* data = NULL;
|
||||
size_t nbytes = 0;
|
||||
if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes))
|
||||
return shared_ptr<StructuredClone>();
|
||||
// TODO: should we have better error handling?
|
||||
// Currently we'll probably continue and then crash in ReadStructuredClone
|
||||
|
||||
shared_ptr<StructuredClone> ret (new StructuredClone);
|
||||
ret->m_Data = data;
|
||||
ret->m_Size = nbytes;
|
||||
return ret;
|
||||
}
|
||||
|
||||
jsval ScriptInterface::ReadStructuredClone(const shared_ptr<ScriptInterface::StructuredClone>& ptr)
|
||||
{
|
||||
jsval ret = JSVAL_VOID;
|
||||
JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, &ret);
|
||||
return ret;
|
||||
}
|
||||
|
@ -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<ScriptRuntime> 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<ScriptRuntime>& 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<StructuredClone> WriteStructuredClone(jsval v);
|
||||
jsval ReadStructuredClone(const shared_ptr<StructuredClone>& ptr);
|
||||
|
||||
private:
|
||||
bool CallFunction_(jsval val, const char* name, size_t argc, jsval* argv, jsval& ret);
|
||||
bool Eval_(const char* code, jsval& ret);
|
||||
|
@ -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;
|
||||
|
@ -30,7 +30,7 @@ class TestScriptConversions : public CxxTest::TestSuite
|
||||
template <typename T>
|
||||
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 <typename T>
|
||||
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<i32>(cx, 0)));
|
||||
@ -144,7 +144,7 @@ public:
|
||||
roundtrip<float>(-INFINITY, "-Infinity");
|
||||
convert_to<float>(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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -63,9 +63,6 @@ COMPONENT(AIInterfaceScripted)
|
||||
INTERFACE(AIManager)
|
||||
COMPONENT(AIManager)
|
||||
|
||||
INTERFACE(AIProxy)
|
||||
COMPONENT(AIProxyScripted)
|
||||
|
||||
INTERFACE(CommandQueue)
|
||||
COMPONENT(CommandQueue)
|
||||
|
||||
|
@ -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<ScriptRuntime>& runtime, boost::rand48& rng) :
|
||||
m_Worker(worker), m_AIName(aiName), m_Player(player), m_ScriptInterface("Engine", "AI", runtime)
|
||||
{
|
||||
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
|
||||
|
||||
struct SCommands
|
||||
{
|
||||
player_id_t player;
|
||||
std::vector<CScriptValRooted> commands;
|
||||
m_ScriptInterface.ReplaceNondeterministicFunctions(rng);
|
||||
|
||||
m_ScriptInterface.RegisterFunction<void, std::wstring, CAIPlayer::IncludeModule>("IncludeModule");
|
||||
m_ScriptInterface.RegisterFunction<void, CScriptValRooted, CAIPlayer::PostCommand>("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<CAIPlayer*> (cbdata);
|
||||
|
||||
self->LoadScripts(name);
|
||||
}
|
||||
|
||||
static void PostCommand(void* cbdata, CScriptValRooted cmd)
|
||||
{
|
||||
CAIPlayer* self = static_cast<CAIPlayer*> (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<shared_ptr<ScriptInterface::StructuredClone> > m_Commands;
|
||||
std::set<std::wstring> m_LoadedModules;
|
||||
};
|
||||
|
||||
public:
|
||||
struct SReturnedCommands
|
||||
struct SCommandSets
|
||||
{
|
||||
player_id_t player;
|
||||
std::vector<std::string> commands;
|
||||
std::vector<shared_ptr<ScriptInterface::StructuredClone> > 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<void*> (this));
|
||||
|
||||
// TODO: ought to seed the RNG (in a network-synchronised way) before we use it
|
||||
m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG);
|
||||
|
||||
m_ScriptInterface.RegisterFunction<void, CScriptValRooted, CAIWorker::PostCommand>("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<CAIPlayer> 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<ScriptInterface::StructuredClone>& gameState)
|
||||
{
|
||||
debug_assert(m_CommandsComputed);
|
||||
|
||||
@ -178,26 +249,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void GetCommands(std::vector<SReturnedCommands>& commands)
|
||||
void GetCommands(std::vector<SCommandSets>& 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<SerializeScriptVal>()(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<SerializeScriptVal>()(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<CAIWorker*> (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<ScriptRuntime> m_ScriptRuntime;
|
||||
ScriptInterface m_ScriptInterface;
|
||||
boost::rand48 m_RNG;
|
||||
|
||||
CScriptValRooted m_EntityTemplates;
|
||||
std::map<std::wstring, CScriptValRooted> m_PlayerMetadata;
|
||||
std::vector<SAIPlayer> m_Players;
|
||||
std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying
|
||||
|
||||
std::string m_GameState;
|
||||
shared_ptr<ScriptInterface::StructuredClone> m_GameState;
|
||||
|
||||
bool m_CommandsComputed;
|
||||
std::vector<SCommands> m_Commands;
|
||||
int m_CurrentlyComputingPlayer; // used so PostCommand knows what player the command is for
|
||||
};
|
||||
|
||||
|
||||
@ -424,45 +448,17 @@ public:
|
||||
CmpPtr<ICmpAIInterface> 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<entity_id_t, IComponent*>& ents = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_AIProxy);
|
||||
|
||||
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
|
||||
{
|
||||
// Skip local entities
|
||||
if (ENTITY_IS_LOCAL(it->first))
|
||||
continue;
|
||||
|
||||
CScriptVal rep = static_cast<ICmpAIProxy*>(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<CAIWorker::SReturnedCommands> commands;
|
||||
std::vector<CAIWorker::SCommandSets> commands;
|
||||
m_Worker.GetCommands(commands);
|
||||
|
||||
CmpPtr<ICmpCommandQueue> 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<CScriptVal> ("GetRepresentation");
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_COMPONENT_SCRIPT_WRAPPER(AIProxyScripted)
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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
|
@ -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)
|
||||
|
@ -192,3 +192,18 @@ template<> bool ScriptInterface::FromJSVal<CFixedVector2D>(JSContext* cx, jsval
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> jsval ScriptInterface::ToJSVal<CFixedVector2D>(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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
@ -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<void, std::string, CScriptVal, CComponentManager::Script_RegisterGlobal> ("RegisterGlobal");
|
||||
m_ScriptInterface.RegisterFunction<IComponent*, int, int, CComponentManager::Script_QueryInterface> ("QueryInterface");
|
||||
m_ScriptInterface.RegisterFunction<std::vector<int>, int, CComponentManager::Script_GetEntitiesWithInterface> ("GetEntitiesWithInterface");
|
||||
m_ScriptInterface.RegisterFunction<std::vector<IComponent*>, int, CComponentManager::Script_GetComponentsWithInterface> ("GetComponentsWithInterface");
|
||||
m_ScriptInterface.RegisterFunction<void, int, int, CScriptVal, CComponentManager::Script_PostMessage> ("PostMessage");
|
||||
m_ScriptInterface.RegisterFunction<void, int, CScriptVal, CComponentManager::Script_BroadcastMessage> ("BroadcastMessage");
|
||||
m_ScriptInterface.RegisterFunction<int, std::string, CComponentManager::Script_AddEntity> ("AddEntity");
|
||||
@ -349,7 +351,18 @@ std::vector<int> CComponentManager::Script_GetEntitiesWithInterface(void* cbdata
|
||||
std::vector<int> ret;
|
||||
const std::map<entity_id_t, IComponent*>& ents = componentManager->GetEntitiesWithInterface(iid);
|
||||
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
|
||||
ret.push_back(it->first);
|
||||
ret.push_back(it->first); // TODO: maybe we should exclude local entities
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<IComponent*> CComponentManager::Script_GetComponentsWithInterface(void* cbdata, int iid)
|
||||
{
|
||||
CComponentManager* componentManager = static_cast<CComponentManager*> (cbdata);
|
||||
|
||||
std::vector<IComponent*> ret;
|
||||
const std::map<entity_id_t, IComponent*>& ents = componentManager->GetEntitiesWithInterface(iid);
|
||||
for (std::map<entity_id_t, IComponent*>::const_iterator it = ents.begin(); it != ents.end(); ++it)
|
||||
ret.push_back(it->second); // TODO: maybe we should exclude local entities
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -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<int> Script_GetEntitiesWithInterface(void* cbdata, int iid);
|
||||
static std::vector<IComponent*> 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);
|
||||
|
@ -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));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user