/** ** ============================== ** 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: * */ 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.(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;j1) 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;j1) 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;j1) 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)} }