/** ** ============================== ** 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-2022 bLAB ** $CREATED: 25-12-16 by sbosse. ** $RCS: $Id: jamlib.js,v 1.5 2020/02/03 09:45:01 sbosse Exp sbosse $ ** $VERSION: 1.35.1 ** ** $INFO: ** ** JAM library API that can be embedded in any host application. ** ** ** New: Embedded auto setup (e.g., for clusters) using command line arguments ** ** jamlib autosetup:"{options}" ** ** ** $ENDOFINFO */ var onexit=false; var start=false; var options = { geo:undefined, verbose:0, version:'1.35.1' // public version }; global.config={simulation:false,nonetwork:false}; var Io = Require('com/io'); var Comp = Require('com/compat'); var Aios = Require('jam/aios'); var Esprima = Require('parser/esprima'); var Json = Require('jam/jsonfn'); var fs = Require('fs'); var Sat = Require('dos/ext/satelize'); var GPS5 = Require('geoip/gps5'); var GeoLoc5 = Require('geoip/geoloc5'); var CBL = Require('com/cbl'); var platform = Require('os/platform'); var DIR = Aios.DIR; // Parse command line arguments; extract a:v attributes var environment = process.env; process.argv.slice(2).forEach(function (arg) { var tokens=arg.match(/([a-zA-Z]+):(['"0-9a-zA-Z_:\->\.\{\},;]+)/); if (tokens && tokens.length==3) environment[tokens[1]]=tokens[2]; }); function locationEvalError(e) { return (e.lineNumber?(' at line '+e.lineNumber+ (e.columnNumber?(' column '+e.columnNumber):'')):'') } if (typeof setImmediate == 'undefined') { function setImmediate(callback) {return setTimeout(callback,0)}; } // Extend DIR with IP capabilities of NORTH, .. DIR.North= function (ip) { return {tag:DIR.NORTH,ip:ip}} DIR.South= function (ip) { return {tag:DIR.SOUTH,ip:ip}} DIR.West = function (ip) { return {tag:DIR.WEST ,ip:ip}} DIR.East = function (ip) { return {tag:DIR.EAST ,ip:ip}} DIR.Up = function (ip) { return {tag:DIR.UP ,ip:ip}} DIR.Down = function (ip) { return {tag:DIR.DOWN ,ip:ip}} /** * typeof options = { * connections?, * print? is agent and control message output function, * printAgent? is agent message only output function, * printAsync? async (callback) output function, * fork?, * provider?, consumer?, * classes?, * id?:string is JAM and JAM root node id, * world?:string is JAM world id, * position?:{x,y}, * cluster?:boolean|[] is an attached cluster node, * nowatch:boolean is a disable flag for agent watchdog checking, * checkpoint:boolean is a flag forcing code checkpointing (even if watchdog is available), * nolimits:boolean is a disable flag for agent resource monitoring, * log?:{class?:boolean,node?,agent?,parent?,host?,time?,Time?,pid?}, * logJam?:{host?,time?,pid?,node?,world?}, * scheduler?:scheduler is an external scheduler, singlestep?, * network?:{cluster?,rows,columns,connect?:function}, * verbose?, TMO? } * with typeof connections = { * @kind : {from:string,to?:string,proto:string='udp'|'tcp'|'http'|'stream',num?:number,on?,range?:number[]}, * @kind : {send:function, status:function, register?:function(@link)} , * @kind : .. } * with @kind = {north,south,west,east,ip, ..} * * Connecting JAM nodes (IP) * ------------------------- * * .. Jam({ * connections: { * // Generic, P2PN * ip?: {from:string,to?:string,proto:string='udp'|'tcp'|'http',num?:number} // AMP link (UDP) or set of AMP links (num>1) * // Assigned to a logical direction, P2P * north?: { * from:string,to?:string,proto?='udp'|'tcp'|'http'|'device',device?:string // device is a hardware P2P stream device * }, .. * * Integration of host program streams * ------------------------------------ * * var chan = Some Stream Channel Object; * * .. Jam({ * connections: { * north?: { * register: function (link) { * // register channel data handler with link handler * chan.on('data',function (data) { * // process raw data, extract msg={agent:string,to?,from?,..} or {signal:string,to?,from?,..} * if (msg.agent) link.emit('agent',msg.agent); * if (msg.signal) link.emit('signal',msg.signal); * }); * } * send: function (msg) { * chan.send(msg); * }, * status: function (to) { * return true; * } * } * }, .. * } * * Cluster * -------- * * A forked cluster consists of a master node (0) and up to 8 child ndoes connected around the root node * by streams in directions {E,S,SE,W,SW,N,NW,NE}. Each node is executed physically in a different JAM process. * Ex. network: {cluster:true, rows:2, columns:2}, * */ var jam = function (options) { var self=this, p,conn,node; this.options = options||{}; this.environment=environment; if (this.setup) this.setup(); // overwrite options if (this.options.world && !this.options.id) this.options.id=this.options.world; if (!this.options.id) this.options.id=Aios.aidgen(); if (!this.options.log) this.options.log={}; if (!this.options.logJam) this.options.logJam={pid:false,host:false,time:false}; this.verbose = this.options.verbose || 0; this.Aios = Aios; this.DIR = Aios.aios.DIR; Aios.options.verbose=this.verbose; if (options.scheduler) Aios.current.scheduler=scheduler; if (options.nolimits||options.nowatch||options.checkpoint) Aios.config({nolimits:options.nolimits,nowatch:options.nowatch,checkpoint:options.checkpoint}); // out=function (msg) { Io.print('[JAM '+self.options.id+'] '+msg)}; if (this.options.print) Aios.print=Aios.printAgent=this.options.print; if (this.options.print2) Aios.printAgent=this.options.print2; if (this.options.printAgent) Aios.printAgent=this.options.printAgent; if (this.options.printAsync) Aios.printAsync=this.options.printAsync; // JAM messages this.log=function (msg) { var s='[JAM',sep=' '; if (self.options.logJam.pid && process) s += (' '+process.pid),sep=':'; if (self.options.logJam.world && Aios.current.world) s += (sep+Aios.current.world.id),sep=':'; if (self.options.logJam.node && Aios.current.node) s += (sep+Aios.current.node.id),sep=':'; if (self.options.logJam.time) s += (sep+Aios.time()); Aios.print(s+'] '+msg); }; this.err=function (msg,err) { self.log('Error: '+msg); throw (err||'JAMLIB'); } this.warn=function (msg) { self.log('Warning: '+msg); } this.error=undefined; // Create a world this.world = Aios.World.World([],{ id:this.options.world||this.options.id.toUpperCase(), classes:options.classes||[], scheduler:options.scheduler, verbose:options.verbose }); if (this.verbose) this.log('Created world '+this.world.id+'.'); this.node=none; this.run=false; // Service loop executing the AIOS scheduler // NOT USED if there is an external scheduler supplied (world will create JAM scheduling loop) this.ticks=0; // schedule loop execution counter! this.steps=0; // Number of schedule loop execution steps this.loop=none; // Schedule loop function this.looping=none; // Current schedule loop run (or none); can be waiting for a timeout Aios.config({fastcopy:this.options.fastcopy, verbose:this.options.verbose}); if (this.options.log) for(p in this.options.log) Aios.config(this.options.log[p]?{"log+":p}:{"log-":p}); this.process = Aios.Proc.Proc(); this.process.agent={id:'jamlib'}; this.events={}; } // Import analyzer class... var JamAnal = Require('jam/analyzer'); JamAnal.current(Aios); jam.prototype.analyzeSyntax=JamAnal.jamc.prototype.analyze; jam.prototype.syntax=JamAnal.jamc.prototype.syntax; /** Add agent class to the JAM world and create sandboxed constructors. * type constructor = function|string */ jam.prototype.addClass = function (name,constructor,env) { this.world.addClass(name,constructor,env); if (this.verbose) this.log('Agent class '+name+' added to world library.'); }; /** Add a new node to the world. * Assumption: 2d meshgrid network with (x,y) coordinates. * The root node has position {x=0,y=0}. * type of nodeDesc = {x:number,y:number,id?} * */ jam.prototype.addNode = function (nodeDesc) { var node,x,y; x=nodeDesc.x; y=nodeDesc.y; if (Comp.array.find(this.world.nodes,function (node) { return node.position.x==x && node.position.y==y; })) { this.err('addNodes: Node at positition ('+x+','+y+') exists already.'); return; } node=Aios.Node.Node({id:nodeDesc.id||Aios.aidgen(),position:{x:x,y:y}},true); if (this.verbose) this.log('Created node '+node.id+' ('+x+','+y+').'); // Add node to world this.world.addNode(node); return node.id; } /** Add logical nodes. * The root node has position {x=0,y=0}. * type of nodes = [{x:number,y:number,id?},..] */ jam.prototype.addNodes = function (nodes) { var n,node,x,y,nodeids=[]; for(n in nodes) { nodeids.push(this.addNode(nodes[n])); } return nodeids; } /** Analyze agent class template in text or object form ** typeof @options = {..,classname?:string} * Returns {report:string,interface} */ jam.prototype.analyze = function (ac,options) { var source,name,syntax,content,report,interface; if (Comp.obj.isString(ac)) { // TODO } else if (Comp.obj.isObject(ac)) { // TODO } else if (Comp.obj.isFunction(ac)) { source = ac.toString(); if (!options.classname) { name=source.match(/^ *function *([^\s\(]*)\(/); if (name && name[1]!='') options.classname=name[1]; } content = 'var ac ='+source; syntax = Esprima.parse(content, { tolerant: true, loc:true }); try { interface=this.analyzeSyntax(syntax,{ classname:options.classname||'anonymous', level:options.level==undefined?2:options.level, verbose:options.verbose, err:function (msg){throw msg}, out:function (msg){if (!report) report=msg; else report=report+'\n'+msg;}, warn:function (msg){if (!report) report=msg; else report=report+'\n'+msg;} }); return {report:report||'OK',interface:interface}; } catch (e) { return {report:e,interface:interface}; } } } jam.prototype.clock = Aios.clock; /** Compile (analyze) an agent class constructor function and add it to the world class library. ** Can be used after an open statement. ** Usage: compileClass(name,constructor,options?) ** compileClass(constructor,options?) ** ** typeof @name=string|undefined ** typeof @constructor=function|string ** typeof @options={verbose:number|boolean)|verbose:number|undefined */ jam.prototype.compileClass = function (name,constructor,options) { var ac,p,verbose,content,syntax,report,text,env={ac:undefined},self=this,ac; if (typeof name == 'function') constructor=name,name=undefined,options=constructor; if (typeof options == 'object') verbose=options.verbose||0; else if (options!=undefined) verbose=options; else verbose=this.verbose; // if (typeof constructor != 'function') throw 'compileClass: second constructor argument not a function'; if (typeof constructor == 'function') text = constructor.toString(); else text = constructor; if (!name) { // try to find name in function definition name=text.match(/[\s]*function[\s]*([A-Za-z0-9_]+)[\s]*\(/); if (!name) throw ('compileClass: No class name provided and no name found in constructor '+ text.substring(0,80)); name=name[1]; } content = 'var ac = '+text; try { syntax = Esprima.parse(content, { tolerant: true, loc:true }) } catch (e) { throw 'compileClass('+name+'): Parsing failed with '+e } report = this.analyzeSyntax(syntax, { classname:name, level:2, verbose:verbose||0, err: function (msg){self.log(msg)}, out: function (msg){self.log(msg)}, warn: function (msg){self.log(msg)} }); if (report.errors.length) { throw 'compileClass('+name+'): failed with '+report.errors.join('; ')}; for (p in report.activities) env[p]=p; try { with (env) { eval(content) } } catch (e) { throw ('compileClass('+name+'): failed with '+e+locationEvalError(e)) }; ac=env.ac; env.ac=undefined; this.addClass(name,ac,env); return name; } /** Connect logical nodes (virtual link). * The root node has position {x=0,y=0}. * type of links = [{x1:number,y1:number,x2:number,x2:number},..]|[{x,y},{x,y}] */ jam.prototype.connectNodes = function (connections) { var c,node1,node2,x1,y1,x2,y2,dir; if (connections[0].x != undefined && connections[0].y != undefined) { if (connections.length!=2) throw 'INVALID'; // invalid // simple style connections=[{x1:connections[0].x,x2:connections[1].x, y1:connections[0].y,y2:connections[1].y}]; } for(c in connections) { x1=connections[c].x1; y1=connections[c].y1; x2=connections[c].x2; y2=connections[c].y2; if (this.verbose) this.log('Connecting ('+x1+','+y1+') -> ('+x2+','+y2+')'); node1=Comp.array.find(this.world.nodes,function (node) { return node.position.x==x1 && node.position.y==y1; }); node2=Comp.array.find(this.world.nodes,function (node) { return node.position.x==x2 && node.position.y==y2; }); if (!node1) this.err('connectNodes: Node at positition ('+x1+','+y1+') does not exist.'); if (!node2) this.err('connectNodes: Node at positition ('+x2+','+y2+') does not exist.'); if ((x2-x1)==0) { if ((y2-y1) > 0) dir=Aios.DIR.SOUTH; else dir=Aios.DIR.NORTH; } else if ((x2-x1)>0) dir=Aios.DIR.EAST; else dir=Aios.DIR.WEST; this.world.connect(dir,node1,node2); this.world.connect(Aios.DIR.opposite(dir),node2,node1); } } /** Dynamically connect remote endpoint at run-time * typeof @to = string url>| */ jam.prototype.connectTo = function (to,nodeid) { var node=this.getNode(nodeid), tokens=(typeof to=='string')?to.split('->'):null, dir; // console.log(tokens) if (!node) return; if (to.tag) dir=to; else if (tokens.length==2) { dir=Aios.DIR.from(tokens[0]); if (dir) dir.ip=tokens[1]; } else dir={tag:'DIR.IP',ip:to}; if (dir) this.world.connectTo(dir,node); } /** Check connection status of a link * */ jam.prototype.connected = function (dir,nodeid) { var node=this.getNode(nodeid); if (!node) return; return this.world.connected(dir,node); } /** Create and start an agent from class ac with arguments. * Ac is either already loaded (i.e., ac specifies the class name) or * AC is supplied as a constructor function (ac), a class name, or a sandboxed constructor * {fun:function,mask:{}} object for a specific level. * * type of ac = string|object|function * type of args = * [] * level = {0,1,2,3} * */ jam.prototype.createAgent = function (ac,args,level,className,parent) { var node=this.world.nodes[this.node], process=none,sac; if (level==undefined) level=Aios.options.LEVEL; if (!className && typeof ac == 'string') className=ac; if (!className && typeof ac == 'function') className=Aios.Code.className(ac); if (Comp.obj.isFunction(ac) || Comp.obj.isObject(ac)) { // Create an agent process from a constructor function or sandboxed constructor object process = Aios.Code.createOn(node,ac,args,level,className); if (process && !process.agent.parent) process.agent.parent=parent; if (process) return process.agent.id; } else { // It is a class name. Find an already sandboxed constructor from world classes pool if (this.world.classes[ac]) process = Aios.Code.createOn(node,this.world.classes[ac][level],args,level,className); else { this.error='createAgent: Cannot find agent class '+ac; this.log(this.error); return; } if (process) { if (!process.agent.parent) process.agent.parent=parent; process.agent.ac=ac; return process.agent.id; } else return none; } } /** Create agent on specified (logical or physical) node. * typeof node = number|string|{x,y} */ jam.prototype.createAgentOn = function (node,ac,args,level,className,parent) { var res,_currentNode=this.node,found=this.getNode(node); if (found) { this.setCurrentNode(); res=this.createAgent(ac,args,level,className,parent); this.setCurrentNode(_currentNode); } return res; } /** Create a physical communication port * */ jam.prototype.createPort = function (dir,options,nodeid) { if (!options) options={}; var multicast=options.multicast; switch (dir.tag) { case Aios.DIR.NORTH: case Aios.DIR.SOUTH: case Aios.DIR.WEST: case Aios.DIR.EAST: case Aios.DIR.UP: case Aios.DIR.DOWN: multicast=false; break; } if (dir.ip && typeof dir.ip == 'string' && dir.ip.indexOf('//')>0) { // extract proto from url var addr = Aios.Amp.url2addr(dir.ip); if (!options.proto && addr.proto) options.proto=addr.proto; dir.ip=Aios.Amp.addr2url(addr); } if (options.from==undefined && dir.ip) options.from=dir.ip.toString(); var chan=this.world.connectPhy( dir, this.getNode(nodeid), { broker : options.broker, keepAlive : options.keepAlive, multicast : multicast, name : options.name, on : options.on, oneway : options.oneway, pem : options.pem, proto : options.proto||'udp', rcv : options.from, secure : options.secure, sharedSocket:options.sharedSocket, snd : options.to, verbose:(options.verbose!=undefined?options.verbose:this.verbose) }); chan.init(); chan.start(); return chan; } /** Dynamically disconnect remote endpoint at run-time * */ jam.prototype.disconnect = function (to,nodeid) { var node=this.getNode(nodeid); if (node) { this.world.disconnect(to,node); } } /** Emit an event ** function emit(@event,@arg1,..) */ jam.prototype.emit = function () { Aios.emit.apply(this,arguments); } /** Execute an agent snapshot on current node delivered in JSON+ text format or read from a file. */ jam.prototype.execute = function (data,file) { if (!data && file && fs) try { data=fs.readFileSync(file,'utf8'); } catch (e) { this.log('Error: Reading file '+file+' failed: '+e); return undefined; } if (data) return this.world.nodes[this.node].receive(data,true); } /** Execute an agent snapshot on node @node delivered in JSON+ text format or read from a file. */ jam.prototype.executeOn = function (data,node,file) { node=this.getNode(node); if (!node) return; if (!data && file && fs) try { data=fs.readFileSync(file,'utf8'); } catch (e) { this.log('Error: Reading file '+file+' failed: '+e); return undefined; } if (data) return node.receive(data,true); } /** Extend AIOS of specific privilege level. The added functions can be accessed by agents. * * function extend(level:number [],name:string,func:function,argn?:number|number []); */ jam.prototype.extend = function (level,name,funcOrObj,argn) { var self=this; if (Comp.obj.isArray(level)) { Comp.array.iter(level,function (l) {self.extend(l,name,funcOrObj,argn)}); return; } function range(n) { var l=[]; for(var i=0;i world node# function makeconn (p,conn) { var link = { _handler:[], emit: function (event,msg) { if (link._handler[event]) link._handler[event](msg); }, on: function (event,callback) { link._handler[event]=callback; }, send: function (data,dest,context) { var res; self.world.nodes[self.node].connections[p]._count += data.length; res=conn.send(data,dest); if (!res) { context.error='Migration to destination '+dest+' failed'; // We're still in the agent process context! Throw an error for this agent .. throw 'MOVE'; }; // kill ghost agent context.process.finalize(); }, status : conn.status?conn.status:(function () {return true}), count: conn.count?conn.count:function () {return link._count}, _count:0 }; if (conn.register) conn.register(link); return link; } node.connections[p] = makeconn(p,conn); // register agent receiver and signal handler node.connections[p].on('agent',node.receive.bind(node)); node.connections[p].on('signal',node.handle.bind(node)); } else if (p=='stream') { // 3. Physical process stream interface (cluster); child->parent proecss connection chan=this.world.connectPhy( conn.dir, this.getNode(), { proto:'stream', sock:process, mode:'object', verbose:this.verbose }); chan.init(); } } } if (callback) callback(); } /** Tuple space input operation - non blocking, i.e., equiv. to inp(pat,_,0) */ jam.prototype.inp = function (pat,all) { return this.world.nodes[this.node].ts.extern.inp(pat,all); } /** Kill agent with specified id ('*': kill all agents on node or current node) */ jam.prototype.kill = function (id,node) { if (id=='*') { this.world.nodes[this.node].processes.table.forEach(function (p) { if (p) Aios.kill(p.agent.id); }); } else return Aios.kill(id); } /** Try to locate this node (based on network connectivity) * Any geospatial information is attached to current (node=undefined) or specific node */ jam.prototype.locate = function (nodeid,cb,options) { if (typeof nodeid == 'function') { options=cb;cb=nodeid;nodeid=0}; if (typeof nodeid == 'object') { options=nodeid;cb=null;nodeid=0}; var node=this.getNode(nodeid); if (!node) return; return GeoLoc5.locate(function (location,errors) { node.location=node.location||{}; Object.assign(node.location,location); if (cb) cb(location,errors); },options); } /** Lookup nodes and get connection info (more general as connected and broker support) * */ jam.prototype.lookup = function (dir,callback,nodeid) { var node=this.getNode(nodeid); if (!node) return; return this.world.lookup(dir,callback,node); } /** Tuple space output operation with timeout */ jam.prototype.mark = function (tuple,tmo) { return this.world.nodes[this.node].ts.extern.mark(tuple,tmo); } /** Execute an agent snapshot in JSON+ text form after migration provided from host application */ jam.prototype.migrate = function (data) { return this.world.nodes[this.node].receive(data,false); } /** Install event handler * * typeof @event = {'agent','agent+','agent-','signal+','signal','link+','link-',..} * agent+/agent-: Agent creation and destruction event * agent: Agent receive event * signal+: Signal raise event * signal: Signal receive (handle) event * route+: A new link was established * route-: A link is broken */ jam.prototype.on = function (event,handler) { Aios.on(event,handler); } /** Remove event handler */ jam.prototype.off = function (ev) { Aios.off(event); } /** Read and parse one agent class from file. Can contain nested open statements. * Browser (no fs module): @file parameter contains source text. * File/source text format: function [ac] (p1,p2,..) { this.x; .. ; this.act = {..}; ..} * open(file:string,options?:{verbose?:number|boolean,classname?:string}) -> function | object * * Output can be processed by method compileClass */ jam.prototype.open = function (file,options) { var self=this, res, text, name, ast=null; if (!options) options={}; name=options.classname||''; if (options.verbose>0) this.log('Reading agent class template '+name+' from '+file); function parseModel (text) { var modu={},more,module={exports:{}},name=text.match(/[\s]*function[\s]*([a-z0-9]+)[\s]*\(/); if (name) name=name[1]; function open(filename) { var text; try { text=fs?fs.readFileSync(filename,'utf8'):null; return parseModel(text); } catch (e) { self.log('Error: Opening of '+(fs?file:'text')+' failed: '+e); } } try { with (module) {eval('res = '+text)}; if (name) { modu[name]=res; return modu} else if (module.exports) return module.exports; else return res; } catch (e) { try { ast = Esprima.parse(text, { tolerant: true, loc:true }); if (ast.errors && ast.errors.length>0) more = ', '+ast.errors[0]; } catch (e) { if (e.lineNumber) more = ', in line '+e.lineNumber; } self.log(e.name+(e.message?': '+e.message:'')+(more?more:'')); } } try { text=fs?fs.readFileSync(file,'utf8'):file; // Browser: file parameter contains already source text return parseModel(text); } catch (e) { this.log('Error: Opening of '+(fs?file:'text')+' failed: '+e); } }; /** Tuple space output operation */ jam.prototype.out = function (tuple) { return this.world.nodes[this.node].ts.extern.out(tuple); } /** Tuple space read operation - non blocking, i.e., equiv. to rd(pat,_,0) */ jam.prototype.rd = function (pat,all) { return this.world.nodes[this.node].ts.extern.rd(pat,all); } /** 1. Read agent template classes from file and compile (analyze) agent constructor functions. * Expected file format: module.exports = { ac1: function (p1,p2,..) {}, ac2:.. } * 2. Read single agent constructor function from file * * typeof @options={verbose,error:function} */ // TODO: clean up, split fs interface, no require caching .. if (fs) jam.prototype.readClass = function (file,options) { var self=this, ac, name, env, interface, text, modu, path, p,m, regex1, ast=null, fileText=null, off=null; this.error=_; function errLoc(ast) { var err; if (ast && ast.errors && ast.errors.length) { err=ast.errors[0]; if (err.lineNumber != undefined) return 'line '+err.lineNumber; } return 'unknown' } try { if (!options) options={}; if (options.verbose>0) this.log('Looking up agent class template(s) from '+file); //modu=Require(file); if (Comp.obj.isEmpty(modu)) { if (options.verbose>0) this.log('Reading agent class template(s) from file '+file); if (Comp.string.get(file,0)!='/') path = (process.cwd?process.cwd()+'/':'./')+file; else path = file; fileText=fs.readFileSync(path,'utf8'); ast=Esprima.parse(fileText, { tolerant: true, loc:true }); if (require.cache) delete require.cache[file]; // force reload of file by require modu=require(path); if(Comp.obj.isEmpty(modu)) { modu={}; // Try evaluation of fileText containing one single function definition if (!fileText) throw 'No such file!'; name=fileText.match(/[\s]*function[\s]*([a-z0-9]+)[\s]*\(/); if (!name) throw ('Export interface of module is empty and file contains no valid function definition!'); name=name[1]; eval('(function () {'+fileText+' modu["'+name+'"]='+name+'})()'); } } if (!modu || Comp.obj.isEmpty(modu)) throw 'Empty module.'; for (m in modu) { ac=modu[m]; env={}; if (fileText) off=this.syntax.find(fileText,'VariableDeclarator',m); if (off && off.loc) this.syntax.offset=off.loc.start.line-1; content = 'var ac = '+ac; syntax = Esprima.parse(content, { tolerant: true, loc:true }); interface = this.analyzeSyntax(syntax, { classname:m, level:2, verbose: options.verbose||0, err: options.error||function (msg){throw(msg)}, out: function (msg){self.log(msg)}, warn: function (msg){self.log(msg)} }); // text=Json.stringify(ac); for (var p in interface.activities) env[p]=p; with (env) { eval(content) }; if (options.verbose>0) this.log('Adding agent class constructor '+m+' ('+(typeof ac)+').'); this.addClass(m,ac,env); this.syntax.offset=0; } this.error=undefined; return true; } catch (e) { this.error='Compiling agent class file "'+file+'" failed: '+e+ (ast && ast.errors.length?', in '+errLoc(ast):''); if (options.error) options.error(e+(ast && ast.errors.length?', in '+errLoc(ast):'')); else { this.log(this.error); } return false; } }; /** Register jamlib event handler for the (root) node */ jam.prototype.register = function (node) { this.on('agent', function (msg) { node.receive(msg) }); this.on('signal', function (msg) { node.handle(msg) }); } /** Disconnect and remove a virtual node from the world * */ jam.prototype.removeNode = function (nodeid) { this.world.removeNode(nodeid); } /** Tuple space remove operation */ jam.prototype.rm = function (pat,all) { return this.world.nodes[this.node].ts.extern.rm(pat,all); } /** Take an agent process snapshot executed currently on given node @node:number|string|undefined. * If @file:string is not specified, a string containing the snapshot is * returned, otehrwise it is saved to the file (text format. JSON+). * If @node is undefined, the current node is used. * If @kill is set, the agent is killed after taken the snapshot. */ jam.prototype.saveSnapshotOn = function (aid,node,file,kill) { var snapshot,pro; node=this.getNode(node); if (!node) return; // Look-up agent process .. pro=node.getAgentProcess(aid); if (!pro) return; // Take snapshot od the process .. snapshot=Aios.Code.ofCode(pro,false); if (kill) Aios.killOn(aid,node); // Save it .. if (!file) return snapshot; else if (fs) return fs.writeFileSync(file, snapshot, 'utf8'); } jam.prototype.saveSnapshot = function (aid,file,kill) { return this.saveSnapshotOn(aid,_,file,kill); } /** Force a scheduler run immediately normally executed by the * jam service loop. Required if there were externeal agent * management, e.g., by sending signals. */ jam.prototype.schedule = function () { if (this.loop) { clearTimeout(this.loop); setImmediate(this.looping); } else if (!this.run) setImmediate(this.looping); } /** Access to JAM security module * */ jam.prototype.security = Aios.Sec; /** Set current node (by index number or node name) * */ jam.prototype.setCurrentNode=function (n) { if (typeof n == 'number') { if (n>=0 && n < this.world.nodes.length) this.node=n; } else if (typeof n == 'string') { this.node=this.world.nodes.indexOf(this.world.getNode(n)) } current.node=this.world.nodes[this.node]; } /** Send a signal to a specific agent 'to'. * */ jam.prototype.signal=function (to,sig,arg,broadcast) { var node=this.getNode(), _process=Aios.current.process; Aios.current.process=this.process; if (!broadcast) Aios.aios.send(to,sig,arg); else Aios.aios.broadcast(to,sig,arg); Aios.current.process=_process; this.schedule(); } /** Set-up connections, start the JAM, but not the scheduler (used in single-step mode) * */ jam.prototype.start0=function (callback) { if (this.run) return; var self=this,cbl=CBL(callback); // Start all connections if not already done this.world.nodes.forEach(function (node) { node.connections.forEach(function (chan,kind) { if (!chan) return; if (chan.start) cbl.push(function (next) {chan.start(next)}); }); }); cbl.start(); Aios.on('schedule',function () { self.schedule(); }); this.world.start(); if (this.verbose) this.log('Starting JAM .. '); return; } /** Set-up connections, start the JAM scheduler * */ jam.prototype.start=function (callback) { if (this.run) return; var self=this, current=Aios.current, cbl=CBL(callback); // Start all connections if not already done this.world.nodes.forEach(function (node) { node.connections.forEach(function (chan,kind) { if (!chan) return; if (chan.start) cbl.push(function (next) {chan.start(next)}); }); }); cbl.start(); Aios.on('schedule',function () { self.schedule(); }); function loop() { var loop = function () { var nexttime,curtime; if (self.verbose>3) self.log('loop: Entering scheduler #'+self.ticks); self.ticks++; nexttime=Aios.scheduler(); curtime=Aios.time(); if (self.verbose>3) self.log('loop: Scheduler returned nexttime='+nexttime+ ' ('+(nexttime>0?nexttime-curtime:0)+')'); if (!self.run) return; if (nexttime>0) self.loop=setTimeout(loop,nexttime-curtime); else if (nexttime==0) self.loop=setTimeout(loop,1000); else setImmediate(loop); // else setTimeout(loop,10); }; self.loop = setTimeout(loop,1); }; this.looping=loop; Aios.config({iterations:100}); this.run=true; this.world.start(); if (this.verbose) this.log('Starting JAM loop .. '); if (!this.options.scheduler) loop(); // Start internal scheduling loop } /** Get agent process table info and other statistics * * type kind = 'process'|'agent'|'node'|'vm'|'conn' */ jam.prototype.stats = function (kind,id) { var p,n,sys,conn,pro,agent,state,stats,allstats={},signals,node; switch (kind) { case 'process': case 'agent': for(n in this.world.nodes) { stats={}; node=this.world.nodes[n]; for (p in node.processes.table) { if (node.processes.table[p]) { pro=node.processes.table[p]; if (pro.signals.length == 0) signals=[]; else signals = pro.signals.map(function (sig) {return sig[0] }); agent=pro.agent; if (pro.suspended) state='SUSPENDED'; else if (pro.blocked) state='BLOCKED'; else if (pro.dead) state='DEAD'; else if (pro.kill) state='KILL'; else if (pro.move) state='MOVE'; else state='READY'; stats[agent.id]={ pid:pro.pid, gid:pro.gid, state:state, parent:pro.agent.parent, class:pro.agent.ac, next:agent.next, resources:Comp.obj.copy(pro.resources) }; if (signals.length) stats[agent.id].signals=signals; } } allstats[node.id]=stats; } break; case 'node': return Comp.obj.copy(this.getNode(id).stats); break; case 'conn': for(n in this.world.nodes) { stats={}; node=this.world.nodes[n]; for (p in node.connections) { conn=node.connections[p]; if (conn) { stats[p]={count:conn.count(),conn:conn.status('%')}; } } allstats[node.id]=stats; } break; case 'vm': // Return VM memory usage in kB units and VM system information if (process && process.memoryUsage) { sys=process.memoryUsage(); for ( p in sys) sys[p] = (sys[p]/1024)|0; sys.v8 = process.versions && process.versions.v8; sys.node = process.versions && process.versions.node; sys.arch = process.arch; sys.platform = process.platform; sys.watchdog = Aios.watchdog?(Aios.watchdog.checkPoint?'semi':'full'):'none'; return sys; } break; } if (this.world.nodes.length==1) return stats; else return allstats; } /** Stepping the scheduler loop */ jam.prototype.step = function (steps,callback) { // TODO: accurate timing var self=this, sync=callback===true, milliTime=function () {return Math.ceil(Date.now())}, current=Aios.current, curtime=Aios.time(),// Aios.time(); lasttime=curtime; function loop () { var loop = function () { var nexttime,curtime; if (self.verbose>1) self.log('loop: Entering scheduler #'+self.ticks); self.ticks++,self.steps--; self.time=curtime=Aios.time(); // Execute scheduler loop nexttime=Aios.scheduler(); curtime=Aios.time(); if (self.verbose>3) self.log('loop: Scheduler returned nexttime='+nexttime+ ' ('+(nexttime>0?nexttime-curtime:0)+')'); if (sync) { self.time=curtime; return; } if (self.steps==0 || !self.run) { self.loop=none; self.run=false; self.time=curtime; if (callback) callback(); return; } if (nexttime>0) self.loop=setTimeout(loop,nexttime-curtime); else if (nexttime < 0) self.loop=setImmediate(loop); else { self.loop=none; self.run=false; self.time=curtime; if (callback) callback(); } }; if (sync) loop(); else self.loop = setTimeout(loop,1); }; this.looping=loop; Aios.config({iterations:1}); this.steps=steps; this.run=true; if (this.time>0) current.world.lag=current.world.lag+(curtime-this.time); this.time=curtime; if (!this.options.scheduler) { if (sync) { this.run=true; for(var step=0;step