@barteh/machinize
Version:
a javascript library implements finite-state machine for application developement purpose
694 lines (535 loc) • 17.2 kB
JavaScript
/**
* FSM (finit machine state) javascript library for using in barteh production
* Borna Mehr Fann co.
* Ahad Rafat talebi
* 2017 * version 1.0.0
* not used
*/
/*
import * as Rx from "rxjs";
export class Machinize {
static fsmList = {};
static init(fsm) {
if (!fsm) return;
if (fsm.name === undefined || fsm.name === "") {
console.log("barteh fsm machinize error: fsm must have a name for init");
return;
}
if (fsm.imports) {
for (let i = 0; i < fsm.imports.length; i++) {
fsm.onStart = fsm.onStart || fsm.imports[i].onStart;
if (fsm.imports[i].data)
fsm.data = Object.assign({}, fsm.imports[i].data, fsm.data);
if (fsm.imports[i].states) {
fsm.states = fsm.states || {};
for (let st in fsm.imports[i].states) {
let stobj = fsm.imports[i].states[st];
fsm.states[st] = fsm.states[st] || {};
for (let el in stobj) {
fsm.states[st][el] = fsm.states[st][el] || stobj[el];
}
}
}
if (fsm.imports[i].transitions) {
fsm.transitions = fsm.transitions || {};
for (let tr in fsm.imports[i].transitions) {
let stobj = fsm.imports[i].transitions[tr];
fsm.transitions[tr] = fsm.transitions[tr] || {};
for (let el in stobj) {
fsm.transitions[tr][el] = fsm.transitions[tr][el] || stobj[el];
}
}
}
// fsm.transitions=Object.assign( {}, fsm.imports[i].transitions,fsm.transitions);
if (fsm.imports[i].actions)
fsm.actions = Object.assign({}, fsm.imports[i].actions, fsm.actions);
if (fsm.imports[i].computed)
fsm.computed = Object.assign(
{},
fsm.imports[i].computed,
fsm.computed
);
if (fsm.imports[i].parameters)
fsm.parameters = Object.assign(
{},
fsm.imports[i].parameters,
fsm.parameters
);
}
}
//if(fsm.imports)
// Object.assign(fsm, fsm.imports)
if (!fsm["$machine"]) fsm["$machine"] = new Machinize(fsm);
//if(Machinize.fsmList[fsm.name])
//return lst[0];
fsm["instanceList"] = [];
fsm["removeFromInstanceList"] = o => {
let idx = fsm.instanceList.indexOf(o);
if (idx != -1) fsm.instanceList.splice(idx, 1);
};
fsm["signalAll"] = (act, ...args) => {
let list = fsm.instanceList;
for (let i = 0; i < list.length; i++) {
list[i][act](...args);
}
};
fsm["destroyAll"] = func => {
let list = func ? fsm.instanceList.filter(func) : fsm.instanceList;
for (let i = 0; i < list.length; i++) {
if (!list[i]) continue;
if (list[i].destroy) list[i].destroy(true);
list[i] = null;
}
fsm.instanceList = fsm.instanceList.filter(a => a !== null);
};
fsm["stopAll"] = func => {
let list = func ? fsm.instanceList.filter(func) : fsm.instanceList;
for (let i = 0; i < list.length; i++) {
list[i].stop();
}
};
fsm["startAll"] = func => {
let list = func ? fsm.instanceList.filter(func) : fsm.instanceList;
for (let i = 0; i < fsm.instanceList.length; i++) {
fsm.instanceList[i].start();
}
};
fsm["createInstance"] = indata => {
try {
let inst = fsm.$machine.machinize(indata);
fsm.instanceList.push(inst);
return inst;
} catch (e) {
console.log(`barteh fsm error: cannot create instance, ${e.message}`);
}
};
Object.seal(fsm);
Machinize.fsmList[fsm.name] = fsm;
return fsm;
}
destroyAll() {}
constructor(fsm, insData) {
this.$prototype = fsm;
if (fsm.states === undefined) {
console.log(
`barteh Machinize fsm error no states in machine: ${fsm.name}`
);
return null;
}
if (fsm.transitions === undefined) {
console.log(
`barteh Machinize fsm error no transition in machine: ${fsm.name}`
);
return null;
}
if (fsm.imports) this.imports = fsm.imports;
this.onBeforeEnterAll = fsm.onBeforeEnterAll;
this.onEnterAll = fsm.onEnterAll;
this.onBeforeExitAll = fsm.onBeforeExitAll;
this.onExitAll = fsm.onExitAll;
fsm.data = fsm.data || {};
this._data = {};
if (fsm.data) {
for (let s in fsm.data) {
if (insData) {
this._data[s] = insData[s] || fsm.data[s];
this[s] = this._data[s];
} else {
this._data[s] = fsm.data[s];
this[s] = this._data[s];
}
}
}
this.state = null;
this.states = fsm.states;
for (let s in this.states) {
this.states[s].name = s;
this[s] = this.states[s];
if (this.states[s].start === true) this.state = this.states[s];
this.states[s].whenEnter = new Promise((res, rej) => {});
this.states[s].whenExit = new Promise((res, rej) => {});
}
this.computed = fsm.computed;
this.parameters = fsm.parameters;
this.transitions = {};
this._tHash = {};
this._tGroups = {};
for (let t in fsm.transitions) {
let trans = {};
trans.from = fsm.transitions[t].from;
trans.to = fsm.transitions[t].to;
trans.name = t;
trans.condition = fsm.transitions[t].condition;
trans.entryCondition = fsm.transitions[t].entryCondition;
trans.fromObject = this.states[trans.from];
trans.toObject = this.states[trans.to];
trans.group = fsm.transitions[t].group;
let hash = `${trans.from}_${trans.to}`;
this._tHash[hash] = trans;
trans.$hash = hash;
this.transitions[t] = trans;
if (trans.group) {
this._tGroups[trans.group] = this._tGroups[trans.group] || {};
let g = this._tGroups[trans.group];
g[trans.from] = trans; //(target)=>this[t](target);
}
}
for (let g in this._tGroups) {
this._tGroups[g].func = (target, feedback) => {
let trans = this._tGroups[g][target.$state];
if (trans) {
this.transit(trans, target, feedback);
}
// feedback={result:true,code:0,message:`transition ${g} not defined `,state:target.$state,target:target};
//return false;
//return this[this._tGroups[g][target.$state].name](target)
};
}
this.actions = fsm.actions;
//this.actions={};
//if(fsm.actions){
// for (let s in fsm.actions) {
// this["$"+s]=fsm.actions[s];
// this[s]=(target,...args)=> {
// if(target)
// if(!target.$isRunning)
// return;
// try{
// return this["$"+s](target,...args);
// }
// catch(e){
// console.log(`barteh Machinize fsm error in calling action:${s}`,e.message);
// }
// }
// this.actions[s]=this[s];
// }
//}
this.onStart = fsm.onStart || function() {};
this.state = this.state || this.states[Object.keys(this.states)[0]];
let feedback = {
result: true,
code: 0,
message: "",
state: this.state.name
};
fsm["$machine"] = this;
// Object.seal(this)
// this.onStart();
if (insData) return this.machinize(this, insData);
Object.seal(this);
}
get State() {
return this.state.name;
}
is(s) {
return s === this.state.name;
}
getTransition(f, t) {
return this._tHash[`${f}_${t}`];
}
transit(tr, target, feedback) {
if(target.$lock)return false;
target.$lock=true;
// let target=this;
if (target) if (!target.$isRunning) return false;
let sn = target ? target.$state : this.state.name;
let targetData = target || this._data;
if (tr.to === sn) return false;
//console.log(999999999999,sn,tr.to)
//if(sn!==tr.from)
if (!this.can(sn, tr.to)) {
console.log(
`barteh Machinize fsm warrning: cant do transition from state ${sn} to ${
tr.to
}`
);
return;
}
feedback = feedback || {
result: true,
code: 0,
message: "",
state: this.states[sn],
target: targetData,
fromState: tr.from
};
if (tr.condition) {
if (!tr.condition.call(targetData)) {
return false;
}
}
if (this.onBeforeExitAll)
if (this.onBeforeExitAll.call(targetData) === false) {
return false;
}
if (this.onBeforeEnterAll)
if (this.onBeforeEnterAll.call(targetData) === false) {
return false;
}
if (tr.fromObject.onBeforeExit)
if (tr.fromObject.onBeforeExit.call(targetData) === false) {
return false;
}
if (tr.toObject.onBeforeEnter)
if (tr.toObject.onBeforeEnter.call(targetData) === false) {
return false;
}
if (target) {
target.$state = tr.toObject.name;
target.$stateDescription = tr.toObject.description;
sn = tr.toObject.name;
} else {
this.state = tr.toObject;
sn = this.state;
}
feedback = {};
feedback.target = target;
feedback.state = sn;
feedback.fromState = tr.from;
feedback.result = true;
feedback.message = this.states[sn].description;
target.$_observable.next(feedback);
target.$lock=false;
if (tr.fromObject.onExit) tr.fromObject.onExit.call(targetData, feedback);
if (this.onExitAll) this.onExitAll.call(targetData, feedback);
if (tr.toObject.onEnter) tr.toObject.onEnter.call(targetData, feedback);
if (this.onEnterAll) this.onEnterAll.call(targetData, feedback);
//tr.toObject.whenEnter.resolve(feedback);
//tr.fromObject.whenExit.resolve(feedback);
this.refreshConditions(targetData);
return true;
}
go(s, target) {
let sn = target ? target.$state : this.state.name;
if (s === sn) return false;
let tr = this.getTransition(sn, s);
if (tr) return this.transit(tr(target));
else return false;
}
can(from, to) {
return this._tHash[`${from}_${to}`] !== undefined;
}
is(s) {
return this.state.name === s;
}
machinize(oo) {
if (oo) {
if (oo.$fsm) return oo;
}
let o = {};
let pro = new Proxy(o, {
set: (tar, k, v) => {
if (typeof tar[k] === "number") tar[k] = Number(v);
else tar[k] = v;
this.refreshConditions(o);
return true;
}
});
oo = oo || {};
o.$data = {};
for (let k in oo) {
o.$data[k] = oo[k];
o[k] = oo[k];
}
for (let k in this._data) {
if (!o[k]) {
o[k] = this._data[k];
o.$data[k] = this._data[k];
}
}
for (let k in this.parameters) {
if (!o[k]) {
o[k] = this.parameters[k];
}
}
// o.__proto__=this.__proto__;
let arrs = Object.getOwnPropertyNames(oo.__proto__);
for (let i = 0; i < arrs.length; i++) {
let ad = arrs[i];
if (ad !== "constructor") o[ad] = oo.__proto__[ad];
}
o["$fsm"] = this;
o["$state"] = this.state.name;
for (let a in this.actions) {
o[a] = (...args) => {
if (o.$isRunning) {
try {
return this.actions[a].call(o, ...args);
} catch (e) {
console.log(
`barteh machinize fsm error: calling action ${a} -> ${e.message}`
);
}
} else return;
};
//o[a]=()=>{
// if(o.$isRunning)
// return this[a](pro,arguments);
// else return;
//}
}
for (let tr in this.transitions) {
o[tr] = (...args) => this.transit(this.transitions[tr], pro, ...args);
//o[tr] = (...args) => this.transit(this.transitions[tr], o, ...args);
//ahad
}
for (let g in this._tGroups) {
o[g] = () => this._tGroups[g].func(o);
}
//
if (this.computed) {
o["$computed"] = {};
for (let cmp in this.computed) {
// o.$computed[cmp]=cmp;
Object.defineProperty(o, cmp, {
configurable: true,
get: () => {
if (!o.$isRunning) return;
try {
return o.$fsm.computed[cmp].call(o);
} catch (e) {
console.log(
`barteh machinize fsm error: computed cant resolve ${cmp} -> ${
e.message
}`
);
}
},
enumerable: true
});
}
}
o["$_observable"] = new Rx.Subject();
/// using $observable({from:'',to:''})
///using $observable('sSaved')
o["$observable"] = (st, fromst) => {
let from = undefined;
let to = undefined;
if (st === undefined) return o.$_observable.filter(a => a);
else if (typeof st === "string") to = st;
else if (typeof st === "object") {
from = st.from;
to = st.to;
if (typeof from !== "string" && typeof to === "string") {
console.log(
`barteh fsm warrning: observable in machine name ${
o.$fsm.name
} cant call with from and to both undefined`
);
return undefined;
}
} else if (typeof st === "function")
return o.$_observable.filter(a => a).filter(st);
else return undefined;
if (from)
if (!this.states[from]) {
console.log(
`barteh fsm warrning: observable in machine name ${
o.$fsm.name
} cant find from state: ${from} `
);
return undefined;
}
if (to)
if (!this.states[to]) {
console.log(
`barteh fsm warrning: observable in machine name ${
o.$fsm.name
} cant find to state: ${to} `
);
return undefined;
}
return o.$_observable
.filter(a => a)
.filter(
b =>
b.state === (to ? to : b.state) &&
b.fromState === (from ? from : b.fromState)
);
};
o["$stringfy"] = () => {
let ret = "";
let p = {};
for (let e in o) {
if (!e.match(/^\$(.*)/)) p[e] = o[e];
}
ret = JSON.stringify(p);
return ret;
};
let feedback = {
result: true,
code: 0,
message: "",
target: pro,
to: this.state
};
//o.$_observable.next(feedback);
//Object.seal(o);
//for(let imp in this.imports){
if (this.imports) {
o["$imports"] = {};
for (let i = 0; i < this.imports.length; i++) {
let imp = this.imports[i];
o.$imports[imp.name] = {};
if (imp.onStart) {
o.$imports[imp.name]["onStart"] = () => {
imp.onStart.call(pro);
};
}
}
}
o["$isRunning"] = !this.$prototype.stopOnCreate;
o["start"] = () => {
o.$isRunning = true;
if (this.states[o.$state].onEnter)
this.states[o.$state].onEnter.call(pro);
this.onStart.call(pro);
};
o["stop"] = () => (o.$isRunning = false);
o["destroy"] = self => {
//o.$fsm.$prototype.removeAll(a=>a===o);
if (!self) o.$fsm.$prototype.removeFromInstanceList();
for (let p in o) {
delete o[p];
}
o["destroy"] = () => {};
};
//
if (!this.$prototype.stopOnCreate) o.start();
return pro;
}
refreshConditions(o) {
if (!o.$isRunning) return;
if(o.$lock)return;
//console.log(778,o.name)
let notransit = false;
let whilecount = 0;
while (!notransit && whilecount < 100) {
//for all chain transitions conditions
whilecount++;
if (whilecount > 99) {
console.log(`machinize warnning: entrycondition loop over 100 times`);
break;
}
let relatedtransitions = Object.keys(this.transitions).filter(
j => this.transitions[j].from === o.$state
);
if (!relatedtransitions.length) break;
for (let m = 0; m < relatedtransitions.length; m++) {
let tr = this.transitions[relatedtransitions[m]];
if (tr.entryCondition) {
if (tr.entryCondition.call(o)) {
this.transit(tr, o);
//this[tr.name](o);
} else {
notransit = true;
}
} else {
notransit = true;
}
}
}
}
}
*/
"use strict";