2685 lines
89 KiB
JavaScript
2685 lines
89 KiB
JavaScript
/**
|
|
** ==============================
|
|
** O O O OOOO
|
|
** O O O O O O
|
|
** O O O O O O
|
|
** OOOO OOOO O OOO OOOO
|
|
** O O O O O O O
|
|
** O O O O O O O
|
|
** OOOO OOOO O O OOOO
|
|
** ==============================
|
|
** Dr. Stefan Bosse http://www.bsslab.de
|
|
**
|
|
** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
|
|
** BY THE AUTHOR(S).
|
|
** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
|
|
** MODIFIED, OR OTHERWISE USED IN A CONTEXT
|
|
** OUTSIDE OF THE SOFTWARE SYSTEM.
|
|
**
|
|
** $AUTHORS: Stefan Bosse
|
|
** $INITIAL: (C) 2006-2024 bLAB
|
|
** $CREATED: 01-02-17 by sbosse.
|
|
** $VERSION: 1.18.21
|
|
**
|
|
** $INFO:
|
|
**
|
|
** SEJAM: JAM Agent Simluator
|
|
** + WEB/nw.js webix+ Anychart graphics.js GUI
|
|
** + Chat Dialog API (botui)
|
|
**
|
|
** $ENDOFINFO
|
|
*/
|
|
|
|
|
|
var Comp = Require('com/compat');
|
|
var Io = Require('com/io');
|
|
var current=none;
|
|
var Aios = none;
|
|
|
|
var Papa = Require('parser/papaparse.js');
|
|
var Esprima = Require('parser/esprima');
|
|
var Name = Require('com/pwgen');
|
|
var Json = Require('jam/jsonfn');
|
|
var util = Require('util');
|
|
var JamAnal = Require('jam/analyzer');
|
|
var Db = Require('db/dbS');
|
|
var DbQ = Require('db/dbQ');
|
|
var SQLJSON = Require('db/sqljson');
|
|
var RTree = Require('rtree/rtree');
|
|
var AiosXnet = Require('simu/aiosXnet');
|
|
|
|
var ml = Require('ml/ml')
|
|
var nn = Require('nn/nn')
|
|
var csp = Require('csp/csp')
|
|
var sat = Require('logic/sat')
|
|
var numerics = Require('numerics/numerics');
|
|
|
|
var nameopts = {
|
|
world:{length:8, memorable:true, uppercase:true},
|
|
node: {length:8, memorable:true, lowercase:true}
|
|
}
|
|
|
|
// Geometric Utiliy Functions
|
|
function sind(x) { return Math.sin(x/360*(2*Math.PI)) }
|
|
function cosd(x) { return Math.cos(x/360*(2*Math.PI)) }
|
|
function rotate(d,a) {
|
|
return [
|
|
int(d[0]*cosd(a)-d[1]*sind(a)),
|
|
int(d[1]*sind(a)+d[0]*sind(a))
|
|
]
|
|
}
|
|
function distance2Rect (pos,bbox,scale) {
|
|
if (!scale) scale={x:1,y:1};
|
|
var px = pos.x,
|
|
py = pos.y,
|
|
x0 = bbox.x+bbox.w/2,
|
|
y0 = bbox.y+bbox.h/2,
|
|
dx = (Math.max(Math.abs(px - x0) - bbox.w / 2, 0))/scale.x,
|
|
dy = (Math.max(Math.abs(py - y0) - bbox.h / 2, 0))/scale.y;
|
|
|
|
return Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2))
|
|
}
|
|
function distance (pos1,pos2,scale) {
|
|
if (!scale) scale={x:1,y:1};
|
|
var
|
|
dx = Math.abs(pos1.x - pos2.x) / scale.x,
|
|
dy = Math.abs(pos1.y - pos2.y) / scale.y;
|
|
|
|
return Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2))
|
|
}
|
|
|
|
var hrtime;
|
|
|
|
if (global.TARGET != "browser") hrtime = process.hrtime;
|
|
else {
|
|
// polyfil for window.performance.now
|
|
var performance = global.performance || {}
|
|
var performanceNow =
|
|
performance.now ||
|
|
performance.mozNow ||
|
|
performance.msNow ||
|
|
performance.oNow ||
|
|
performance.webkitNow ||
|
|
function(){ return (new Date()).getTime() }
|
|
|
|
// generate timestamp or delta
|
|
// see http://nodejs.org/api/process.html#process_process_hrtime
|
|
hrtime = function (previousTimestamp){
|
|
var clocktime = performanceNow.call(performance)*1e-3
|
|
var seconds = Math.floor(clocktime)
|
|
var nanoseconds = Math.floor((clocktime%1)*1e9)
|
|
if (previousTimestamp) {
|
|
seconds = seconds - previousTimestamp[0]
|
|
nanoseconds = nanoseconds - previousTimestamp[1]
|
|
if (nanoseconds<0) {
|
|
seconds--
|
|
nanoseconds += 1e9
|
|
}
|
|
}
|
|
return [seconds,nanoseconds]
|
|
}
|
|
}
|
|
|
|
/* construct bbox {x,y,w,h} from geometric data
|
|
{x,y,x0,y0,x1,y1,dx,dy,w,h,dir} relative
|
|
to current position {x,y}
|
|
optional bounds {x0,y0,x1,y1}
|
|
|
|
+-----> x
|
|
| N x,y--+
|
|
| W X E | |
|
|
v S +--w,h
|
|
|
|
y
|
|
|
|
*/
|
|
|
|
function makeBbox (pos,geo,bounds) {
|
|
bbox={x:pos.x,y:pos.y,w:0,h:0} // {x,y,w,h}
|
|
if (typeof geo == 'number') // radius around center pos
|
|
return {x:pos.x-geo,y:pos.y-geo,w:2*geo+1,h:2*geo+1};
|
|
|
|
if (geo.x) bbox.x=geo.x;
|
|
if (geo.y) bbox.y=geo.y;
|
|
if (geo.x0) bbox.x=geo.x0;
|
|
if (geo.y0) bbox.x=geo.y0;
|
|
if (geo.dx) bbox.x=pos.x+geo.dx;
|
|
if (geo.dy) bbox.y=pos.y+geo.dy;
|
|
if (geo.w) bbox.w=geo.w;
|
|
if (geo.h) bbox.w=geo.h;
|
|
if (geo.x1) bbox.w=geo.x1-bbox.x+1;
|
|
if (geo.y1) bbox.h=geo.y1-bbox.y+1;
|
|
if (geo.r) return {x:bbox.x-geo.r,y:bbox.y-geo.r,w:2*geo.r+1,h:2*geo.r+1};
|
|
if (geo.dir) switch (geo.dir) {
|
|
// including current position X
|
|
// Ex. WEST:
|
|
// ****
|
|
// ***X
|
|
// ****
|
|
case Aios.DIR.NORTH:
|
|
if (geo.distance) bbox.w=geo.spread||1,bbox.h=geo.distance+1;
|
|
bbox.x -= int(bbox.w/2); bbox.y -= (bbox.h-1);
|
|
break;
|
|
case Aios.DIR.SOUTH:
|
|
if (geo.distance) bbox.w=geo.spread||1,bbox.h=geo.distance+1;
|
|
bbox.x -= int(bbox.w/2);
|
|
break;
|
|
case Aios.DIR.WEST:
|
|
if (geo.distance) bbox.h=geo.spread||1,bbox.w=geo.distance+1;
|
|
bbox.y -= int(bbox.h/2); bbox.x -= (bbox.w-1);
|
|
break;
|
|
case Aios.DIR.EAST:
|
|
if (geo.distance) bbox.h=geo.spread||1,bbox.w=geo.distance+1;
|
|
bbox.y -= int(bbox.h/2);
|
|
break;
|
|
}
|
|
return bbox;
|
|
}
|
|
|
|
function bbox2pp(bbox) {
|
|
return {x0:bbox.x,y0:bbox.y,x1:bbox.x+bbox.w-1,y1:bbox.y+bbox.h-1,
|
|
dir:bbox.dir,distance:bbox.distance}
|
|
}
|
|
function pp2bbox(pp) {
|
|
return {x:pp.x0,y:pp.y0,w:pp.x1-pp.x0+1,h:pp.y1-pp.y0+1}
|
|
}
|
|
/** Create a simulation world with visualization
|
|
*
|
|
* options: {config:object,id:string?,
|
|
* nolimits:boolean is a disable flag for agent check poininting,
|
|
* connection:{random:number?},markings?,
|
|
* fastcopy?,
|
|
* gui,
|
|
* log:function,
|
|
* msg:function,
|
|
* units? : {},
|
|
* format? : {node,agent,class,...} is logging format setting,
|
|
* classes:{node:{..},ac1:{..},..}}
|
|
* with config: <geometrical world configuration>
|
|
*
|
|
*/
|
|
var simu = function (options) {
|
|
var self=this;
|
|
var node1,node2,row,row1,row2,i,j,p;
|
|
|
|
this.options=options||{};
|
|
|
|
if (this.options.id==_) this.options.id=Name.generate(nameopts.world);
|
|
if (this.options.connections==_) this.options.connections={};
|
|
if (this.options.markings) this.options.nodebar=true;
|
|
if (this.options.nolimits) Aios.config({nolimits:true});
|
|
|
|
this.classes=this.options.classes||{};
|
|
delete this.options.classes;
|
|
this.run=false; // Simulator running
|
|
this.stopped=false; // Forced stop
|
|
this.step=0; // Step counter
|
|
this.stepped=0; // Remaining steps before stopping
|
|
this.loop=none;
|
|
this.time=0; // Current simulation time
|
|
this.time0=0;
|
|
this.lag=0; // Time lag between simulation runs
|
|
this.events=[];
|
|
this.parameter=options.parameter||{};
|
|
this.verbose = options.verbose||0;
|
|
Aios.config({verbose:this.verbose});
|
|
|
|
this.cleanWorld=false;
|
|
|
|
this.log=options.log; // Simulation messaging
|
|
this.msg=options.msg; // Agent messaging
|
|
Aios.config({print:options.log});
|
|
// Aios.World.options.verbose=1;
|
|
|
|
this.UI = options.gui.UI;
|
|
this.webix = options.gui.webix;
|
|
this.gui = options.gui;
|
|
this.simuPhy = options.simuPhy;
|
|
this.CANNON = options.CANNON;
|
|
|
|
this.stats = {
|
|
agents:{},
|
|
custom:{},
|
|
cpu:0
|
|
};
|
|
|
|
// Caches
|
|
this.cache = {
|
|
agent2node:{}
|
|
}
|
|
|
|
// Region tree for resources
|
|
this.rtree = RTree(10);
|
|
|
|
this.err=function (msg,err) {
|
|
self.log('Error: '+msg);
|
|
throw (err||'[SIM] Error');
|
|
}
|
|
|
|
this.warn=function (msg) {
|
|
self.log('Warning: '+msg);
|
|
}
|
|
|
|
this.out=function (msg) {
|
|
self.log(msg);
|
|
}
|
|
|
|
|
|
if (options.time!='real')
|
|
Aios.config({time:function () {
|
|
return self.step;
|
|
}}),this.log('Setting simulation clock to step time!') ;
|
|
if (!options.format) this.options.format={}; // Agent logging format
|
|
this.options.format.forEach(function (v,a) { Aios.config(v?{'log+':a}:{'log-':a})});
|
|
this.out('[SIM] Logging flags: '+util.inspect(this.options.format));
|
|
|
|
Aios.off('agent+');
|
|
Aios.on('agent+',function (desc) {
|
|
var aid,nid='node['+desc.node.id+']',
|
|
visual,xn,yn,level=desc.node.position.z||0;
|
|
if (desc.agent.ac && self.world.classes[desc.agent.ac]) {
|
|
if (!self.stats.agents[desc.agent.ac]) self.stats.agents[desc.agent.ac]=0;
|
|
self.stats.agents[desc.agent.ac]++;
|
|
aid='agent['+desc.agent.ac+':'+desc.agent.id+':'+desc.node.id+']';
|
|
visual=self.world.classes[desc.agent.ac].visual;
|
|
//self.out('Event register '+desc.agent.id+' of class '+desc.agent.ac+' on node '+desc.node.id+
|
|
// '('+self.gui.objects[nid].visual.x+','+self.gui.objects[nid].visual.y+')');
|
|
if (visual) {
|
|
xn=self.gui.objects[nid].visual.x+self.gui.objects[nid].visual.w/2;
|
|
yn=self.gui.objects[nid].visual.y+self.gui.objects[nid].visual.h/2;
|
|
if (visual.x!=undefined && visual.y!=undefined) {
|
|
xn=xn+visual.x;
|
|
yn=yn+visual.y;
|
|
}
|
|
if (visual.center && visual.center.x!=undefined && visual.center.y!=undefined) {
|
|
xn=xn+visual.center.x;
|
|
yn=yn+visual.center.y;
|
|
}
|
|
self.gui.objects[aid] = {
|
|
visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:visual.shape||'circle',
|
|
w:visual.width,
|
|
h:visual.height,
|
|
fill:visual.fill,
|
|
line:visual.line,
|
|
level:level
|
|
},
|
|
obj:desc.agent,
|
|
id:aid
|
|
};
|
|
self.gui.drawObject(aid);
|
|
}
|
|
} else {
|
|
}
|
|
});
|
|
|
|
Aios.off('agent-');
|
|
Aios.on('agent-',function (desc) {
|
|
var aid='agent['+desc.agent.ac+':'+desc.agent.id+':'+desc.node.id+']';
|
|
//self.out('Event unregister '+proc.agent.id+' on node '+node.id);
|
|
self.gui.destroyObject(aid);
|
|
if (self.cache.agent2node[desc.agent.id]) delete self.cache.agent2node[desc.agent.id];
|
|
if (desc.proc.type=='physical' && desc.node.type=='physical' && self.world.getNode(desc.node.id)) {
|
|
self.deleteNode(desc.node.id);
|
|
}
|
|
});
|
|
|
|
Aios.off('signal+');
|
|
Aios.on('signal+',function (proc,node,sig,arg,from) {
|
|
var aid='agent['+proc.agent.ac+':'+proc.agent.id+':'+node.id+']',
|
|
sid='signal['+proc.agent.ac+':'+proc.agent.id+':'+node.id+']',
|
|
nid='node['+node.id+']',
|
|
visual,level=node.position.z||0;
|
|
// self.log(aid);
|
|
if (self.model.classes[proc.agent.ac] && self.model.classes[proc.agent.ac].on)
|
|
visual=self.model.classes[proc.agent.ac].on[sig];
|
|
if (visual) {
|
|
xn=self.gui.objects[nid].visual.x+self.gui.objects[nid].visual.w/2;
|
|
yn=self.gui.objects[nid].visual.y+self.gui.objects[nid].visual.h/2;
|
|
if (visual.x && visual.y) {
|
|
xn=xn+visual.x;
|
|
yn=yn+visual.y;
|
|
}
|
|
self.gui.objects[sid] = {
|
|
visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:visual.shape||'circle',
|
|
w:visual.width,
|
|
h:visual.height,
|
|
fill:visual.fill,
|
|
line:visual.line,
|
|
level:level,
|
|
timeout:visual.time
|
|
},
|
|
obj:{sig:sig,arg:arg,from:from},
|
|
id:sid
|
|
};
|
|
self.gui.drawObject(sid);
|
|
self.cleanWorld=true;
|
|
}
|
|
|
|
});
|
|
|
|
// PRINT: Smart print function
|
|
var print = function (msg,header,depth,nolog) {
|
|
var lines=[];
|
|
var line='';
|
|
if (depth==_) depth=1;
|
|
if (msg==undefined) msg='undefined';
|
|
if (msg==null) msg='null';
|
|
function isvec(obj) {return(Comp.obj.isArray(obj) && (obj.length == 0 || !Comp.obj.isArray(obj[0])))}
|
|
function ismat(obj) {return(Comp.obj.isArray(obj) && obj.length > 0 && Comp.obj.isArray(obj[0]))}
|
|
function mat(o,depth) {
|
|
// matrix
|
|
var lines=[];
|
|
var line = '';
|
|
if (header) {line=header; header=_};
|
|
for (var j in o) {
|
|
var row=o[j];
|
|
line += Comp.printf.list(row,function (v) {
|
|
return (Comp.obj.isArray(v)?(depth>0?'['+vec(v,depth-1)+']':'[..]'):
|
|
Comp.obj.isObj(v)?(depth>0?obj(v,depth-1):'{..}'):v);
|
|
});
|
|
lines.push(line);
|
|
line='';
|
|
}
|
|
return lines;
|
|
}
|
|
function vec(v,depth) {
|
|
// vector
|
|
var lines=[];
|
|
var line = '';
|
|
if (header) {line=header; header=_};
|
|
if (v.length==0) return(line+'[]');
|
|
else {
|
|
// can still contain matrix elements that must bes separated
|
|
var sep='',sepi='';
|
|
for (var p in v) {
|
|
if (ismat(v[p])) {
|
|
//self.log.log(line); line=' ';
|
|
if (depth>0) {
|
|
lines = mat(v[p],depth-1);
|
|
line += sep+'['; sepi='';
|
|
Comp.array.iter(lines,function (line2) {
|
|
line += sepi+'['+line2+']';
|
|
sepi=',';
|
|
});
|
|
line += ']';
|
|
sep=',';
|
|
} else {
|
|
line += sep+'[[..]]';
|
|
sep=',';
|
|
}
|
|
}
|
|
else if (isvec(v[p])) {
|
|
//self.log.log(line); line=' ';
|
|
line += sep+vec(v[p],depth-1);
|
|
sep=',';
|
|
}
|
|
else {
|
|
line += sep+(Comp.obj.isArray(v[p])?(depth>0?vec(v[p],depth-1):'[..]'):
|
|
Comp.obj.isObj(v[p])?(depth>0?obj(v[p],depth-1):'{..}'):v[p]);
|
|
sep=',';
|
|
}
|
|
}
|
|
if (line!='') return line;
|
|
}
|
|
}
|
|
function obj(o,depth) {
|
|
var line='';
|
|
var sep='';
|
|
if (header) {line=header; header=_};
|
|
line += '{';
|
|
for (var p in o) {
|
|
if (!Comp.obj.isFunction(o[p])) {
|
|
line += sep + p+':'+
|
|
(Comp.obj.isArray(o[p])?(depth>0?vec(o[p],depth-1):'[..]'):
|
|
Comp.obj.isObj(o[p])?(depth>0?obj(o[p],depth-1):'{..}'):o[p]);
|
|
sep=',';
|
|
} else {
|
|
line += sep + p+':'+'function()';
|
|
sep=',';
|
|
}
|
|
}
|
|
return line+'}';
|
|
}
|
|
|
|
function str(s) {
|
|
var line='';
|
|
var lines=[];
|
|
var lines2 = Comp.string.split('\n',msg);
|
|
if (header) {line=header; header=_};
|
|
if (lines2.length==1)
|
|
lines.push(line+msg);
|
|
else {
|
|
Comp.array.iter(lines2,function (line2,i) {
|
|
if (i==0) lines.push(line+line2);
|
|
else lines.push(line2);
|
|
});
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
if (Comp.obj.isError(msg)) {
|
|
if (header) {line=header; header=_};
|
|
line += (/Error/.test(msg.toString())?msg.toString():'Error: '+msg.toString());
|
|
lines.push(line);
|
|
} else if (ismat(msg)) lines = Comp.array.concat(lines,
|
|
Comp.array.map(mat(msg,depth-1),function (line){
|
|
return ' '+line}));
|
|
else if (Comp.obj.isString(msg)) lines = Comp.array.concat(lines,str(msg));
|
|
else if (isvec(msg)) lines.push(vec(msg,depth-1));
|
|
else if (Comp.obj.isObj(msg)) lines.push(obj(msg,depth-1));
|
|
else {
|
|
if (header) {line=header; header=_};
|
|
line += msg;
|
|
lines.push(line);
|
|
}
|
|
|
|
if (nolog) return lines; else Comp.array.iter(lines,function (line) {self.msg(line)});
|
|
};
|
|
|
|
var msg = function(){
|
|
var header='';
|
|
if (!current.node || !current.process) {
|
|
print('[SIM] '+arguments[0]);
|
|
}
|
|
else if (arguments.length==1) {
|
|
header='[';
|
|
if (self.options.format.time) header += (Aios.time()+':');
|
|
if (self.options.format.node) header += (current.node.id+':');
|
|
header += (current.process.agent.id+':');
|
|
if (self.options.format.pid) header += (current.process.pid+':');
|
|
if (self.options.format.class) header += (current.process.agent.ac+'');
|
|
header += '] ';
|
|
print(arguments[0],header);
|
|
} else {
|
|
header='[';
|
|
if (self.options.format.time) header += (Aios.time()+':');
|
|
if (self.options.format.node) header += (current.node.id+':');
|
|
header += (current.process.agent.id+':');
|
|
if (self.options.format.pid) header += (current.process.pid+':');
|
|
if (self.options.format.class) header += (current.process.agent.ac+'');
|
|
header += '] ';
|
|
for (var i in arguments) {
|
|
if (i==0)
|
|
print(arguments[i],'['+current.node.id+':'+current.process.agent.id+':'+current.process.pid+':'+current.process.agent.ac+'] ');
|
|
else
|
|
print(arguments[i],_,2);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Module extensions
|
|
Aios.ml = ml.agent;
|
|
Aios.aios1.ml = ml.agent;
|
|
Aios.aios2.ml = ml.agent;
|
|
Aios.aios3.ml = ml.agent;
|
|
Aios.nn = nn.agent;
|
|
Aios.aios1.nn = nn.agent;
|
|
Aios.aios2.nn = nn.agent;
|
|
Aios.aios3.nn = nn.agent;
|
|
Aios.sat = sat.agent;
|
|
Aios.aios1.sat = sat.agent;
|
|
Aios.aios2.sat = sat.agent;
|
|
Aios.aios3.sat = sat.agent;
|
|
Aios.csp = csp.agent;
|
|
Aios.aios1.csp = csp.agent;
|
|
Aios.aios2.csp = csp.agent;
|
|
Aios.aios3.csp = csp.agent;
|
|
Aios.numerics = numerics.agent;
|
|
Aios.aios1.numerics = numerics.agent;
|
|
Aios.aios2.numerics = numerics.agent;
|
|
Aios.aios3.numerics = numerics.agent;
|
|
|
|
// Extended sandbox environment available for all agents
|
|
Aios.aios0.log=msg;
|
|
Aios.aios1.log=msg;
|
|
Aios.aios2.log=msg;
|
|
Aios.aios3.log=msg;
|
|
|
|
Aios.err=function(_msg) {msg('[AIOS] Error: '+_msg)};
|
|
Aios.aios0.print=function(msg,depth) {return print(msg,_,depth,false)};
|
|
Aios.aios1.print=function(msg,depth) {return print(msg,_,depth,false)};
|
|
Aios.aios2.print=function(msg,depth) {return print(msg,_,depth,false)};
|
|
Aios.aios3.print=function(msg,depth) {return print(msg,_,depth,false)};
|
|
Aios.aios0.sprint=function(msg,depth) {return print(msg,_,depth,true)};
|
|
Aios.aios1.sprint=function(msg,depth) {return print(msg,_,depth,true)};
|
|
Aios.aios2.sprint=function(msg,depth) {return print(msg,_,depth,true)};
|
|
Aios.aios3.sprint=function(msg,depth) {return print(msg,_,depth,true)};
|
|
//Aios.aios0.inspect=function(msg,depth) {return util.inspect(msg)};
|
|
//Aios.aios1.inspect=function(msg,depth) {return util.inspect(msg)};
|
|
//Aios.aios2.inspect=function(msg,depth) {return util.inspect(msg)};
|
|
//Aios.aios3.inspect=function(msg,depth) {return util.inspect(msg)};
|
|
Aios.aios3.sensors=function () {
|
|
return Aios.aios0.copy(current.node.sensors);
|
|
};
|
|
Aios.aios0.Vec3=function(x,y,z) {return new self.CANNON.Vec3(x,y,z)};
|
|
Aios.aios1.Vec3=function(x,y,z) {return new self.CANNON.Vec3(x,y,z)};
|
|
Aios.aios2.Vec3=function(x,y,z) {return new self.CANNON.Vec3(x,y,z)};
|
|
Aios.aios3.Vec3=function(x,y,z) {return new self.CANNON.Vec3(x,y,z)};
|
|
|
|
Aios.config({iterations:1,fastcopy:options.fastcopy||false});
|
|
|
|
if (!this.options.nolimits) Aios.config({TIMESCHED:1000});
|
|
|
|
// Create the simulation world
|
|
this.world = Aios.World.World([],{id:this.options.id,classes:this.classes});
|
|
|
|
/* optional geographical (gps) or geometric (geo) mapping functions ({x,y,z}) -> {latitude,longitude,height}
|
|
** { gps2px, px2gps, geo2px, px2geo, gps2dist }
|
|
** Can be used for absolute or relative coordinate transformations ({delta:true}).
|
|
*/
|
|
this.world.units = options.units||{gps2dist:function () {}};
|
|
|
|
// old channel-based SQL DB connection
|
|
this.db = {
|
|
init: function (path,channel,callback) {
|
|
var proc=current.process,stat;
|
|
if (!self.Db) self.Db={};
|
|
if (!self.Db[path]) {
|
|
if (channel==undefined) {
|
|
// embedded SQL
|
|
self.Db[path]=DbQ.Sql(path);
|
|
stat=self.Db[path].open();
|
|
if (callback) proc.callback(callback,[stat]);
|
|
} else {
|
|
// remote SQL
|
|
self.Db[path]=Db.Sqlc(path,channel);
|
|
self.Db[path].setLog(function (msg) {self.log(msg)});
|
|
self.Db[path].init(function (stat,err) {
|
|
self.log('[DB] '+path+' Initialized: '+stat);
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
// function (path:string,matname:string,header:[$var:$type]|[$type], callback?)
|
|
createMatrix: function (path,matname,header,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].createMatrix(matname,header,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].createMatrix(matname,header);
|
|
},
|
|
// function (path:string,matname:string,header:{$var:$type}, callback?)
|
|
createTable: function (path,tblname,header,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].createTable(tblname,header,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].createTable(tblname,header);
|
|
},
|
|
drop: function (path,tbl,callback) {
|
|
var proc=current.process;
|
|
self.Db[path].drop(tbl,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
});
|
|
},
|
|
error: function (path) {
|
|
return self.Db[path].error
|
|
},
|
|
exec: function (path,cmd,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].exec(cmd,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].exec(cmd);
|
|
},
|
|
get: function (path,callback) {
|
|
var proc=current.process;
|
|
self.Db[path].get(undefined,function (row,err) {
|
|
if (callback) proc.callback(callback,[row,err]);
|
|
});
|
|
},
|
|
insert: function (path,tbl,row,callback) {
|
|
var proc=current.process;
|
|
self.Db[path].insert(tbl,row,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
});
|
|
},
|
|
insertMatrix: function (path,matname,row,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].insertMatrix(matname,row,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].insertMatrix(matname,row);
|
|
},
|
|
insertTable: function (path,tblname,row,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].insertTable(tblname,row,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].insertTable(tblname,row);
|
|
},
|
|
readMatrix: function (path,matname,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].readMatrix(matname,function (mat,err) {
|
|
if (callback) proc.callback(callback,[mat,err]);
|
|
}); else return self.Db[path].readMatrix(matname);
|
|
},
|
|
readTable: function (path,tblname,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].readTable(tblname,function (tbl,err) {
|
|
if (callback) proc.callback(callback,[tbl,err]);
|
|
}); else return self.Db[path].readTable(tblname);
|
|
},
|
|
select: function (path,tbl,vars,cond,callback) {
|
|
var proc=current.process;
|
|
if (!callback && Comp.obj.isFunction(cond)) callback=cond,cond=undefined;
|
|
self.Db[path].select(tbl,vars,cond,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
});
|
|
},
|
|
writeMatrix: function (path,matname,matrix,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].writeMatrix(matname,matrix,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].writeMatrix(matname,matrix);
|
|
},
|
|
writeTable: function (path,tblname,table,callback) {
|
|
var proc=current.process;
|
|
if (callback) self.Db[path].writeTable(tblname,table,function (stat,err) {
|
|
if (callback) proc.callback(callback,[stat,err]);
|
|
}); else return self.Db[path].writeTable(tblname,table);
|
|
},
|
|
}
|
|
// New SQLJSOn RPC API
|
|
this.sql = {
|
|
init : function (url) {
|
|
return SQLJSON.sql(url)
|
|
},
|
|
}
|
|
|
|
// CSV file import/export
|
|
this.csv = {
|
|
read: function (file,callback,verbose) {
|
|
var text,data,header,convert,proc=current.process;
|
|
if (callback == true) convert=true,callback=null;
|
|
if (verbose) self.log('CSV: Reading from '+file);
|
|
try {
|
|
text=Io.read_file(file);
|
|
if (!text) throw 'CSV File read error: '+file;
|
|
if (verbose) self.log('CSV: Parsing '+file);
|
|
Papa.parse(text,{
|
|
skipEmptyLines: true,
|
|
dynamicTyping: true,
|
|
complete: function(results) {
|
|
if (verbose) self.log('CSV parsed with DEL="'+results.meta.delimiter+'" TRUNC='+results.meta.truncated+
|
|
' ABORT='+results.meta.aborted);
|
|
if (callback) proc.callback(callback,[results.data]);
|
|
data=results.data;
|
|
}
|
|
});
|
|
if (convert) { // first line must be header
|
|
header=data.shift();
|
|
data=data.map(function (row) {
|
|
var r={};
|
|
header.forEach(function (col,i) { r[col]=row[i] });
|
|
return r;
|
|
})
|
|
}
|
|
return data;
|
|
} catch (e) {
|
|
if (callback) callback(e);
|
|
return e;
|
|
}
|
|
},
|
|
write: function (file,header,data,callback,verbose) {
|
|
if (file[0]!='/') file=self.gui.workdir+'/'+file;
|
|
var d1=false,fd,i,convert=!Comp.obj.isArray(data[0])&&Comp.obj.isObj(data[0]),
|
|
sep=',',proc=current.process;
|
|
if (typeof callback == 'string') sep=callback,callback=null;
|
|
d1 = typeof data[0] != 'object';
|
|
if (!header || header.length==0) {
|
|
if (!convert)
|
|
header=d1?['0']:data[0].map(function (x,i) { return String(i) });
|
|
else {
|
|
header=[];
|
|
for (var p in data[0]) {
|
|
header.push(p);
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
if (verbose) self.log('CSV: Writing to '+file);
|
|
fd=Io.open(file,'w+');
|
|
Io.write_line(fd,header.join(sep));
|
|
if (!d1)
|
|
for(i in data) {
|
|
if (!convert)
|
|
Io.write_line(fd,data[i].join(sep));
|
|
else
|
|
Io.write_line(fd,header.map(function (col) { return data[i][col]}).join(sep));
|
|
}
|
|
else
|
|
for(i in data) {
|
|
if (!convert)
|
|
Io.write_line(fd,data[i]);
|
|
else
|
|
Io.write_line(fd,data[i][header[0]]);
|
|
};
|
|
|
|
Io.close(fd);
|
|
if (callback) proc.callback(callback,[data.length]);
|
|
return data.length
|
|
} catch (e) {
|
|
if (callback) proc.callback(callback,[e]);
|
|
return e;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/* utils */
|
|
function within(p1,p2,r) {
|
|
return (p1.x>= p2.x-r && p1.x <= p2.x+2) &&
|
|
(p1.y>= p2.y-r && p1.y <= p2.y+2)
|
|
}
|
|
function whatType(what) {
|
|
// agent-twin => agent
|
|
var tokens = what.match(/([a-z]+)(-.+)/)
|
|
return tokens?tokens[1]:what;
|
|
}
|
|
function whatName(what) {
|
|
// agent-twin => twin
|
|
var tokens = what.match(/[a-z]+-(.+)/)
|
|
return tokens?tokens[1]:null;
|
|
}
|
|
// Generic simulator interface
|
|
var aiosXsimu = {
|
|
changeVisual: function (id,visual) {
|
|
self.gui.changeObject(id,visual);
|
|
},
|
|
|
|
chat : {
|
|
agent : self.gui.chatAgent,
|
|
message: self.gui.chatMessage,
|
|
question : function (id, question, action, timeout, callback) {
|
|
var process = Aios.current.process;
|
|
self.gui.chatQuestion (id, question, action, function (res) {
|
|
process.callback(callback,res?[res.value]:null);
|
|
process.wakeup();
|
|
},timeout);
|
|
process.suspend(Aios.timeout(timeout));
|
|
},
|
|
script: self.gui.chatScript,
|
|
},
|
|
|
|
clear: function (msg,log) {
|
|
if (msg) {
|
|
self.gui.clear('msgWin');
|
|
self.gui.messages=[];
|
|
}
|
|
if (log) self.gui.clear('logWin');
|
|
},
|
|
|
|
/*
|
|
** typeof arguments = {nodeclass:string|function,arg1,arg2,...}
|
|
** arguments arg1,.. are node constructor function arguments
|
|
** if nodeclass is a string then a function of model.nodes.<nodeclass>(arg1,arg2,..) or
|
|
** model.world.nodes is used
|
|
** returns node identifier
|
|
*/
|
|
createNode: function () {
|
|
var nodes = (self.model.world && self.model.world.nodes) ||
|
|
self.model.nodes,res,
|
|
cls=typeof arguments[0] == 'string'?arguments[0]:'function';
|
|
function shift(args) {
|
|
var i=0,p;
|
|
for(p in args) {
|
|
if (p=='0') continue;
|
|
args[i]=args[p];
|
|
i++;
|
|
}
|
|
return args;
|
|
}
|
|
if (nodes) {
|
|
var constrFun;
|
|
if (Comp.obj.isFunction(arguments[0])) constrFun=arguments[0];
|
|
else constrFun=nodes[arguments[0]];
|
|
if (constrFun && Comp.obj.isFunction(constrFun)) {
|
|
var desc=constrFun.apply(null,shift(arguments));
|
|
if (desc) {
|
|
if (res=self.checkVisual(desc.visual)) throw 'Invalid node ('+cls+') visual: '+res;
|
|
return self.createNode(desc,true);
|
|
}
|
|
} else self.log('createNode: unknown node class '+arguments[0])
|
|
} else self.log('createNode: no node constructors defined (expected model.world.nodes or model.nodes')
|
|
},
|
|
|
|
/*
|
|
** Create an agent on specified node
|
|
*/
|
|
createOn:function (nid,ac,args,level) {
|
|
var node=self.world.getNode(nid),
|
|
proc=none;
|
|
if (!node) return none;
|
|
if (level==undefined) level=1;
|
|
if (args==undefined) args=[{}];
|
|
if (self.world.classes[ac])
|
|
agent = Aios.Code.createOn(node,self.world.classes[ac][level],args,level,ac);
|
|
else
|
|
self.err('createOn: no such class '+ac);
|
|
if (agent)
|
|
return agent.agent.id;
|
|
else return none;
|
|
},
|
|
|
|
deleteNode: function (nodeid) {
|
|
self.deleteNode(nodeid);
|
|
},
|
|
|
|
event: {
|
|
add: function (ev) {
|
|
self.events.push({step:self.step,node:current.node.id,event:ev});
|
|
},
|
|
get: function () {
|
|
var evl = self.events;
|
|
self.events=[];
|
|
return evl;
|
|
}
|
|
},
|
|
|
|
get : function (p) {
|
|
switch (p) {
|
|
case 'delay': return self.delay;
|
|
}
|
|
},
|
|
|
|
// Return node object (if id is a regular expression, an array of matching nodes)
|
|
getNode:function (id) {
|
|
return self.world.getNode(id);
|
|
},
|
|
|
|
getVisual: function (id) {
|
|
return self.gui.getObject(id).visual;
|
|
},
|
|
|
|
getStats: function (target,arg) {
|
|
var i,j,proc,node,p,
|
|
stats;
|
|
switch (target) {
|
|
case 'node':
|
|
node=self.world.getNode(arg);
|
|
if (node) stats=node.stats;
|
|
break;
|
|
default:
|
|
stats={steps:self.step,time:Aios.time(),agents:{},agentsN:self.stats.agents,total:0,nodes:0,
|
|
create:0,migrate:0,fork:0,fastcopy:0,signal:0,
|
|
send:0,
|
|
cpu:self.stats.cpu,
|
|
custom:{}}
|
|
for(p in self.stats.custom) stats.custom[p]=self.stats.custom[p];
|
|
if (!self.world || !self.world.nodes) return {};
|
|
for (i in self.world.nodes) {
|
|
node=self.world.nodes[i];
|
|
if (!node) continue;
|
|
stats.nodes++;
|
|
for (p in node.stats) stats[p] += node.stats[p];
|
|
for(j in node.processes.table) {
|
|
proc=node.processes.table[j];
|
|
if (proc && proc.agent.ac) {
|
|
if (stats.agents[proc.agent.ac]==undefined) stats.agents[proc.agent.ac]=0;
|
|
stats.agents[proc.agent.ac]++;
|
|
stats.total++;
|
|
}
|
|
}
|
|
for(p in node.connections) {
|
|
if (node.connections[p] && node.connections[p].count)
|
|
stats.send += node.connections[p].count();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return stats;
|
|
},
|
|
|
|
getSteps:function () {return self.step},
|
|
|
|
// Load JSON object from file
|
|
load : function (file) {
|
|
var json;
|
|
if (global.config.os=='win32' && global.config.win) {
|
|
json = Io.read_file('Z:'+self.gui.lastdir+'\\'+file);
|
|
self.gui.log('Z:'+self.gui.lastdir+'\\'+file)
|
|
} else if (global.config.os=='win32') {
|
|
json = Io.read_file(self.gui.lastdir+'\\'+file);
|
|
self.gui.log(self.gui.lastdir+'\\'+file)
|
|
} else if (global.TARGET != 'browser') {
|
|
json = Io.read_file(self.gui.lastdir+'/'+file);
|
|
self.gui.log(self.gui.lastdir+'/'+file)
|
|
} else {
|
|
json = loadFile(file);
|
|
self.gui.log(file)
|
|
}
|
|
if (json) return aiosXsimu.ofJSON(json);
|
|
},
|
|
|
|
log: function (msg) {
|
|
self.gui.log(msg);
|
|
},
|
|
|
|
inspect : util.inspect,
|
|
|
|
model : function (name) {
|
|
return self.model;
|
|
},
|
|
message:this.gui.message,
|
|
// relative move dx,dy,dz with|w/o units m | \B0 ..
|
|
|
|
move: self.moveObject.bind(this),
|
|
// absolute move to x,y,z with|w/o units m | \B0 ..
|
|
|
|
moveTo: self.moveObjectTo.bind(this),
|
|
|
|
network: {
|
|
x:this.options.x,
|
|
y:this.options.y,
|
|
z:this.options.z
|
|
},
|
|
|
|
ofJSON: function (s) {
|
|
return Aios.Code.Jsonf.parse(s,{})
|
|
},
|
|
|
|
options:this.options,
|
|
|
|
parameter: function (name) {
|
|
return name?self.parameter[name]:self.parameter;
|
|
},
|
|
|
|
position: function () { return current.node.position },
|
|
|
|
print:print,
|
|
|
|
// Save object to JSON file
|
|
save : function (file,o) {
|
|
var json = aiosXsimu.toJSON(o),res;
|
|
if (global.config.os=='win32' && global.config.win)
|
|
res = Io.write_file('Z:'+self.gui.lastdir+'\\'+file,json);
|
|
else if (global.config.os=='win32')
|
|
res = Io.write_file(self.gui.lastdir+'\\'+file,json);
|
|
else
|
|
res = Io.write_file(self.gui.lastdir+'/'+file,json);
|
|
|
|
return res
|
|
},
|
|
|
|
// Set simulation control parameter and GUI control
|
|
set : function (p,v) {
|
|
var old;
|
|
switch (p) {
|
|
case 'delay': old=self.delay; self.delay=v; break;
|
|
case 'window':
|
|
if (Comp.obj.isObj(v)) for(var o in v) {
|
|
console.log(o,v[o])
|
|
switch (o) {
|
|
case 'layout':
|
|
if (v[o]=='auto') {
|
|
var _locked=self.gui.toolbar.locked;
|
|
self.gui.toolbar.locked=false;
|
|
self.gui.autoLayout();
|
|
self.gui.toolbar.locked=_locked;
|
|
}
|
|
break;
|
|
case 'Inspector':
|
|
case 'inspector':
|
|
if (self.gui.inspectorWin[v[o]]) self.gui.inspectorWin[v[o]]();
|
|
break;
|
|
case 'Chat':
|
|
case 'chat':
|
|
if (self.gui.chatWin[v[o]]) self.gui.chatWin[v[o]]();
|
|
if (v[o]=='show') self.gui.chatInit();
|
|
break;
|
|
case 'World':
|
|
case 'world':
|
|
if (self.gui.worldWin.container) self.gui.worldWin.container.resume()
|
|
if (v[o]['zoom'] && v[o]['zoom']=='fit') self.gui.worldWin.zoomFit();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return old;
|
|
},
|
|
|
|
start: function (steps) { self.start(steps,true)},
|
|
|
|
stat: function (p,v) {
|
|
self.stats.custom[p]=v;
|
|
},
|
|
|
|
stop: function () { return self.stop(true) },
|
|
// Use simulation time (steps) instead of system time
|
|
|
|
toJSON: function (o) {
|
|
return Aios.Code.Jsonf.stringify(o)
|
|
},
|
|
|
|
time : function () {return self.step},
|
|
|
|
utime: function () {
|
|
hr=hrtime(); return hr[0]*1E9+hr[1]
|
|
},
|
|
};
|
|
|
|
aiosXsimu.csv = this.csv;
|
|
aiosXsimu.db = this.db;
|
|
aiosXsimu.sql = this.sql;
|
|
aiosXsimu.units = this.world.units;
|
|
|
|
// Simulation extension for phyiscal (behaviorual) agents
|
|
var aiosXnet = AiosXnet.call(self,aiosXsimu,Aios);
|
|
|
|
// if (this.simuPhy) ..
|
|
// always set-up API for optional physical simulation
|
|
// -> simuPhy will be usually created AFTER simuWeb!
|
|
aiosXsimu.simuPhy = aiosXsimu.phy = {
|
|
changeScene: function (scene,options) {
|
|
if (!self.simuPhy) return;
|
|
self.simuPhy.changeScene(scene,options);
|
|
},
|
|
get : function (id) {
|
|
var tokens,
|
|
obj;
|
|
if (!self.simuPhy) return;
|
|
tokens = (id||Aios.current.node.id).split(',');
|
|
obj = self.simuPhy.get(tokens.map(function (s) {return Number(s)}));
|
|
return obj;
|
|
},
|
|
refresh: function () {
|
|
if (!self.simuPhy) return;
|
|
self.simuPhy.refresh();
|
|
},
|
|
step: function (n,callback) {
|
|
if (!self.simuPhy) return;
|
|
return self.simuPhy.step(n,callback);
|
|
},
|
|
stepPhyOnly: function (n,callback) {
|
|
if (!self.simuPhy) return;
|
|
self.simuPhy.stepPhyOnly(n,callback);
|
|
}
|
|
};
|
|
|
|
// Extend corefuncs in JAM analyzer ...
|
|
// MUST BE COMPLETE - else analyzer fails!
|
|
JamAnal.extend({
|
|
// keep consistent with aiosXnet.js API
|
|
net : {
|
|
obj : {
|
|
ask:{argn:[2,3]},
|
|
create:{argn:[2,3]},
|
|
die:{argn:[0,1]},
|
|
forward:{ argn:[0,1]},
|
|
get:{argn:1},
|
|
globals:{argn:0},
|
|
group : { obj : {
|
|
add: {argn:[2,3] },
|
|
rm: {argn:2 }
|
|
}},
|
|
set:{argn:2},
|
|
setxy:{argn:2},
|
|
turn:{ argn:1},
|
|
within:{ argn:2},
|
|
}
|
|
},
|
|
simu:{
|
|
obj:{
|
|
chat : {
|
|
obj : {
|
|
agent:{argn:1},
|
|
message:{argn:2},
|
|
question:{argn:5},
|
|
script:{obj:{
|
|
init:{argn:2},
|
|
next:{argn:0},
|
|
cancel:{argn:0},
|
|
reset:{argn:0}
|
|
}}
|
|
}
|
|
},
|
|
changeVisual:{argn:2},
|
|
clear:{argn:[0,1,2]},
|
|
createNode:{argn:[1,2,3,4,5,6,7,8,9]},
|
|
createOn:{argn:[2,3,4]},
|
|
csv:{ obj:{
|
|
read:{argn:[1,2,3]},
|
|
write:{argn:[2,3,4]},
|
|
}},
|
|
db: { obj :{
|
|
error:{argn:1},
|
|
init:{argn:3},
|
|
createMatrix:{argn:[2,3]},
|
|
createTable:{argn:[2,3]},
|
|
drop:{argn:[2,3]},
|
|
insert:{argn:[3,4]},
|
|
}},
|
|
deleteNode:{argn:1},
|
|
event:{obj:{add:{argn:1},get:{argn:0}}},
|
|
get:{argn:1},
|
|
getNode:{argn:1},
|
|
getStats:{argn:0},
|
|
getSteps:{argn:0},
|
|
inspect:{argn:[1,2,3,4,5,6,7,8,9]},
|
|
load:{argn:1},
|
|
log:{argn:1},
|
|
message:{argn:1},
|
|
model:{argn:0},
|
|
move: {argn:[3,4]},
|
|
moveTo: {argn:[3,4]},
|
|
ofJSON: {argn:1},
|
|
parameter:{argn:0},
|
|
position : {argn:0},
|
|
phy : {
|
|
obj : {
|
|
changeScene:{argn:2},
|
|
get:{argn:1},
|
|
refresh:{argn:0},
|
|
step:{argn:[1,2]},
|
|
stepPhyOnly:{argn:[1,2]},
|
|
}
|
|
},
|
|
save:{argn:2},
|
|
set: {argn:2},
|
|
simuPhy : {
|
|
obj : {
|
|
changeScene:{argn:2},
|
|
get:{argn:1},
|
|
refresh:{argn:0},
|
|
step:{argn:[1,2]},
|
|
stepPhyOnly:{argn:[1,2]},
|
|
}
|
|
},
|
|
sql : {
|
|
init : {argn:1},
|
|
},
|
|
start: {argn:1},
|
|
stat: {argn:2},
|
|
stop: {argn:0},
|
|
toJSON: {argn:1},
|
|
time: {argn:0},
|
|
units: {obj:{gps2dist:{argn:2}}},
|
|
utime:{argn:0},
|
|
}
|
|
}
|
|
});
|
|
|
|
// Extended sandbox environment available for all agents in simulation
|
|
this.aiosX = aiosXsimu;
|
|
// Aios.aios0.simu=this.aiosX;
|
|
// Aios.aios1.simu=this.aiosX;
|
|
Aios.aios2.simu=this.aiosX;
|
|
Aios.aios3.simu=this.aiosX;
|
|
// only immobile level-3 phyiscal agents (on mobile nodes) can use the net API
|
|
Aios.aios3.net=aiosXnet;
|
|
|
|
if (options.aios) {
|
|
// Extend AIOS environment(s)
|
|
for (p in options.aios) {
|
|
if (!Aios.aios0[p]) Aios.aios0[p]=options.aios[p];
|
|
if (!Aios.aios1[p]) Aios.aios1[p]=options.aios[p];
|
|
if (!Aios.aios2[p]) Aios.aios2[p]=options.aios[p];
|
|
if (!Aios.aios3[p]) Aios.aios3[p]=options.aios[p];
|
|
}
|
|
}
|
|
if (options.aios0) {
|
|
// Extend AIOS environment(s)
|
|
for (p in options.aios0) {
|
|
if (!Aios.aios0[p]) Aios.aios0[p]=options.aios[p];
|
|
}
|
|
}
|
|
if (options.aios1) {
|
|
// Extend AIOS environment(s)
|
|
for (p in options.aios1) {
|
|
if (!Aios.aios1[p]) Aios.aios1[p]=options.aios[p];
|
|
}
|
|
}
|
|
if (options.aios2) {
|
|
// Extend AIOS environment(s)
|
|
for (p in options.aios2) {
|
|
if (!Aios.aios2[p]) Aios.aios2[p]=options.aios[p];
|
|
}
|
|
}
|
|
if (options.aios3) {
|
|
// Extend AIOS environment(s)
|
|
for (p in options.aios3) {
|
|
if (!Aios.aios3[p]) Aios.aios3[p]=options.aios[p];
|
|
}
|
|
}
|
|
this.delay=0;
|
|
|
|
this.out('[SIM] Simulation created.');
|
|
}
|
|
|
|
|
|
simu.prototype.analyze=JamAnal.jamc.prototype.analyze;
|
|
simu.prototype.syntax=JamAnal.jamc.prototype.syntax;
|
|
|
|
/** Add an agent template class
|
|
*
|
|
*/
|
|
simu.prototype.addClass = function (constructor,name,visual) {
|
|
var self=this,
|
|
modu,
|
|
content,
|
|
off,
|
|
syntax,
|
|
regex1,
|
|
env={},
|
|
interface,
|
|
p,
|
|
text;
|
|
|
|
try {
|
|
content = 'var ac = '+constructor;
|
|
this.out('Parsing agent class template "'+name+'" ...');
|
|
syntax = Esprima.parse(content, { tolerant: true, loc:true });
|
|
if (syntax.errors && syntax.errors.length>0) {
|
|
throw syntax.errors[0];
|
|
}
|
|
interface=this.analyze(syntax,{classname:name,level:3,verbose:1,err:function (msg) { self.out(msg); throw msg}});
|
|
this.out('Agent class template "'+name+'" analyzed successfully.');
|
|
text=Json.stringify(constructor);
|
|
// regex1= /this\.next=([a-zA-Z0-9_]+)/;
|
|
// text=text.replace(regex1,"this.next='$1'");
|
|
// console.log(text);
|
|
for (p in interface.activities) env[p]=p;
|
|
with (env) { eval('constructor='+text) };
|
|
this.out('Agent class template "'+name+'" compiled successfully.');
|
|
this.out('Adding agent class constructor "'+name+'" ...');
|
|
this.world.classes[name]=[
|
|
Aios.Code.makeSandbox(constructor,0,env),
|
|
Aios.Code.makeSandbox(constructor,1,env),
|
|
Aios.Code.makeSandbox(constructor,2,env),
|
|
Aios.Code.makeSandbox(constructor,3,env)
|
|
];
|
|
this.world.classes[name].visual=visual;
|
|
} catch (e) {
|
|
this.out('Agent class template "'+name+'" not compiled successfully: '+e);
|
|
this.gui.message(e);
|
|
if (this.verbose>1) this.out(Io.sprintstack(e));
|
|
}
|
|
|
|
};
|
|
|
|
/** Check coordinates in boundaries of the current world
|
|
*
|
|
*/
|
|
simu.prototype.checkBounds = function (x,y,z) {
|
|
if (x < 0 || y < 0) return false;
|
|
if (this.options.x && x >= this.options.x) return false;
|
|
if (this.options.y && y >= this.options.y) return false;
|
|
return true;
|
|
}
|
|
|
|
simu.prototype.checkVisual = function (v) {
|
|
if (!v) return 'missing visual';
|
|
if (['circle','rect','icon'].indexOf(v.shape)<0) return 'invalid shape';
|
|
}
|
|
|
|
/** Convert coordinates from one unit system to another
|
|
*
|
|
*/
|
|
simu.prototype.convertUnit = function (p,from,to) {
|
|
if (Comp.obj.isString(p)) {
|
|
// Convert to px (delta only)
|
|
if (Comp.string.endsWith(p,'m'))
|
|
return this.world.units.geo2px({delta:true,X:Comp.string.prefix(p,'m')}).x;
|
|
else if (Comp.string.endsWith(p,'\B0'))
|
|
return this.world.units.gps2px({delta:true,longitude:Comp.string.prefix(p,'\B0')}).x
|
|
else if (Comp.string.endsWith(p,'px'))
|
|
return Number(Comp.string.prefix(p,'px'));
|
|
else Number(p);
|
|
} else switch (from) {
|
|
case 'px':
|
|
switch (to) {
|
|
case 'px':
|
|
return p;
|
|
case 'm':
|
|
if (this.world.units.px2geo) return this.world.units.px2geo(p);
|
|
break;
|
|
case '\B0':
|
|
if (this.world.units.px2gps) return this.world.units.px2gps(p);
|
|
break;
|
|
}
|
|
break;
|
|
case 'm':
|
|
switch (to) {
|
|
case 'px':
|
|
if (this.world.units.geo2px) return this.world.units.geo2px(p);
|
|
break;
|
|
case 'm':
|
|
return p;
|
|
case '\B0':
|
|
if (this.world.units.geo2gps) return this.world.units.geo2gps(p);
|
|
break;
|
|
}
|
|
break;
|
|
case '\B0':
|
|
switch (to) {
|
|
case 'px':
|
|
if (this.world.units.gps2px) return this.world.units.gps2px(p);
|
|
break;
|
|
case 'm':
|
|
if (this.world.units.gps2geo) return this.world.units.gps2geo(p);
|
|
break;
|
|
case '\B0':
|
|
return p;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Create a regular (or irregular) 2d mesh grid of nodes and connections.
|
|
* type of options = {width: number as node width in pixel,
|
|
height: number as node heigth in pixel,
|
|
rows:number,cols:number,level?:number,
|
|
node : { visual },
|
|
port?, link?,
|
|
map?,
|
|
connections?}
|
|
*/
|
|
simu.prototype.createMeshNetwork = function (model) {
|
|
var self=this,
|
|
options=model.world.meshgrid;
|
|
if (!options.node) return this.out('Incomplete meshgrid model!');
|
|
var margin_x = options.node.visual.width/2,
|
|
margin_y = options.node.visual.height/2,
|
|
space_x = options.node.visual.width/2,
|
|
space_y = options.node.visual.height/2,
|
|
off_x = options.off_x||5,
|
|
off_y = options.off_y||5,
|
|
xn,yn,x,y,w,h,p,obj,obj2,link,
|
|
node,world,node1,node2,place,port,ports={},
|
|
id,i,j,k,level=0,dim=2,
|
|
xMoff=0,yMoff=0;
|
|
|
|
if (options.levels) dim=3;
|
|
if (!options.matrix) this.gui.level=this.gui.level.map(function (x,i) {
|
|
return i==0?1:0;
|
|
}); else this.gui.level=this.gui.level.map(function (x,i) {
|
|
return 1;
|
|
});
|
|
this.gui.level[-1]=1;
|
|
|
|
this.out('Creating meshgrid network with '+options.rows+
|
|
' rows and '+options.cols+
|
|
' columns on '+(options.levels?options.levels:1)+
|
|
' levels.');
|
|
|
|
this.options.x=options.cols;
|
|
this.options.y=options.rows||0;
|
|
this.options.z=options.levels||0;
|
|
|
|
function makeRef(o) {return o};
|
|
function vec2str(vec) {
|
|
if (self.options.z) return vec.join(',');
|
|
else if (self.options.y) return vec.slice(0,2).join(',');
|
|
else return vec[0] }
|
|
|
|
for(level=0;level<(options.levels?options.levels:1);level++) {
|
|
if (options.matrix && options.matrix[level]) {
|
|
xMoff=options.matrix[level][0];
|
|
yMoff=options.matrix[level][1];
|
|
}
|
|
// 1. Nodes
|
|
for(j=0;j<options.rows;j++) {
|
|
for(i=0;i<options.cols;i++) {
|
|
if (options.node.filter && !options.node.filter([i,j,level])) continue;
|
|
id=vec2str([i,j,level]);
|
|
node=Aios.Node.Node({id:id,
|
|
position:{x:i,y:j,z:(dim==3?level:undefined)},
|
|
TMO:self.options.TMO||1000000
|
|
});
|
|
id='node['+id+']';
|
|
this.world.addNode(node);
|
|
if (this.verbose>1) this.log('[SIM] Created node '+node.id);
|
|
xn=off_x+margin_x+i*(options.node.visual.width+space_x)+xMoff;
|
|
yn=off_y+margin_y+j*(options.node.visual.height+space_y)+yMoff;
|
|
this.gui.objects[id] = { visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:options.node.visual.shape||'rect',
|
|
w:options.node.visual.width,
|
|
h:options.node.visual.height,
|
|
fill:options.node.visual.fill,
|
|
line:options.node.visual.line,
|
|
level:level
|
|
},
|
|
obj:makeRef(node),
|
|
id:id
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Link ports of nodes
|
|
if (options.port && options.port.place) {
|
|
for(i in this.world.nodes) {
|
|
node=this.world.nodes[i];
|
|
id='node['+node.id+']';
|
|
obj=this.gui.objects[id];
|
|
place=options.port.place(node);
|
|
if (place && obj) {
|
|
for(j in place) {
|
|
xn=obj.visual.x+place[j].x-options.port.visual.width/2+obj.visual.w/2;
|
|
yn=obj.visual.y+place[j].y-options.port.visual.height/2+obj.visual.h/2;
|
|
id='port['+node.id+':'+place[j].id+']';
|
|
if (!ports[node.id]) ports[node.id]={};
|
|
obj2={node:makeRef(node)};
|
|
ports[node.id]['DIR.'+place[j].id]=obj2;
|
|
this.gui.objects[id] = { visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:options.port.visual.shape||'rect',
|
|
w:options.port.visual.width,
|
|
h:options.port.visual.height,
|
|
fill:options.port.visual.fill,
|
|
line:options.port.visual.line,
|
|
level:obj.visual.level
|
|
},
|
|
obj:obj2,
|
|
id:id
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
// 3. Links between nodes
|
|
// Only nodes having ports can be connected by links!
|
|
if (options.link) {
|
|
function port(i,j,k,dir) {
|
|
var p=ports[vec2str([i,j,k])];
|
|
if (!p) return false;
|
|
return p[dir];
|
|
}
|
|
function setPort(i,j,k,dir,link) {
|
|
var p=ports[vec2str([i,j,k])];
|
|
if (!p) return ;
|
|
p[dir].link=link;
|
|
}
|
|
for(k=0;k<(options.levels?options.levels:1);k++) {
|
|
if (options.matrix && options.matrix[k]) {
|
|
xMoff=options.matrix[k][0];
|
|
yMoff=options.matrix[k][1];
|
|
}
|
|
for(j=0;j<options.rows;j++) {
|
|
for(i=0;i<options.cols;i++) {
|
|
if (options.node.filter && !options.node.filter([i,j,k])) continue;
|
|
xn=off_x+xMoff+margin_x+i*(options.node.visual.width+space_x);
|
|
yn=off_y+yMoff+margin_y+j*(options.node.visual.height+space_y);
|
|
if ((i+1)<options.cols) {
|
|
if (!options.link.connect ||
|
|
options.link.connect([i,j,k],[i+1,j,k])) {
|
|
node1=self.world.getNode(vec2str([i,j,k]));
|
|
node2=self.world.getNode(vec2str([i+1,j,k]));
|
|
if (port(i,j,k,Aios.DIR.EAST) && port(i+1,j,k,Aios.DIR.WEST) && node2) {
|
|
link=this.world.connect(Aios.DIR.EAST,node1,node2);
|
|
setPort(i,j,k,Aios.DIR.EAST,link);
|
|
setPort(i+1,j,k,Aios.DIR.WEST,link);
|
|
id='link['+vec2str([i,j,k])+','+vec2str([i+1,j,k])+']';
|
|
w=margin_x;
|
|
h=options.link.visual.width||2;
|
|
x=xn+options.node.visual.width;
|
|
y=yn+space_y-h/2;
|
|
this.gui.objects[id] = {visual:{
|
|
x:x,
|
|
y:y,
|
|
shape:options.link.visual.shape||'rect',
|
|
w:w,
|
|
h:h,
|
|
fill:options.link.visual.fill,
|
|
line:options.link.visual.line,
|
|
level:k
|
|
},obj:{node1:node1,node2:node2},id:id}
|
|
}
|
|
}
|
|
}
|
|
if ((j+1)<options.rows) {
|
|
if (!options.link.connect ||
|
|
options.link.connect([i,j,k],[i,j+1,k])) {
|
|
node1=self.world.getNode(vec2str([i,j,k]));
|
|
node2=self.world.getNode(vec2str([i,j+1,k]));
|
|
if (port(i,j,k,Aios.DIR.SOUTH) && port(i,j+1,k,Aios.DIR.NORTH) && node2) {
|
|
link=this.world.connect(Aios.DIR.SOUTH,node1,node2);
|
|
setPort(i,j,k,Aios.DIR.SOUTH,link);
|
|
setPort(i,j+1,k,Aios.DIR.NORTH,link);
|
|
id='link['+vec2str([i,j,k])+','+vec2str([i,j+1,k])+']';
|
|
w=options.link.visual.width||2;
|
|
h=margin_x;
|
|
x=xn+space_x-w/2;
|
|
y=yn+options.node.visual.height;
|
|
this.gui.objects[id] = {visual:{
|
|
x:x,
|
|
y:y,
|
|
shape:options.link.visual.shape,
|
|
w:w,
|
|
h:h,
|
|
fill:options.link.visual.fill,
|
|
line:options.link.visual.line,
|
|
level:k
|
|
},obj:{node1:node1,node2:node2},id:id}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(k=0;k<(options.levels?options.levels-1:0);k++) {
|
|
for(j=0;j<options.rows;j++) {
|
|
for(i=0;i<options.cols;i++) {
|
|
if (options.node.filter && !options.node.filter([i,j,k])) continue;
|
|
xn=off_x+margin_x+i*(options.node.visual.width+space_x);
|
|
yn=off_y+margin_y+j*(options.node.visual.height+space_y);
|
|
if (!options.link.connect ||
|
|
options.link.connect([i,j,k],[i,j,k+1])) {
|
|
if (!port(i,j,k,Aios.DIR.UP) || !port(i,j,k+1,Aios.DIR.DOWN)) continue;
|
|
node1=self.world.getNode(vec2str([i,j,k]));
|
|
node2=self.world.getNode(vec2str([i,j,k+1]));
|
|
if (!node2) continue;
|
|
link=this.world.connect(Aios.DIR.UP,node1,node2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 4. World
|
|
if (!model.world.map && !model.world.nodes) {
|
|
id='world';
|
|
node=Aios.Node.Node({id:id,
|
|
position:{},
|
|
TMO:self.options.TMO||1000000
|
|
});
|
|
this.world.addNode(node);
|
|
if (this.verbose>1) this.log('[SIM] Created node '+node.id);
|
|
id='node['+id+']';
|
|
this.gui.objects[id] = {visual:{
|
|
x:margin_x+off_x+xn+space_x*1.5-4,
|
|
y:margin_y+off_y+yn+space_y*1.5-4,
|
|
shape:'rect',
|
|
w:margin_x,
|
|
h:margin_y,
|
|
fill:(options.world&&options.world.visual&&options.world.visual.fill)||{color:'black'},
|
|
line:(options.world&&options.world.visual&&options.world.visual.line)||{width:2}
|
|
},obj:this.world,id:id};
|
|
id='world-anchor';
|
|
this.gui.objects[id] = {visual:{
|
|
x:off_x+1,
|
|
y:off_y+1,
|
|
shape:'rect',
|
|
w:margin_x+xn+space_x*1.5,
|
|
h:margin_y+yn+space_y*1.5,
|
|
line:(options.world&&options.world.visual&&options.world.visual.line)||{width:1,color:'#BBB'}
|
|
},obj:this.world,id:id};
|
|
}
|
|
}
|
|
|
|
/** Create a node in the simulation world
|
|
*
|
|
*/
|
|
simu.prototype.createNode = function (desc,drawnow) {
|
|
var self=this,id,index,
|
|
node,nodeid,port,pos,drawpos,xn,yn,x,y,p,options,chan,addr;
|
|
|
|
desc.id=desc.id.replace(/\$WORLD/,this.world.id);
|
|
|
|
function makeRef(x) {return x};
|
|
if (desc.id == undefined)
|
|
throw 'createNpde: Invalid node descriptor, missing id attribute';
|
|
if (desc.gps) {
|
|
pos=self.world.units.gps2px(desc.gps);
|
|
desc.x=pos.x; desc.y=pos.y; desc.z=pos.z;
|
|
} else if (desc.geo) {
|
|
pos=self.world.units.m2px(desc.geo);
|
|
desc.x=pos.x; desc.y=pos.y; desc.z=pos.z;
|
|
}
|
|
if (desc.x == undefined || desc.y == undefined)
|
|
throw 'createNode: Invalid node descriptor, missing coordinates';
|
|
|
|
id = 'node['+desc.id+']';
|
|
if (this.gui.objects[id])
|
|
throw 'createNode: Node '+desc.id+' exists already!';
|
|
pos = {x:desc.x,y:desc.y};
|
|
// Patch world model?
|
|
if (desc.gps) pos.gps=desc.gps;
|
|
if (desc.geo) pos.geo=desc.geo;
|
|
|
|
node=Aios.Node.Node({id:desc.id,position:pos,parameter:desc.parameter||{}});
|
|
|
|
nodeid=node.id;
|
|
if (desc.location) node.location=desc.location;
|
|
this.world.addNode(node);
|
|
if (this.verbose>1) this.log('[SIM] Created node '+node.id);
|
|
xn=this.world2draw(desc).x+checkOption(desc.visual.center && desc.visual.center.x,0);
|
|
yn=this.world2draw(desc).y+checkOption(desc.visual.center && desc.visual.center.y,0);
|
|
|
|
if (Comp.obj.isString(desc.visual.width)) desc.visual.width=this.convertUnit(desc.visual.width);
|
|
if (Comp.obj.isString(desc.visual.height)) desc.visual.height=this.convertUnit(desc.visual.height);
|
|
if (Comp.obj.isString(desc.visual.radius)) desc.visual.width=desc.visual.height=this.convertUnit(desc.visual.radius)*2;
|
|
this.gui.objects[id] = {
|
|
visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:desc.visual.shape||'rect',
|
|
icon:desc.visual.icon,
|
|
w:desc.visual.width,
|
|
h:desc.visual.height,
|
|
fill:desc.visual.fill,
|
|
line:desc.visual.line,
|
|
align:desc.visual.shape=='circle'?'center':desc.visual.align,
|
|
},
|
|
obj:makeRef(node),
|
|
id:id
|
|
};
|
|
|
|
if (desc.visual.label) {
|
|
self.gui.objects['label['+desc.id+']'] = {
|
|
visual:{
|
|
x:xn,
|
|
y:yn-5-(desc.visual.label.fontSize||24),
|
|
shape:'text',
|
|
fontSize:desc.visual.label.fontSize||24,
|
|
color:desc.visual.label.color,
|
|
text:desc.visual.label.text||desc.visual.label,
|
|
},id:'label['+desc.id+']'
|
|
}
|
|
if (drawnow) this.gui.drawObject('label['+desc.id+']');
|
|
}
|
|
if (drawnow) this.gui.drawObject(id);
|
|
|
|
|
|
// Node virtual and physical communication ports
|
|
if (desc.port) {
|
|
// backward compatibility
|
|
if (Comp.obj.isArray(desc.port))
|
|
desc.ports = desc.port;
|
|
else
|
|
desc.ports = [desc.port];
|
|
}
|
|
if (desc.ports) for (index in desc.ports) {
|
|
port=desc.ports[index];
|
|
if (!port) continue;
|
|
// Create a virtual link port object
|
|
switch (port.type) {
|
|
case 'multicast':
|
|
chan = (function (port) {
|
|
var chan = {
|
|
_connected:[],
|
|
_count:0,
|
|
_state:true,
|
|
|
|
count:function () {return chan._count},
|
|
/** Send agent snapshot to destination node.
|
|
* 'dest' must be a node identifier!
|
|
*/
|
|
/* OLDCOMM send: function (data,dest,current) { */
|
|
/* NEWCOMM */
|
|
send: function (msg) {
|
|
var node;
|
|
//console.debug('chan.vc.send',msg)
|
|
if (!chan.status()) return;
|
|
if (Comp.array.contains(chan._connected,msg.to.node /*dest*/)) {
|
|
node=self.world.getNode(msg.to.node /*dest*/);
|
|
if (!node) return;
|
|
//console.debug('chan.vc.send',msg,node)
|
|
if (msg.agent)
|
|
node.receive(msg.agent /*data*/);
|
|
else if (msg.signal)
|
|
node.handle(msg.signal);
|
|
chan._count += ((msg.agent||msg.signal).length||1);
|
|
if (msg.context) msg.context.kill=true;
|
|
} else {
|
|
// Throw exception??
|
|
if (msg.context) msg.context.kill=true;
|
|
}
|
|
},
|
|
// Enable or disable communication port (default: on)
|
|
getState: function () { return chan._state },
|
|
label:desc.id+(port.id?(':'+port.id):''),
|
|
setState: function (on) { chan._state=on },
|
|
/** Check connection status and/or return available connected or reachable nodes (links).
|
|
*
|
|
*/
|
|
status: function (path) {
|
|
// self.log('status state='+chan._state);
|
|
if (!chan._state) {
|
|
chan._connected=[];
|
|
return none;
|
|
}
|
|
// Find all overlapping connections
|
|
var port1='port['+node.id+']',objs;
|
|
// self.log(port1);
|
|
if (port1) {
|
|
objs=self.gui.overlapObjects(port1);
|
|
}
|
|
// self.log(objs);
|
|
if (port.status) objs=port.status(objs);
|
|
if (objs && objs.length>0) {
|
|
// Return all connected nodes/links
|
|
chan._connected=Comp.array.map(objs,function (o) {
|
|
return o.replace(/port\[([^\]]+)\]/,'$1');
|
|
});
|
|
return chan._connected;
|
|
} else {
|
|
chan._connected=[];
|
|
return none;
|
|
}
|
|
},
|
|
virtual:true
|
|
}; return chan})(port);
|
|
// TODO multiple ports
|
|
node.connections.path=chan;
|
|
x=xn; y=yn;
|
|
id='port['+chan.label+']';
|
|
if (!port.visual) port.visual={
|
|
w:desc.visual.width*2,
|
|
h:desc.visual.height*2
|
|
};
|
|
if (port.visual.line && !port.visual.line.width)
|
|
port.visual.line.width=2;
|
|
port.visual.x=x+desc.visual.width/2;
|
|
port.visual.y=y+desc.visual.height/2;
|
|
if (Comp.obj.isString(port.visual.width))
|
|
port.visual.width=self.convertUnit(port.visual.width);
|
|
if (Comp.obj.isString(port.visual.height))
|
|
port.visual.height=self.convertUnit(port.visual.height);
|
|
if (Comp.obj.isString(port.visual.radius))
|
|
port.visual.width=
|
|
port.visual.height=self.convertUnit(port.visual.radius)*2;
|
|
if (Comp.obj.isNumber(port.visual.radius))
|
|
port.visual.width=
|
|
port.visual.height=port.visual.radius*2;
|
|
self.gui.objects[id] = {visual:{
|
|
x:port.visual.x,
|
|
y:port.visual.y,
|
|
shape:port.visual.shape||'circle',
|
|
w:port.visual.width,
|
|
h:port.visual.height,
|
|
fill:port.visual.fill,
|
|
line:port.visual.line
|
|
},obj:chan,id:id}
|
|
if (drawnow) this.gui.drawObject(id);
|
|
break;
|
|
|
|
case 'physical':
|
|
// Physical communication ports
|
|
options={verbose:1,proto:'http',multicast:true};
|
|
if (port.verbose!=undefined) options.verbose=port.verbose;
|
|
if (port.proto != undefined) options.proto=port.proto;
|
|
if (port.multicast != undefined) options.multicast=port.multicast;
|
|
if (port.broker) options.broker=port.broker;
|
|
if (options.broker) options.multicast=false;
|
|
|
|
if (port.port) addr=port.ip+':'+port.port; else addr=port.ip;
|
|
options.from=addr;
|
|
this.log('[SIM] Creating physical port IP('+addr+') '+Io.inspect(options)+' on node '+node.id);
|
|
chan=this.world.connectPhy(
|
|
Aios.DIR.IP(addr),
|
|
node,
|
|
{
|
|
broker:options.broker,
|
|
multicast:options.multicast,
|
|
name:options.name,
|
|
on:options.on,
|
|
oneway:options.oneway,
|
|
proto:options.proto,
|
|
rcv:options.from,
|
|
snd:options.to,
|
|
verbose:options.verbose
|
|
});
|
|
chan.init();
|
|
chan.start();
|
|
node.connections.ip.chan=chan;
|
|
if (port.to) this.world.connectTo(Aios.DIR.IP(port.to),node);
|
|
break;
|
|
}
|
|
|
|
}
|
|
if (this.verbose>1) this.log('[SIM] Created node '+node.id);
|
|
return nodeid;
|
|
}
|
|
/** Create a regular (or irregular) 2d patch grid of nodes and connections.
|
|
* type of options = {
|
|
width: number as node width in pixel,
|
|
height: number as node heigth in pixel,
|
|
visual?, // if not set, model.patches.default is required
|
|
rows:number,cols:number,
|
|
floating?:boolean, // if false/undef. grid consists of logical nodes, else resources
|
|
}
|
|
*/
|
|
simu.prototype.createPatchWorld = function (model,resources) {
|
|
var self=this,
|
|
i,j,row,
|
|
options=model.world.patchgrid,
|
|
patch = options.visual || (model.patches && model.patches.default && model.patches.default.visual);
|
|
if (!model.nodes || !model.nodes.world)
|
|
this.err('Incomplete patchgrid model (requires model.nodes.world); no world node found');
|
|
|
|
if (patch) options.visual = patch;
|
|
|
|
var width = (options.visual&&options.visual.width)||options.width||10,
|
|
height = (options.visual&&options.visual.height)||options.height||10,
|
|
margin_x = 0,
|
|
margin_y = 0,
|
|
off_x = 0,
|
|
off_y = 0,
|
|
xn,yn,x,y,w,h,p,obj,obj2,link,
|
|
node,world,node1,node2,place,port,ports={},
|
|
id,i,j,k,level=0,dim=2,
|
|
xMoff=0,yMoff=0;
|
|
|
|
function makeRef(o) {return o};
|
|
function vec2str(vec) { return vec.join(',') }
|
|
|
|
this.out('Creating patchgrid network with '+options.rows+
|
|
' rows and '+options.cols+
|
|
' columns');
|
|
|
|
this.options.x=options.cols;
|
|
this.options.y=options.rows;
|
|
|
|
// A world map for fast search of physical agents(nodes)
|
|
this.agentMap = [];
|
|
// A world map for fast search of physical resources
|
|
this.resourceMap = [];
|
|
|
|
for (j=0;j<options.rows*2;j++) {
|
|
row=[];
|
|
for(i=0;i<options.cols*2;i++)
|
|
row.push({})
|
|
this.agentMap.push(row);
|
|
}
|
|
for (j=0;j<options.rows*2;j++) {
|
|
row=[];
|
|
for(i=0;i<options.cols*2;i++)
|
|
row.push({})
|
|
this.resourceMap.push(row);
|
|
}
|
|
// Patches parameter variables
|
|
this.patches = [];
|
|
for (j=0;j<options.rows;j++) {
|
|
row=[];
|
|
for(i=0;i<options.cols;i++)
|
|
row.push({x:i,y:j})
|
|
this.patches.push(row);
|
|
}
|
|
// Draw resources before any other objects
|
|
// Resources have currently patch grid coordinates needing transformation!
|
|
resources.forEach(function (r) {
|
|
if (r.visual.shape == 'circle') r.visual.x += r.visual.width/2, r.visual.y += r.visual.height/2
|
|
r.visual.x *= width; r.visual.y *= height;
|
|
if (r.visual.width) r.visual.width *= width;
|
|
if (r.visual.height) r.visual.height *= height;
|
|
})
|
|
this.options.patch = {rows:options.rows, cols:options.cols, width:width, height:height}
|
|
|
|
// 1. Patches
|
|
if (patch) {
|
|
this.createResources(resources);
|
|
|
|
for(j=0;j<options.rows;j++) {
|
|
for(i=0;i<options.cols;i++) {
|
|
id=vec2str([i,j]);
|
|
if (!options.floating) {
|
|
// patch == node
|
|
node=Aios.Node.Node({id:id,
|
|
position:{x:i,y:j},
|
|
TMO:self.options.TMO||1000000
|
|
});
|
|
if (this.verbose>1) this.log('[SIM] Created node '+node.id);
|
|
this.world.addNode(node);
|
|
} // else patch == resource
|
|
if (options.floating) id = 'patch['+id+']'; else id='node['+id+']';
|
|
xn=off_x+margin_x+i*width+xMoff;
|
|
yn=off_y+margin_y+j*height+yMoff;
|
|
this.gui.objects[id] = { visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:options.visual.shape||'rect',
|
|
w:width,
|
|
h:height,
|
|
fill:options.visual.fill,
|
|
line:options.visual.line,
|
|
},
|
|
obj:{parameter:{}},
|
|
id:id
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Default patchgrid visual: frame boundary + grid pattern
|
|
xn=off_x+margin_x+xMoff;
|
|
yn=off_y+margin_y+yMoff;
|
|
id='patch[world]';
|
|
this.gui.objects[id] = { visual:{
|
|
x:xn,
|
|
y:yn,
|
|
shape:'rect',
|
|
pattern:{
|
|
shape:'rect',
|
|
w:options.width,
|
|
h:options.width,
|
|
color:"1 #ddd 0.9"
|
|
},
|
|
w:width*options.cols,
|
|
h:height*options.rows,
|
|
fill:null,
|
|
line:{color:'#888'},
|
|
},
|
|
obj:{parameter:{},patches:self.patches},
|
|
id:id
|
|
}
|
|
// Draw resources before any other objects
|
|
this.createResources(resources);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/** Create a physical communication port
|
|
*
|
|
*/
|
|
simu.prototype.createPort = function (dir,options,nodeid) {
|
|
if (!options) options={};
|
|
var multicast=options.multicast||true;
|
|
if (dir.tag != Aios.DIR.tag.IP) {}
|
|
if (options.from==undefined && dir.ip) options.from=dir.ip.toString();
|
|
var chan=this.world.connectPhy(
|
|
dir,
|
|
this.getNode(nodeid),
|
|
{
|
|
broker:options.broker,
|
|
multicast:multicast,
|
|
name:options.name,
|
|
on:options.on,
|
|
oneway:options.oneway,
|
|
proto:options.proto||'udp',
|
|
rcv:options.from,
|
|
snd:options.to,
|
|
verbose:options.verbose||this.verbose
|
|
});
|
|
chan.init();
|
|
chan.start();
|
|
return chan;
|
|
}
|
|
|
|
/** Create unit conversion functions from a map object
|
|
*
|
|
*/
|
|
simu.prototype.createUnits = function (map) {
|
|
/*
|
|
** 0---x latitude Y
|
|
** | PX | GPS | GEO
|
|
** | GUI | |
|
|
** y +----- longitude 0-----X
|
|
*/
|
|
// Geographic units using map parameters {offset,scale}
|
|
this.world.units.gps2px = function (gps) {
|
|
if (!gps.delta) {
|
|
if (!map.area) area={x:0,y:0};
|
|
else if (!map.area.selector) area={x:map.area.x||0,y:map.area.y||0};
|
|
else area=map.area[map.area.selector(gps)];
|
|
return {
|
|
x:area.x+((gps.longitude-map.longitude.offset)*map.longitude.scale)|0,
|
|
y:area.y-((gps.latitude-map.latitude.offset)*map.latitude.scale)|0,
|
|
z:gps.height*(map.height?map.height.scale:1)
|
|
};
|
|
} else
|
|
// delta coord.
|
|
return {
|
|
x:(gps.longitude*map.longitude.scale)|0,
|
|
y:-(gps.latitude*map.latitude.scale)|0,
|
|
z:gps.height*(map.height?map.height.scale:1)
|
|
};
|
|
};
|
|
this.world.units.px2gps = function (px) {
|
|
if (!px.delta) {
|
|
if (!map.area) area={x:0,y:0};
|
|
else if (!map.area.selector) area={x:map.area.x||0,y:map.area.y||0};
|
|
else area=map.area[map.area.selector(px)];
|
|
return {
|
|
latitude: -(px.y-area.y)/map.longitude.scale+map.longitude.offset,
|
|
longitude: (px.x-area.x)/map.latitude.scale+map.latitude.offset
|
|
}
|
|
} else
|
|
// delta;
|
|
return {
|
|
latitude: -px.y/map.longitude.scale,
|
|
longitude: px.x/map.latitude.scale
|
|
}
|
|
};
|
|
this.world.units.gps2geo = function (gps) {
|
|
// Only for delta vectors
|
|
if (gps.delta) return {
|
|
X:gps.longitude*111320,
|
|
Y:gps.latitude*111320,
|
|
Z:gps.height
|
|
}
|
|
},
|
|
this.world.units.geo2gps = function (geo) {
|
|
// Only for delta vectors
|
|
if (geo.delta) return {
|
|
latitude: geo.Y/113200,
|
|
longitude: geo.X/113200,
|
|
height: geo.Z
|
|
}
|
|
};
|
|
|
|
// Geometric units using map parameters {offset,scale}
|
|
this.world.units.geo2px = function (geo) {
|
|
if (geo.delta) return {
|
|
x:(geo.X)*(map.X.scale||1),
|
|
y:(geo.Y)*(map.Y.scale||1),
|
|
z:(geo.Z)*(map.Z.scale||1)
|
|
}; else return {
|
|
x:(geo.X-map.X.offset||0)*(map.X.scale||1),
|
|
y:(geo.Y-map.Y.offset||0)*(map.Y.scale||1),
|
|
z:(geo.Z-map.Z.offset||0)*(map.Z.scale||1)
|
|
}
|
|
};
|
|
this.world.units.px2geo = function (px) {
|
|
if (px.delta) return {
|
|
X:px.x/(map.X.scale||1),
|
|
Y:px.y/(map.Y.scale||1),
|
|
Z:px.z/(map.Z.scale||1)
|
|
}; else return {
|
|
X:px.x/(map.X.scale||1)+(map.X.offset||0),
|
|
Y:px.y/(map.Y.scale||1)+(map.Y.offset||0),
|
|
Z:px.z/(map.Z.scale||1)+(map.Z.offset||0)
|
|
}
|
|
};
|
|
function rad2deg (rad) { return rad/Math.PI*180.0 };
|
|
function deg2rad (deg) { return deg/180.0*Math.PI };
|
|
|
|
// Computer distance and angle direction between two GPS locations
|
|
this.world.units.gps2dist = function (gps1,gps2) {
|
|
var dy=(gps2.latitude-gps1.latitude)*111320,
|
|
dx=(gps2.longitude-gps1.longitude)*111320,
|
|
dz=(gps2.height-gps1.height),
|
|
angle = rad2deg(Math.atan2(dy, dx));
|
|
// Convert to 0\B0: N, 90\B0 : E, 180\B0 : S, 270\B0 : W
|
|
if (angle >=0 && angle <= 180) angle=270-angle;
|
|
else if(angle > -90) angle=-angle+270;
|
|
else angle=-angle-90;
|
|
|
|
return {dx:dx,dy:dy,dz:dz,angle:angle}
|
|
}
|
|
|
|
this.world.units.rad2deg = rad2deg;
|
|
this.world.units.deg2rad = deg2rad;
|
|
this.aiosX.units=this.world.units;
|
|
}
|
|
|
|
// Draw resources
|
|
simu.prototype.createResources = function (resources) {
|
|
var self=this,xi,yi,wi,hi;
|
|
if (resources.length) this.log('Creating '+resources.length+' resource(s)..');
|
|
Comp.array.iter(resources,function (r) {
|
|
if (r.id != undefined && r.visual) {
|
|
id='resource['+r.id+']';
|
|
self.gui.objects[id] = {
|
|
visual:{
|
|
x:r.visual.x,
|
|
y:r.visual.y,
|
|
shape:r.visual.shape||'circle',
|
|
w:r.visual.width,
|
|
h:r.visual.height,
|
|
fill:r.visual.fill,
|
|
line:r.visual.line
|
|
},obj:r.data,id:id
|
|
}
|
|
if (r.data) self.gui.objects[id].data=r.data;
|
|
if (r.class) self.gui.objects[id].class=r.class;
|
|
if (self.options.patch) {
|
|
xi=int(r.visual.x/self.options.patch.width);
|
|
yi=int(r.visual.y/self.options.patch.height);
|
|
wi=int(r.visual.width/self.options.patch.width);
|
|
hi=int(r.visual.height/self.options.patch.height);
|
|
if (yi>self.options.patch.rows || xi>self.options.patch.cols)
|
|
throw Error('createResources: invalid visual patch coordinates ('+xi+','+yi+')');
|
|
self.resourceMap[yi][xi][id]=self.gui.objects[id];
|
|
self.rtree.insert({x:xi, y:yi, w:wi, h:hi},id);
|
|
}
|
|
id='label['+r.id+']';
|
|
if (r.visual.label) self.gui.objects[id] = {
|
|
visual:{
|
|
x:r.visual.x+10,
|
|
y:r.visual.y+10,
|
|
shape:'text',
|
|
fontSize:r.visual.label.fontSize||24,
|
|
color:r.visual.label.color,
|
|
text:r.visual.label.text||r.visual.label,
|
|
},id:id
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/** Create the simulation world
|
|
*
|
|
* type of model = { name, classes, resources?, world}
|
|
* type of world = { init?, map?, nodes?, meshgrid?, patchgrid?, resources?, units? }
|
|
* type of units = { map?, gps2px?, px2gps?, geo2px?, px2gep? }
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
simu.prototype.createWorld = function (model) {
|
|
var margin_x = 5,
|
|
margin_y = 5,
|
|
off_x = 5,
|
|
off_y = 5,
|
|
node,
|
|
self=this,
|
|
id,i,j,p,res,pos,
|
|
map,area;
|
|
self.model=model;
|
|
try {
|
|
// Check model ..
|
|
if (model.agents) {
|
|
for (p in model.agents) {
|
|
if (!model.agents[p].visual) throw "Invalid model.agents."+p+": missing visual";
|
|
if (res=this.checkVisual(model.agents[p].visual)) throw "Invalid model.agents."+p+".visual: "+res+")";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (model.world.units) this.world.units=model.world.units;
|
|
if (model.world.units && model.world.units.map) this.createUnits(model.world.units.map);
|
|
|
|
// Draw resources before any other objects (lowest z level)
|
|
// model.world.resources returns array of simulation object descriptors
|
|
var resources = model.world.resources?model.world.resources(model):[];
|
|
|
|
if (!Comp.obj.isArray(resources))
|
|
throw ('odel.world.resources returned '+(typeof resources)+', expected array');
|
|
|
|
if (!model.world.patchgrid) this.createResources(resources);
|
|
if (model.world.meshgrid) this.createMeshNetwork(model);
|
|
if (model.world.patchgrid) this.createPatchWorld(model,resources);
|
|
if (model.world.nodes && typeof model.world.nodes == "function")
|
|
model.world.map=model.world.nodes; // synonym
|
|
if (model.world.map) {
|
|
// use a mapping function and node descriptors {id,x,y,link,visual}
|
|
Comp.array.iter(model.world.map(model), function (desc,i) {
|
|
self.createNode(desc);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
this.log('Failure in createWorld: '+e);
|
|
if (this.gui.options.verbose) this.log(e.stack);
|
|
}
|
|
}
|
|
|
|
// Delete a JAM node and visuals
|
|
simu.prototype.deleteNode = function (nodeid) { try {
|
|
var objid = 'node['+nodeid+']',
|
|
node = this.world.getNode(nodeid),
|
|
objs,p,port;
|
|
|
|
this.gui.destroyObject(objid);
|
|
// Remove ports of deleted node
|
|
if (node) {
|
|
for(p in {NORTH:1,SOUTH:1,WEST:1,EAST:1,UP:1,DOWN:1}) {
|
|
if (node.connections[p.toLowerCase()])
|
|
this.gui.destroyObject('port['+nodeid+':'+p+']');
|
|
}
|
|
if (node.connections.path) {
|
|
this.gui.destroyObject('port['+node.connections.path.label+']');
|
|
}
|
|
if (node.connections.ip && node.connections.ip.chann) {
|
|
// Stop physical link
|
|
node.connections.ip.chann.stop();
|
|
}
|
|
this.world.removeNode(nodeid);
|
|
node.destroy();
|
|
} else if (this.gui.options.verbose) this.log('deleteNode: No node found for id '+nodeid);
|
|
|
|
// Remove links to deleted node
|
|
objs=this.gui.findObjects('link',nodeid);
|
|
// console.debug('deleteNode',objid,node,objs)
|
|
for(p in objs) {
|
|
this.gui.destroyObject(p);
|
|
}
|
|
// Cleanup links of ports currently linked with other nodes
|
|
objs=this.gui.findObjects('port','.+');
|
|
for(p in objs) {
|
|
port=objs[p].obj;
|
|
if (port.link &&
|
|
((port.link.node1 && port.link.node1.id==nodeid)||
|
|
(port.link.node2 && port.link.node2.id==nodeid))) port.link=undefined;
|
|
}
|
|
if (this.verbose>1) this.log('[SIM] Deleted node '+nodeid);
|
|
} catch (e) { console.log(e) }
|
|
}
|
|
|
|
/** Destroy the simulation world: Destroy agents, AIOS nodes, and AIOS world.
|
|
*
|
|
*/
|
|
simu.prototype.destroy = function () {
|
|
var n,p;
|
|
for(n in this.world.nodes) {
|
|
if (!this.world.nodes[n]) continue; // ??
|
|
this.gui.objects[this.world.nodes[n].id]=undefined;
|
|
if (this.world.nodes[n].connections.ip) this.world.nodes[n].connections.ip.chan.stop();
|
|
this.world.nodes[n].destroy();
|
|
}
|
|
if (this.model.world && this.model.world.init && this.model.world.init.physics) {
|
|
if (this.simuPhy)
|
|
this.gui.destroySimuPhy();
|
|
this.simuPhy=none;
|
|
}
|
|
this.world.nodes=none;
|
|
this.world=none;
|
|
this.model=none;
|
|
if (this.Db) for(p in this.Db) this.Db[p].close();
|
|
this.Db=none;
|
|
this.log('[SIM] Simulation world destroyed');
|
|
}
|
|
/** Return agent object (or array if ais is a regex) referenced by logical node number, position, or name,
|
|
** and agent identifier.
|
|
* If @id is undefined return current node object.
|
|
*/
|
|
simu.prototype.getAgent = function (id,aid) {
|
|
var node = this.getNode(id);
|
|
if (node) return node.getAgent(aid);
|
|
}
|
|
simu.prototype.getAgentProcess = function (id,aid) {
|
|
var node = this.getNode(id);
|
|
if (node) return node.getAgentProcess(aid);
|
|
}
|
|
|
|
|
|
/** Return node object referenced by logical node number, position, or name
|
|
* If @id is undefined return current node object.
|
|
*/
|
|
simu.prototype.getNode = function (id) {
|
|
var node;
|
|
if (id==undefined) return this.world.nodes[0];
|
|
if (typeof id == 'number')
|
|
node=this.world.nodes[id];
|
|
else if (typeof id == 'string') {
|
|
// Search node identifier;
|
|
node = this.world.getNode(id);
|
|
} else if (id.x != undefined &&
|
|
id.y != undefined) {
|
|
// Search node position;
|
|
loop: for(var i in this.world.nodes) {
|
|
if (this.world.nodes[i] && Comp.obj.equal(this.world.nodes[i].position,id)) {
|
|
node = this.world.nodes[i];
|
|
break loop;
|
|
}
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/** Return process object referenced by logical node number, position, or name,
|
|
** and agent identifier.
|
|
* If @id is undefined return current node object.
|
|
*/
|
|
simu.prototype.getProcess = function (id,aid) {
|
|
var node = this.getNode(id);
|
|
if (node) return node.getAgentProcess(aid);
|
|
}
|
|
|
|
/** Return statistics
|
|
* type stats = {steps,time,agents:{},agentsN:{},all;number}
|
|
*/
|
|
|
|
simu.prototype.getStats = function () {
|
|
return this.aiosX.getStats();
|
|
}
|
|
/** Initialize and create simulation world
|
|
*
|
|
*/
|
|
simu.prototype.init = function (model) {
|
|
var a,n,c,params,proc,count;
|
|
try {
|
|
this.out('Initializing ...');
|
|
|
|
if (model.parameter) this.parameter=model.parameter;
|
|
// 1. AC COMPILING
|
|
if (model.agents) model.classes = model.agents; // synonym for classes
|
|
else if (model.classes) model.agents = model.classes;
|
|
if (model.classes)
|
|
for(c in model.classes) {
|
|
if (!model.classes[c].behaviour)
|
|
this.out('Error: Agent class "'+c+'" has no behaviour!');
|
|
else {
|
|
this.out('Compiling agent class template "'+c+'"...');
|
|
this.addClass(model.classes[c].behaviour,c,model.classes[c].visual);
|
|
}
|
|
}
|
|
// 2. CREATE World
|
|
|
|
this.createWorld(model);
|
|
|
|
// 3. START AGENTS
|
|
if (model.world && model.world.init && model.world.init.agents) {
|
|
for (a in model.world.init.agents) {
|
|
if (!Comp.obj.isFunction(model.world.init.agents[a]))
|
|
this.out('Error: Init: "'+a+'" is not a function!');
|
|
else if (!this.world.classes[a])
|
|
this.out('Error: Init: Agent class "'+a+'" is not registered!');
|
|
else {
|
|
this.out('Creating agents of class "'+a+'" ..');
|
|
count=0;
|
|
for(n in this.world.nodes) {
|
|
params=model.world.init.agents[a](this.world.nodes[n].id,
|
|
this.world.nodes[n].position,
|
|
// can be defined in model.world.nodes(desc)
|
|
this.world.nodes[n].options.parameter);
|
|
if (params) {
|
|
if (params.level == undefined) params.level=1;
|
|
else if (params.level<0 || params.level>3)
|
|
this.out('Error: Init: Invalid agent level '+params.level+' for agent class "'+a+'".');
|
|
else {
|
|
proc=Aios.Code.createOn(this.world.nodes[n],
|
|
this.world.classes[a][params.level],
|
|
params.args,params.level,a);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
this.out('Created '+count+' '+a+' agents.');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. CREATE PHYSICAL WOLRD - IF ANY
|
|
if (model.world.init && model.world.init.physics) {
|
|
if (!this.simuPhy)
|
|
this.simuPhy=this.gui.createSimuPhy();
|
|
model.world.init.physics(this.simuPhy);
|
|
}
|
|
this.out('Ready.');
|
|
} catch (e) {
|
|
this.out('Error: '+e);
|
|
if (this.verbose) this.out(Io.sprintstack(e));
|
|
}
|
|
}
|
|
|
|
/** Move an object (node or resource) relative to its current position.
|
|
* type of dx,dy,dz = number is normalized pixel units | string (number + 'm' | '\B0')
|
|
*
|
|
*
|
|
*/
|
|
simu.prototype.moveObject = function(objid,dx,dy,dz) {
|
|
var id,obj,_dx,_dy,_dz,isnode=false;
|
|
if (dx && Comp.obj.isString(dx)) {
|
|
if (Comp.string.endsWith(dx,'\B0') && this.world.units.gps2px)
|
|
_dx=this.world.units.gps2px({delta:true,longitude:Number(Comp.string.prefix(dx,'\B0'))}).x;
|
|
else if (Comp.string.endsWith(dx,'m') && this.world.units.geo2px)
|
|
_dx=this.world.units.geo2px({delta:true,X:Number(Comp.string.prefix(dx,'m'))}).x;
|
|
else
|
|
_dx=Number(dx);
|
|
} else _dx=dx;
|
|
if (dy && Comp.obj.isString(dy)) {
|
|
if (Comp.string.endsWith(dy,'\B0') && this.world.units.gps2px)
|
|
_dy=this.world.units.gps2px({delta:true,latitude:Number(Comp.string.prefix(dy,'\B0'))}).y;
|
|
else if (Comp.string.endsWith(dy,'m') && this.world.units.geo2px)
|
|
_dy=this.world.units.geo2px({delta:true,Y:Number(Comp.string.prefix(dx,'m'))}).y;
|
|
else
|
|
_dy=Number(dy);
|
|
} else _dy=dy;
|
|
if (dz && Comp.obj.isString(dz)) {
|
|
if (Comp.string.endsWith(dz,'m') && this.world.units.geo2px)
|
|
_dz=this.world.units.geo2px({delta:true,Z:Number(Comp.string.prefix(dz,'m'))}).z;
|
|
else
|
|
_dz=Number(dz);
|
|
} else _dz=dz;
|
|
|
|
if (Comp.string.startsWith(objid,'node')) {
|
|
id=objid.replace(/node\[([^\]]+)\]/,'$1');
|
|
obj=this.world.getNode(id);
|
|
isnode=true;
|
|
} else if (Comp.string.startsWith(objid,'resource')) {
|
|
// TODO, use .data
|
|
}
|
|
if (!obj) return;
|
|
|
|
if (obj && obj.position) {
|
|
// Assumption: x/y/z are normalized pixel and linear coordinates!
|
|
if (obj.position.x != undefined && _dx) obj.position.x += _dx;
|
|
if (obj.position.y != undefined && _dy) obj.position.y += _dy;
|
|
if (obj.position.z != undefined && _dz) obj.position.z += _dz;
|
|
if (obj.position.gps && this.world.units.px2gps) {
|
|
if (_dx) obj.position.gps.latitude += this.world.units.px2gps({delta:true,y:_dy}).latitude;
|
|
if (_dy) obj.position.gps.longitude += this.world.units.px2gps({delta:true,x:_dx}).longitude;
|
|
if (_dz) obj.position.gps.height += this.world.units.px2gps({delta:true,z:_dz}).height;
|
|
}
|
|
}
|
|
if (isnode) this.gui.moveObject(objid,_dx,_dy,'agent','port');
|
|
else this.gui.moveObject(objid,_dx,_dy);
|
|
}
|
|
|
|
/** Move an object (node or resource) to new absolute position.
|
|
* type of x,y,z = number is normalized pixel unit | string (number + 'm' | '\B0')
|
|
*
|
|
*
|
|
*/
|
|
simu.prototype.moveObjectTo = function(objid,x,y,z) {
|
|
var id,obj,_px,_gps,isnode=false;
|
|
if (Comp.obj.isString(x) && Comp.obj.isString(y)) {
|
|
if (Comp.string.endsWith(x,'\B0') && Comp.string.endsWith(x,'\B0') && this.world.units.gps2px) {
|
|
_gps={latitude:Number(Comp.string.prefix(x,'\B0')),longitude:Number(Comp.string.prefix(y,'\B0')),height:Number(z)};
|
|
_px=this.world.units.gps2px(_gps);
|
|
} else if (Comp.string.endsWith(x,'m') && Comp.string.endsWith(y,'m') && this.world.units.geo2px)
|
|
_px=this.world.units.geo2px({X:Number(Comp.string.prefix(x,'m')),Y:Number(Comp.string.prefix(y,'m')),Z:Number(z)});
|
|
else
|
|
_px={x:Number(x),y:Number(y),z:Number(z)};
|
|
} else _px={x:x,y:y,z:z};
|
|
|
|
if (Comp.string.startsWith(objid,'node')) {
|
|
id=objid.replace(/node\[([^\]]+)\]/,'$1');
|
|
obj=this.world.getNode(id);
|
|
isnode=true;
|
|
} else if (Comp.string.startsWith(objid,'resource')) {
|
|
// TODO, use .data
|
|
}
|
|
if (!obj) return;
|
|
|
|
if (obj && obj.position && !this.options.patch) {
|
|
if (obj.position.x != undefined) obj.position.x = _px.x;
|
|
if (obj.position.y != undefined) obj.position.y = _px.y;
|
|
if (obj.position.z != undefined) obj.position.z = _px.z;
|
|
if (obj.position.gps && _gps) {
|
|
obj.position.gps.latitude = _gps.latitude;
|
|
obj.position.gps.longitude = _gps.longitude;
|
|
obj.position.gps.height = _gps.height;
|
|
}
|
|
}
|
|
if (isnode) this.gui.moveObjectTo(objid,_px.x,_px.y,'agent','port');
|
|
else this.gui.moveObjectTo(objid,_px.x,_px.y);
|
|
}
|
|
|
|
// Set simulation delay between two steps
|
|
simu.prototype.setDelay = function (ms) {
|
|
this.delay=ms;
|
|
}
|
|
|
|
/** Set simulation parameter (accessible by agents using simu.paramater(name))
|
|
*
|
|
*/
|
|
simu.prototype.setParameter = function (name,value) {
|
|
this.parameter[name]=value;
|
|
}
|
|
|
|
/** Run or step simulation
|
|
*/
|
|
simu.prototype.simulate = function (steps,callback) {
|
|
var self=this,
|
|
realtime=this.options.time=='real',
|
|
milliTime=function () {return Math.ceil(Date.now())},
|
|
start,stop,timeReal,
|
|
lazy=this.gui.options.display.lazy,suspend=false,
|
|
curtime=realtime?milliTime():Aios.time();
|
|
var lasttime=curtime;
|
|
this.stepped=steps;
|
|
|
|
start=curtime;
|
|
if (realtime && this.time>0) current.world.lag=current.world.lag+(start-this.time);
|
|
else if (this.time>0) this.lag=this.lag+(start-this.time);
|
|
this.time0=start;
|
|
this.time=start;
|
|
this.run=true;
|
|
if (this.verbose) this.log('[SIM] Stepping simulation ('+steps+') at '+this.step+ ' .. (t '+(start-current.world.lag-this.lag)+' ms)');
|
|
function starting () {
|
|
if (self.model && self.model.world && self.model.world.start) try { self.model.world.start({
|
|
log:self.log.bind(self),
|
|
time:Date.now(),
|
|
step:self.step,
|
|
simtime:curtime,
|
|
model:self.model,
|
|
cpu:self.stats.cpu,
|
|
}) } catch (e) { self.gui.log('model.world.start: '+e.toString())};
|
|
}
|
|
function stopping () {
|
|
if (self.gui.worldWin.container && suspend) { // enable world win updates
|
|
suspend=false; self.gui.worldWin.container.resume() };
|
|
self.gui.updateGui(true);
|
|
if (self.model && self.model.world && self.model.world.stop) try { self.model.world.stop({
|
|
log:self.log.bind(self),
|
|
time:Date.now(),
|
|
step:self.step,
|
|
simtime:curtime,
|
|
model:self.model,
|
|
cpu:self.stats.cpu,
|
|
}) } catch (e) { self.gui.log('model.world.stop:'+e.toString())};
|
|
}
|
|
// self.but2.setStyle({bg:'red'});
|
|
Aios.config({iterations:1});
|
|
var loop = function () {
|
|
if (self.gui.worldWin.container && !suspend) { // disable world win updates
|
|
suspend=true; self.gui.worldWin.container.suspend() };
|
|
|
|
// Execute JAM scheduler
|
|
timeReal=Date.now();
|
|
|
|
var nexttime=Aios.scheduler();
|
|
|
|
self.stats.cpu += (Date.now()-timeReal);
|
|
|
|
if (self.gui.worldWin.container && !lazy && suspend) { // enable world win updates
|
|
suspend=false; self.gui.worldWin.container.resume() };
|
|
|
|
if (self.gui.logging) self.gui.logTable[self.step]=self.getStats();
|
|
if (self.cleanWorld) self.cleanWorld=self.gui.cleanWorld()>0;
|
|
self.step++;
|
|
if (self.stepped>0) self.stepped--;
|
|
|
|
self.time=curtime=Aios.time();
|
|
if (!self.run) {
|
|
stop=realtime?milliTime():Aios.time();
|
|
//self.log(stop+','+current.world.lag+','+self.lag+','+curtime+','+milliTime());
|
|
if (self.verbose) self.log('[SIM] Stopping simulation at '+self.step+ ' .. (d '+(stop-self.time0)+' ms / t '+(stop-current.world.lag-self.lag)+' ms): Stopped.');
|
|
self.time=curtime;
|
|
if (callback) callback();
|
|
stopping();
|
|
return;
|
|
}
|
|
if (self.stepped==0) {
|
|
stop=realtime?milliTime():Aios.time();
|
|
self.run=false;
|
|
//self.but2.setStyle({bg:'blue'});
|
|
if (self.verbose) self.log('[SIM] Stopping simulation at '+self.step+ ' .. (d '+(stop-self.time0)+' ms / t '+(stop-current.world.lag-self.lag)+' ms): No more steps.');
|
|
self.time=stop;
|
|
if (callback) callback();
|
|
stopping();
|
|
return;
|
|
}
|
|
if (nexttime>0) self.loop=setTimeout(loop,(self.options.time=='real'?nexttime:self.delay)); // TODO: resonable timeout if nexttime is in steps!!!
|
|
else if (nexttime<0 && !self.delay) self.loop=setImmediate(loop);
|
|
else if (nexttime<0) self.loop=setTimeout(loop,self.delay);
|
|
else {
|
|
// self.but2.setStyle({bg:'blue'});
|
|
stop=realtime?milliTime():Aios.time();
|
|
self.run=false;
|
|
if (self.verbose) self.log('[SIM] Stopping simulation at '+self.step+ ' .. (d '+(stop-self.time0)+' ms / t '+(stop-current.world.lag-self.lag)+' ms): Nothing to run.');
|
|
self.time=curtime;
|
|
if (callback) callback();
|
|
stopping();
|
|
return;
|
|
}
|
|
|
|
if ((curtime-lasttime)>100) {
|
|
if (self.gui.worldWin.container && lazy && suspend) { // enable world win updates
|
|
suspend=false; self.gui.worldWin.container.resume() };
|
|
self.gui.updateGui(true);
|
|
lasttime=curtime;
|
|
} else
|
|
self.gui.updateGui(false);
|
|
}
|
|
starting();
|
|
this.loop = setTimeout(loop,0);
|
|
}
|
|
|
|
/** Start simulation
|
|
* steps: number of simulation steps
|
|
* temp: temporary step
|
|
*
|
|
*/
|
|
simu.prototype.start = function (steps,temp) {
|
|
var self=this;
|
|
if (steps<=0 || (temp && this.stopped)) return;
|
|
if (!temp) {
|
|
this.stopped=false;
|
|
this.gui.UI('simulationWinButStep').disable();
|
|
this.gui.UI('simulationWinButPlay').disable();
|
|
this.gui.UI('simulationWinButStop').enable();
|
|
}
|
|
setImmediate(function () {
|
|
self.simulate(self.stepped|steps,function () {
|
|
if (self.stepped==0) {
|
|
this.stopped=true;
|
|
self.gui.UI('simulationWinButStep').enable();
|
|
self.gui.UI('simulationWinButPlay').enable();
|
|
self.gui.UI('simulationWinButStop').disable();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/** Stop simulation, return remaining steps (only in temporary mode)
|
|
*
|
|
*/
|
|
simu.prototype.stop = function (temp) {
|
|
this.run=false;
|
|
if (this.loop) clearTimeout(this.loop);
|
|
this.loop=null;
|
|
if (this.gui.worldWin.container) this.gui.worldWin.container.resume()
|
|
if (temp && !this.stopped)
|
|
return this.stepped-1;
|
|
else {
|
|
this.stepped=0;
|
|
this.stopped=true;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/** convert simulation world to graphics coordinates
|
|
*/
|
|
simu.prototype.world2draw = function (p) {
|
|
if (this.options.patch) return {x:p.x*this.options.patch.width,
|
|
y:p.y*this.options.patch.height};
|
|
else return p;
|
|
}
|
|
|
|
var Simu = function(options) {
|
|
var obj=none;
|
|
obj = new simu(options);
|
|
return obj;
|
|
}
|
|
|
|
module.exports = {
|
|
simu:simu,
|
|
Simu:Simu,
|
|
current:function (module) { current=module.current; Aios=module; JamAnal.current(module)}
|
|
}
|