916 lines
28 KiB
JavaScript
916 lines
28 KiB
JavaScript
/**
|
|
** ==============================
|
|
** O O O OOOO
|
|
** O O O O O O
|
|
** O O O O O O
|
|
** OOOO OOOO O OOO OOOO
|
|
** O O O O O O O
|
|
** O O O O O O O
|
|
** OOOO OOOO O O OOOO
|
|
** ==============================
|
|
** Dr. Stefan Bosse http://www.bsslab.de
|
|
**
|
|
** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
|
|
** BY THE AUTHOR(S).
|
|
** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
|
|
** MODIFIED, OR OTHERWISE USED IN A CONTEXT
|
|
** OUTSIDE OF THE SOFTWARE SYSTEM.
|
|
**
|
|
** $AUTHORS: Stefan Bosse
|
|
** $INITIAL: (C) 2006-2016 bLAB
|
|
** $CREATED: 24-3-15 by sbosse.
|
|
** $VERSION: 1.3.1
|
|
**
|
|
** $INFO:
|
|
**
|
|
** DOS: Task Scheduler
|
|
**
|
|
** $ENDOFINFO
|
|
*/
|
|
"use strict";
|
|
var log = 0;
|
|
var timestamp=1;
|
|
|
|
var Io = Require('com/io');
|
|
var util = Require('util');
|
|
var Comp = Require('com/compat');
|
|
var Json = Require('jam/jsonfn');
|
|
var String = Comp.string;
|
|
var Array = Comp.array;
|
|
var Perv = Comp.pervasives;
|
|
var trace = Io.tracing;
|
|
|
|
/**
|
|
*
|
|
* @type {undefined|taskcontext}
|
|
*/
|
|
var current = undefined;
|
|
/**
|
|
*
|
|
* @type {undefined|taskscheduler}
|
|
*/
|
|
var current_scheduler = undefined;
|
|
|
|
var scheduling = false;
|
|
var TICK=10;
|
|
/*
|
|
** Program time with TICK resolution
|
|
*/
|
|
var time=0;
|
|
|
|
/**
|
|
*
|
|
* @type {undefined|object}
|
|
*/
|
|
var timer=undefined;
|
|
|
|
/** The process context class
|
|
*
|
|
* @param {string} id
|
|
* @param {* []} [trans]
|
|
* @param {object} [obj]
|
|
* @constructor
|
|
* @typedef {{id,blocked,trans,block,obj,timeout,timer,state}} taskcontext~obj
|
|
* @see taskcontext~obj
|
|
*/
|
|
var taskcontext = function (id,trans,obj) {
|
|
this.id=id;
|
|
this.blocked=false;
|
|
this.event=0;
|
|
this.trans=trans;
|
|
this.block=[];
|
|
this.obj=obj;
|
|
this.timeout=0;
|
|
this.timer=0;
|
|
this.state=undefined;
|
|
};
|
|
/** The scheduler class
|
|
*
|
|
* @constructor
|
|
* @typedef {{context,callbacks,handler,current:taskcontext,nextid,lock,nested:number,reschedule:number}} taskscheduler~obj
|
|
* @see taskscheduler~obj
|
|
*/
|
|
var taskscheduler = function () {
|
|
var self=this;
|
|
this.context=[new taskcontext('root',[],self)];
|
|
this.callbacks=[];
|
|
this.handler=[];
|
|
this.current=undefined;
|
|
this.nextid=0;
|
|
this.lock=0;
|
|
this.nested=0;
|
|
this.reschedule=0;
|
|
current_scheduler=self;
|
|
};
|
|
|
|
/**
|
|
** Add a callback function executed once in the next scheduler run
|
|
** before any process (context) activity execution.
|
|
*
|
|
*
|
|
* @param {function|[]} callback that is fun or [fun] or [fun,arg1,arg2,..,arg9]
|
|
*/
|
|
taskscheduler.prototype.add_callback = function(callback) {
|
|
this.callbacks.push(callback);
|
|
};
|
|
|
|
/**
|
|
** Add a timer handler function executed once or cont. by the scheduler.
|
|
*
|
|
*
|
|
* @param {number} timeout
|
|
* @param {string} name
|
|
* @param {function} callback
|
|
* @param {boolean} once
|
|
*/
|
|
taskscheduler.prototype.add_timer = function(timeout,name,callback,once) {
|
|
var self=this;
|
|
var cont = new taskcontext(name);
|
|
Object.preventExtensions(cont);
|
|
var trans=[
|
|
[
|
|
undefined,
|
|
function () {
|
|
if (once==true) {
|
|
cont.timeout = 0;
|
|
cont.timer=0;
|
|
cont.blocked = true;
|
|
} else {
|
|
cont.timer = time + cont.timeout;
|
|
cont.blocked = true;
|
|
}
|
|
callback(cont)
|
|
},
|
|
function () {return !cont.blocked; }
|
|
]
|
|
];
|
|
cont.trans=trans;
|
|
cont.timeout=timeout;
|
|
cont.timer=time+timeout;
|
|
cont.blocked=true;
|
|
this.handler.push(cont);
|
|
};
|
|
|
|
/**
|
|
* Register a child scheduler function called on events (e.g., schedule next).
|
|
*/
|
|
taskscheduler.prototype.link = function(callback) {
|
|
this.schedulers.push(callback);
|
|
};
|
|
|
|
taskscheduler.prototype.log = function (v) {log=v};
|
|
|
|
/**
|
|
** Remove a timer handler identified by its name.
|
|
*
|
|
*
|
|
* @param {string} name
|
|
*/
|
|
taskscheduler.prototype.remove_timer = function(name) {
|
|
var i;
|
|
loop: for(i in this.handler) {
|
|
var handler=this.handler[i];
|
|
if (String.equal(handler.id,name))
|
|
{
|
|
this.handler.splice(i,1);
|
|
break loop;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
** next:
|
|
** fun
|
|
** [fun]
|
|
** [fun,arg1,arg2,..,arg9]
|
|
*/
|
|
function exec_block_fun(next) {
|
|
var fun = next[0]||next,
|
|
argn = next.length-1;
|
|
switch (argn) {
|
|
case 0:
|
|
case -1:
|
|
fun(); break;
|
|
case 1: fun(next[1]); break;
|
|
case 2: fun(next[1],next[2]); break;
|
|
case 3: fun(next[1],next[2],next[3]); break;
|
|
case 4: fun(next[1],next[2],next[3],next[4]); break;
|
|
case 5: fun(next[1],next[2],next[3],next[4],next[5]); break;
|
|
case 6: fun(next[1],next[2],next[3],next[4],next[5],next[6]); break;
|
|
case 7: fun(next[1],next[2],next[3],next[4],next[5],next[6],next[7]); break;
|
|
case 8: fun(next[1],next[2],next[3],next[4],next[5],next[6],next[7],next[8]); break;
|
|
case 9: fun(next[1],next[2],next[3],next[4],next[5],next[6],next[7],next[8],next[9]); break;
|
|
default:
|
|
Io.err('Schedule.exec_block_fun: more than 9 function arguments');
|
|
}
|
|
}
|
|
|
|
function schedule_block(con,manage) {
|
|
var next;
|
|
/*
|
|
** Process current function block sequence first!
|
|
** Format: [[fun,arg1,arg2,...],[block2], [block3], ..]
|
|
** Simplified: [fun,fun,...]
|
|
*/
|
|
if (!con.blocked) {
|
|
next = con.block[0];
|
|
con.block.splice(0,1);
|
|
/*
|
|
** Do no execute handler blocks maybe at the end of a subsection
|
|
** of the block list.
|
|
*/
|
|
while (!Array.empty(con.block) && next.handler!=undefined) {
|
|
next = con.block[0];
|
|
con.block.splice(0,1);
|
|
}
|
|
if (next.handler==undefined) {
|
|
current = con;
|
|
manage.scheduled++;
|
|
manage.scheduledcon++;
|
|
Io.log((log<2)||('Schedule [B], starting context '+current.id+' block['+con.block.length+']'));
|
|
Io.trace(trace||('Schedule [B], starting context '+current.id));
|
|
try {exec_block_fun(next)} catch(e) {
|
|
/*
|
|
** Iterate through the block list and try to find a handler entry.
|
|
*/
|
|
while (next.handler==undefined && !Array.empty(con.block)) {
|
|
next = con.block[0];
|
|
con.block.splice(0,1);
|
|
}
|
|
if (next.handler!=undefined) {
|
|
/*
|
|
** Call handler ...
|
|
*/
|
|
Io.log((log<2)||('[SCHE] executing exception handler for error '+e));
|
|
// console.log(next.handler.toString())
|
|
try {exec_block_fun([next.handler,e])}
|
|
catch (e) {
|
|
Io.out('Schedule [B], in context '+con.id+', got exception in exception handler: '+e);
|
|
// Io.printstack(e);
|
|
Io.out(Json.stringify(next).replace(/\\n/g,'\n'));
|
|
};
|
|
} else {
|
|
Io.out('Schedule [B], in context '+con.id+', got uncaught exception in schedule block: '+e);
|
|
// Io.printstack(e);
|
|
Io.out(Json.stringify(next).replace(/\\n/g,'\n'));
|
|
}
|
|
}
|
|
Io.log((log<2)||('Schedule [B], end context '+current.id+' blocked='+con.blocked+' block['+con.block.length+']'));
|
|
Io.trace(trace||('Schedule [B], end context '+current.id+' blocked='+con.blocked+' block['+con.block.length+']'));
|
|
if (con.blocked) manage.blocked++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Add a new process context
|
|
**
|
|
*
|
|
* @param {taskcontext} c
|
|
*/
|
|
taskscheduler.prototype.Add = function(c) {
|
|
this.context.push(c);
|
|
};
|
|
|
|
taskscheduler.prototype.Delay = function(millisec) {
|
|
current.timer=time+millisec;
|
|
current.timeout=0;
|
|
current.blocked=true;
|
|
};
|
|
|
|
taskscheduler.prototype.GetCurrent = function() {
|
|
return current;
|
|
};
|
|
|
|
taskscheduler.prototype.SetCurrent = function(context) {
|
|
var _current=current; current=context;
|
|
return _current;
|
|
};
|
|
|
|
taskscheduler.prototype.TaskContext = function(id,proc) {
|
|
var obj = new taskcontext(id,proc.transitions(),proc);
|
|
Object.preventExtensions(obj);
|
|
return obj;
|
|
};
|
|
|
|
|
|
|
|
/** Init scheduler
|
|
**
|
|
*
|
|
*/
|
|
taskscheduler.prototype.Init = function() {
|
|
var self=this;
|
|
var i,con;
|
|
time=0;
|
|
current_scheduler=self;
|
|
/*
|
|
** Set the root context
|
|
*/
|
|
current=self.context[0];
|
|
setInterval(function () {
|
|
// TBD lock
|
|
time=time+TICK;
|
|
for (i in self.context) {
|
|
con = self.context[i];
|
|
if (con.timer > 0 && con.timer <= time) {
|
|
// Timeout event occurred
|
|
con.timer=0;
|
|
con.blocked=false;
|
|
}
|
|
}
|
|
for (i in self.handler) {
|
|
con = self.handler[i];
|
|
if (con.timer > 0 && con.timer <= time && Array.empty(con.block)) {
|
|
// Timeout event occurred
|
|
// Schedule only handler with empty scheduling block!
|
|
Io.log((log<20)||('TIMEOUT '+con.timeout+' '+time));
|
|
con.timer=0;
|
|
con.blocked=false;
|
|
}
|
|
}
|
|
// TBD unlock
|
|
},TICK)
|
|
};
|
|
|
|
|
|
/** Scheduler run loop using timeout calls
|
|
*
|
|
*/
|
|
taskscheduler.prototype.Run = function() {
|
|
var i,con,
|
|
self=this,
|
|
reschedule=self.reschedule,
|
|
nexttime=0;
|
|
scheduling=true;
|
|
Io.trace(trace||('Run('+this.nested+')..'));
|
|
this.nested++;
|
|
if (timer != undefined) clearTimeout(timer);
|
|
while (self.Schedule()>0 || reschedule>0) {
|
|
reschedule=self.reschedule;
|
|
self.reschedule=Perv.max(0,self.reschedule-1);
|
|
}
|
|
// if there are only blocked processes start a timer calling the scheduler later...
|
|
|
|
for (i in self.context) {
|
|
con = self.context[i];
|
|
if (con.timeout>0) nexttime=(nexttime==0?con.timer:Perv.min(nexttime,con.timer));
|
|
}
|
|
for (i in self.handler) {
|
|
con = self.handler[i];
|
|
if (con.timeout>0) nexttime=(nexttime==0?con.timer:Perv.min(nexttime,con.timer));
|
|
}
|
|
/**
|
|
* If there are no timeout processes, we need no scheduling.
|
|
* But a timeout is required for proper JS processing. Each event
|
|
* will restart the timer!
|
|
*/
|
|
|
|
if (nexttime==0) nexttime=time+TICK*10;
|
|
timer=setTimeout(function () {self.Run ()},nexttime-time);
|
|
this.nested--;
|
|
Io.trace(trace||('Run('+this.nested+')+ '+(nexttime-time)));
|
|
|
|
scheduling=false;
|
|
};
|
|
|
|
/** One schedule iteration
|
|
*
|
|
* @returns {number}
|
|
*/
|
|
taskscheduler.prototype.Schedule = function() {
|
|
var i, j,con,cond,trans,state,state0,state1,remove,
|
|
manage={
|
|
scheduled:0,
|
|
blocked:0,
|
|
scheduledcon:0};
|
|
//var log=2;
|
|
|
|
// TBD lock
|
|
Io.trace(trace||('Schedule Start'));
|
|
Io.log((log<2)||('[SCHE '+Perv.mtime()+'] ('+time+') Schedule'));
|
|
/*
|
|
** First the urgent callbacks, if any, A callback may not suspend the execution!
|
|
** (Pure computational statements)
|
|
*/
|
|
for (i in this.callbacks) {
|
|
var callback=this.callbacks[i];
|
|
Io.log((log<2)||('[SCHE] executing callback ['+Array.length(callback)+']'));
|
|
exec_block_fun(callback);
|
|
}
|
|
|
|
|
|
this.callbacks=[];
|
|
/*
|
|
** Then the handlers, if any..
|
|
*/
|
|
var handler=[];
|
|
for (i in this.handler) {
|
|
con=this.handler[i];
|
|
remove=false;
|
|
Io.log((log<10)||('handler '+util.inspect(con)));
|
|
if (con.block && !Array.empty(con.block)) {
|
|
// what to do? timer handler is always blocked!
|
|
Io.log((log<2)||('Schedule [H], checking context '+con.id));
|
|
schedule_block(con,manage);
|
|
if (Array.empty(con.block)) {
|
|
/*
|
|
** Restore timer handler
|
|
*/
|
|
if (con.timeout<=0) {
|
|
// must be removed
|
|
remove=true;
|
|
} else {
|
|
con.blocked=true;
|
|
manage.blocked++;
|
|
con.state = state0;
|
|
if (con.timeout>0) con.timer = time + con.timeout;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
trans:for(j in con.trans) {
|
|
trans=con.trans[j];
|
|
state=con.state;
|
|
/*
|
|
** Transition row: [current activity function, next acitivity function, optional condition function]
|
|
*/
|
|
state0=trans[0];
|
|
state1=trans[1];
|
|
cond=trans[2];
|
|
if (!con.blocked && state==state0 && (cond==undefined || cond(cond.obj))) {
|
|
con.state=state1;
|
|
current=con;
|
|
manage.scheduled++;
|
|
Io.log((log<2)||('Schedule [H], starting context '+current.id));
|
|
Io.trace(trace||('Schedule [H], starting context '+current.id));
|
|
state1();
|
|
if (con.block && !Array.empty(con.block)) {
|
|
// a new block was added in the timer handler, unblock this handler!
|
|
con.blocked=false;
|
|
remove=false;
|
|
// must be restored after the block list was executed!
|
|
}
|
|
else {
|
|
if (con.timeout<=0) {
|
|
// must be removed
|
|
remove=true;
|
|
} else {
|
|
if (con.blocked) manage.blocked++;
|
|
con.state = state0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!remove) handler.push(con);
|
|
}
|
|
this.handler=handler;
|
|
/*
|
|
** Finally the context processes ..
|
|
*/
|
|
for (i in this.context) {
|
|
manage.scheduledcon=0;
|
|
con=this.context[i];
|
|
/*
|
|
** First the sequential statement blocks of the context process..
|
|
*/
|
|
if (con.block && !Array.empty(con.block)) {
|
|
schedule_block(con,manage);
|
|
}
|
|
if (!con.blocked && manage.scheduledcon==0) {
|
|
/*
|
|
** Second the transitional section of the context process, if any (maybe empty)
|
|
*/
|
|
trans:for (j in con.trans) {
|
|
trans = con.trans[j];
|
|
state = con.state;
|
|
state0 = trans[0];
|
|
state1 = trans[1];
|
|
cond = trans[2];
|
|
if (!con.blocked && state == state0 && (cond==undefined || cond(con.obj))) {
|
|
con.state = state1;
|
|
current = con;
|
|
Io.log((log<2)||('Schedule [T], starting context '+current.id));
|
|
Io.trace(trace||('Schedule [T], starting context '+current.id));
|
|
manage.scheduled++;
|
|
state1.call(con.obj);
|
|
if (con.blocked) manage.blocked++;
|
|
break trans;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
if (current.blocked) {
|
|
/*
|
|
** Try to find a non-blocked context (root?)
|
|
*/
|
|
loop: for (i in this.context) {
|
|
con = this.context[i];
|
|
if (!con.blocked) {
|
|
current = con;
|
|
break loop;
|
|
}
|
|
}
|
|
}
|
|
// TBD unlock
|
|
Io.log((log<2)||('[SCHE '+Perv.mtime()+'] ('+time+') End '+(manage.scheduled+manage.scheduledcon)));
|
|
Io.trace(trace||('Schedule End('+(manage.scheduled+manage.scheduledcon)+')'));
|
|
return (manage.scheduled+manage.scheduledcon);
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
*
|
|
* Mutex Lock Object
|
|
* May only be used in scheduling blocks (last statement of a block element)!!
|
|
*
|
|
* @constructor
|
|
* @typedef {{locked:bool,waiter:[],owner:taskcontext}} lock~obj
|
|
* @see lock~obj
|
|
*/
|
|
var lock = function() {
|
|
this.locked = false;
|
|
this.waiter = [];
|
|
this.owner = undefined;
|
|
};
|
|
|
|
lock.prototype.acquire = function () {
|
|
if (!this.locked) {
|
|
this.locked = true;
|
|
this.owner = current;
|
|
} else {
|
|
this.waiter.push(current);
|
|
current.blocked=true;
|
|
}
|
|
};
|
|
|
|
lock.prototype.try_acquire = function () {
|
|
if (!this.locked) {
|
|
this.locked = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
lock.prototype.release = function () {
|
|
if (!Array.empty(this.waiter)) {
|
|
var next = Array.head(this.waiter);
|
|
this.waiter = Array.tail(this.waiter);
|
|
next.blocked = false;
|
|
} else {
|
|
this.locked = false;
|
|
this.owner=undefined;
|
|
}
|
|
};
|
|
|
|
lock.prototype.init = function () {
|
|
this.locked = false;
|
|
this.owner = undefined;
|
|
this.waiter = [];
|
|
};
|
|
|
|
lock.prototype.is_locked = function () {
|
|
return this.locked;
|
|
};
|
|
|
|
var modu = {
|
|
TICK:TICK,
|
|
time:time,
|
|
/**
|
|
*
|
|
* @param timeout
|
|
* @param name
|
|
* @param {function([context])} callback
|
|
* @param [once]
|
|
*/
|
|
AddTimer: function(timeout,name,callback,once) {
|
|
if (current_scheduler) current_scheduler.add_timer(timeout,name,callback,once);
|
|
this.ScheduleNext();
|
|
},
|
|
/**
|
|
*
|
|
* @param name
|
|
*/
|
|
RemoveTimer: function(name) {
|
|
if (current_scheduler) current_scheduler.remove_timer(name);
|
|
},
|
|
/**
|
|
*
|
|
* @returns {taskscheduler}
|
|
*/
|
|
TaskScheduler: function() {
|
|
var obj = new taskscheduler();
|
|
Object.preventExtensions(obj);
|
|
return obj;
|
|
},
|
|
/** Create a new transitional task context (virtual process)
|
|
* The process object consists of activity functions and a transition function.
|
|
* The object transition function variable must have the name 'transitions'.
|
|
* The transition function must return an array in the format:
|
|
* [
|
|
* [undefined,act_start] // initial start activity
|
|
* [act1,act2,function(self){return <cond>}], // conditional transitions
|
|
* [act2,act3] // unconditional transition
|
|
* ..
|
|
* ]
|
|
* A transition from an outgoing activity function (of the process) act_i
|
|
* to another acitivty function a_j occurs only: 1. If the context is not blocked;
|
|
* 2. If the condition is satisfied.
|
|
*
|
|
* @param {string} id
|
|
* @param {object} proc
|
|
* @returns {taskcontext}
|
|
*/
|
|
TaskContext: function(id,proc) {
|
|
var obj = new taskcontext(id,proc.transitions(),proc);
|
|
Object.preventExtensions(obj);
|
|
return obj;
|
|
},
|
|
/** Create and initialize a new transitional task context from a process constructor.
|
|
* Simplified version of TaskContext. Returns the created process object.
|
|
*
|
|
* Proc = function (arg) {
|
|
* this.a1 = function () {}; ..
|
|
* this.a2 = function () {}; ..
|
|
* this.transitions = [ [ai,aj,cond], .. ];
|
|
* this.on = { err: function () {}, ..}; *optional*
|
|
* }
|
|
*
|
|
*
|
|
*/
|
|
NewTask: function (id,Proc,arg) {
|
|
var proc, context;
|
|
proc = new Proc(arg);
|
|
context = new taskcontext(id,proc.transitions,proc);
|
|
proc.context=context;
|
|
current_scheduler.Add(proc.context);
|
|
return proc;
|
|
},
|
|
/** Create and add a new functional task context. Inside the function
|
|
* scheduling blocks and loops can be used.
|
|
*
|
|
* @param {taskscheduler|undefined} sched
|
|
* @param id
|
|
* @param fun
|
|
* @param [arg]
|
|
* @returns {taskcontext}
|
|
*/
|
|
FunContext: function(sched,id,fun,arg) {
|
|
var Sch=this, proccon, proc;
|
|
if (sched==undefined) sched=current_scheduler;
|
|
proccon = function () {
|
|
var self=this;
|
|
this.act =function(){fun(arg)};
|
|
this.transitions = function(){
|
|
var trans;
|
|
trans = [
|
|
[undefined,this.act]
|
|
];
|
|
return trans;
|
|
};
|
|
this.context = Sch.TaskContext(id, self);
|
|
};
|
|
proc = new proccon();
|
|
sched.Add(proc.context);
|
|
return proc.context;
|
|
},
|
|
Bind: function (object, method) {
|
|
return method.bind(object);
|
|
},
|
|
Delay: function(millisec) {
|
|
current.timer=time+millisec;
|
|
current.timeout=0;
|
|
current.blocked=true;
|
|
},
|
|
GetId: function() {return current.id;},
|
|
/** Return current context
|
|
*
|
|
* @returns {undefined|taskcontext}
|
|
*/
|
|
GetCurrent: function() {return current;},
|
|
/** Return current context
|
|
*
|
|
* @param [taskcontext]
|
|
* @returns {undefined|taskcontext}
|
|
*/
|
|
SetCurrent: function(context) {var _current=current; current=context; return _current},
|
|
/** Get current scheduler
|
|
*
|
|
* @returns {undefined|taskscheduler}
|
|
*/
|
|
GetScheduler: function() {return current_scheduler;},
|
|
/** Get current system time
|
|
*
|
|
* @returns {number}
|
|
*/
|
|
GetTime: function() {return time;},
|
|
|
|
/** Call the scheduler ASAP.
|
|
* Usefull after a Wakeup call.
|
|
*/
|
|
Schedule: function() {
|
|
Io.trace(trace||('Schedule'));
|
|
if (!scheduling && current_scheduler != undefined) {
|
|
if (timer != undefined) clearTimeout(timer);
|
|
timer=undefined;
|
|
//timer=setTimeout(function () {current_scheduler.Run ()},0);
|
|
current_scheduler.Run ()
|
|
} else {
|
|
current_scheduler.reschedule++;
|
|
}
|
|
},
|
|
/** Call the scheduler ASAP, eventually with a callback function executed first.
|
|
* Must be preemption save!
|
|
*
|
|
*
|
|
* @param [callback]
|
|
* @param [args]
|
|
*/
|
|
ScheduleNext: function(callback,args) {
|
|
Io.trace(trace||('ScheduleNext'));
|
|
if (!scheduling && current_scheduler != undefined) {
|
|
if (callback) current_scheduler.add_callback(callback,args);
|
|
if (timer != undefined) clearTimeout(timer);
|
|
timer=undefined;
|
|
//timer=setTimeout(function () {current_scheduler.Run ()},0);
|
|
current_scheduler.Run ()
|
|
} else {
|
|
if (callback) current_scheduler.add_callback(callback,args);
|
|
current_scheduler.reschedule++;
|
|
}
|
|
},
|
|
/**
|
|
** Schedule an asynchronous callback function execution.
|
|
* Must be preemption-save! If we are currently scheduling, queue the
|
|
* callback, otherwise execute it immediately,
|
|
*
|
|
*
|
|
* @param {function|[]} callback that is fun or [fun] or [fun,arg1,arg2,..,arg9]
|
|
*
|
|
*/
|
|
ScheduleCallback: function(callback) {
|
|
Io.trace(trace||('ScheduleNext'));
|
|
if (!scheduling) {
|
|
scheduling=true;
|
|
exec_block_fun(callback);
|
|
scheduling=false;
|
|
/*
|
|
** Pending scheduler run?
|
|
*/
|
|
if (current_scheduler.reschedule>0) {
|
|
if (timer != undefined) clearTimeout(timer);
|
|
timer=undefined;
|
|
//timer=setTimeout(function () {current_scheduler.Run ()},0);
|
|
current_scheduler.Run ()
|
|
}
|
|
} else {
|
|
current_scheduler.add_callback(callback);
|
|
current_scheduler.reschedule++;
|
|
}
|
|
},
|
|
/**
|
|
** Add a scheduler function execution block to the current context.
|
|
** Each entry in the block array (a partition) is executed in the given order sequentially.
|
|
** Each entry of [[fun,arg1,arg2,...],[block2], [block3], ..] may block (suspend execution).
|
|
** Notes:
|
|
** - Bind methods to respective objects: [Sch.Bind(ob,obj.method),..]
|
|
** - The scheduler block must be the last (and(or only) statement in an activity or a function.
|
|
* - A blocking statement must be the last statement in a block partition.
|
|
** - The current context activity may NOT be blocked with a Sch.Suspend operation!!
|
|
*
|
|
*
|
|
* @param {* []} block [[fun,arg1,arg2,...],[block2], [block3], ..] or simplified [fun,fun,..]
|
|
* @param {function} [handler] optional exception handler function fun(exception)
|
|
*/
|
|
ScheduleBlock: function(block,handler) {
|
|
/*
|
|
** Schedule sequence of functions that may block (suepend execution of current context process).
|
|
** If there are already block elements, add the new
|
|
** block elements to the top (!!) of the current block.
|
|
*/
|
|
if (handler!=undefined ) block.push({handler:handler});
|
|
if (current.block.length == 0) current.block=block;
|
|
else current.block=Array.merge(block,current.block);
|
|
},
|
|
/**
|
|
*
|
|
* @param {function(index:number):boolean} cond function() { return cond; }
|
|
* @param {* []} body [[fun,arg1,arg2,...],[block2], [block3], ..] or simplified [fun,fun,..]
|
|
* @param {* []} [finalize] optional loop finalize block
|
|
* @param {function} [handler] optional exception handler function fun(exception)
|
|
*
|
|
* Note: NEVER raise an exception in a callback called in a block function if there is a handler here!
|
|
* Another still active handler can be executed instead!
|
|
*/
|
|
ScheduleLoop: function(cond,body,finalize,handler) {
|
|
var self=this;
|
|
var index=0;
|
|
/*
|
|
** Iterate and schedule a block
|
|
* cond: function(index) { return cond; }
|
|
*/
|
|
var block = [
|
|
function() {
|
|
if (cond(index)) {
|
|
self.ScheduleBlock(body.slice());
|
|
} else if (finalize!=undefined) {
|
|
self.ScheduleBlock(finalize);
|
|
}
|
|
},
|
|
function() {
|
|
index++;
|
|
},
|
|
function () {
|
|
if (cond(index)) {
|
|
self.ScheduleBlock(block.slice());
|
|
}
|
|
else if (finalize!=undefined) {
|
|
self.ScheduleBlock(finalize);
|
|
}
|
|
}
|
|
];
|
|
if (handler!=undefined ) block.push({handler:handler});
|
|
self.ScheduleBlock(block.slice());
|
|
},
|
|
/**
|
|
*
|
|
* @param blocked
|
|
* @param [context]
|
|
*/
|
|
SetBlocked: function(blocked,context) {
|
|
if (context==undefined) {
|
|
current.blocked=blocked;
|
|
} else {
|
|
context.blocked=blocked;
|
|
if (context.blocked==false && context.parent) {
|
|
if (context.parent.blocked) {
|
|
context.parent.blocked=false;
|
|
}
|
|
// console.log(context.parent.id);
|
|
context.parent.event++;
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
*
|
|
* @param context
|
|
* @returns {*|boolean}
|
|
*/
|
|
IsBlocked: function(context) {
|
|
if (context==undefined)
|
|
return current.blocked;
|
|
else
|
|
return context.blocked;
|
|
},
|
|
/**
|
|
*
|
|
* @param context
|
|
*/
|
|
Suspend: function(context) {
|
|
Io.log((log<3)||('[SCH] Suspend: '+(context?context.id:current.id) ));
|
|
if (context==undefined)
|
|
current.blocked=true;
|
|
else
|
|
context.blocked=true;
|
|
return current;
|
|
},
|
|
/** Wake up a context. Modifies only the context.
|
|
* Use Schedule() to force ASAP scheduling.
|
|
*
|
|
* @param context
|
|
*/
|
|
Wakeup: function(context) {
|
|
context.blocked = false;
|
|
if (context.parent) {
|
|
if (context.parent.blocked) {
|
|
context.parent.blocked=false;
|
|
}
|
|
// console.log(context.parent);
|
|
context.parent.event++;
|
|
}
|
|
Io.log((log<3)||('[SCH] Wakekup: '+context.id));
|
|
},
|
|
/**
|
|
** Mutex Lock Object
|
|
** May only be used in scheduling blocks (last statement of a block element)!!
|
|
*
|
|
* @returns {lock}
|
|
*/
|
|
Lock: function() {
|
|
var obj = new lock();
|
|
Object.preventExtensions(obj);
|
|
return obj;
|
|
}
|
|
|
|
};
|
|
module.exports=modu;
|
|
modu.B = module.exports.ScheduleBlock.bind(module.exports);
|
|
modu.L = module.exports.ScheduleLoop.bind(module.exports);
|
|
global.B = module.exports.ScheduleBlock.bind(module.exports);
|
|
global.L = module.exports.ScheduleLoop.bind(module.exports);
|
|
global.Delay = module.exports.Delay.bind(module.exports);
|