Skip to content

Commit

Permalink
First go at allowing calls to Instance.evaluate in actions
Browse files Browse the repository at this point in the history
  • Loading branch information
David Mesquita-Morris committed Nov 12, 2019
1 parent c1e0319 commit 46176e8
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 47 deletions.
3 changes: 3 additions & 0 deletions RELEASES.md
@@ -1,3 +1,6 @@
## v8.1.3
Allow calls to Instance.evaluate within an entry/exit and transition actions.

## v8.1.0
Added stronger transaction management by way of an internal transaction class and removal of unnecessary members and methods from Instance class.

Expand Down
3 changes: 2 additions & 1 deletion lib/node/Instance.d.ts
Expand Up @@ -7,6 +7,8 @@ export declare class Instance {
readonly root: State;
/** The stable active state configuration of the state machine, conveying the last known state for each region. */
private activeStateConfiguration;
/** The currently active transaction */
private transaction;
/**
* Creates a new state machine instance conforming to a particular state machine model.
* @param name The name of the state machine instance.
Expand All @@ -23,7 +25,6 @@ export declare class Instance {
* Performs an operation that may alter the active state configuration with a transaction.
* @param TReturn The return type of the transactional operation.
* @param operation The operation to perform within a transaction.
* @param transaction The current transaction being executed; if not passed explicitly, one will be created on demand.
* @return Returns the result of the operation.
*/
private transactional;
Expand Down
46 changes: 31 additions & 15 deletions lib/node/Instance.js
Expand Up @@ -23,7 +23,13 @@ var Instance = /** @class */ (function () {
* @hidden
*/
this.deferredEventPool = [];
this.transactional(function (transaction) { return _this.root.doEnter(transaction, false, _this.root); }); // enter the root element
this.transactional(function (transaction) {
_this.root.doEnter(transaction, false, _this.root); // enter the root element
if (_this.deferredEventPool.length !== 0) {
_this.evaluateDeferred(transaction);
_this.deferredEventPool = _this.deferredEventPool.filter(function (t) { return t; }); // repack the deferred event pool
}
});
}
/**
* Evaluates a trigger event to see if it causes a state transition.
Expand All @@ -33,27 +39,37 @@ var Instance = /** @class */ (function () {
Instance.prototype.evaluate = function (trigger) {
var _this = this;
_1.log.write(function () { return _this + " evaluate " + trigger; }, _1.log.Evaluate);
return this.transactional(function (transaction) {
var result = _this.root.evaluate(transaction, false, trigger); // evaluate the trigger event
if (result && _this.deferredEventPool.length !== 0) { // if there are deferred events, process them
_this.evaluateDeferred(transaction);
_this.deferredEventPool = _this.deferredEventPool.filter(function (t) { return t; }); // repack the deferred event pool
}
return result;
});
if (this.transaction) {
this.defer(trigger);
return false;
}
else {
return this.transactional(function (transaction) {
var result = _this.root.evaluate(transaction, false, trigger); // evaluate the trigger event
if (result && _this.deferredEventPool.length !== 0) { // if there are deferred events, process them
_this.evaluateDeferred(transaction);
_this.deferredEventPool = _this.deferredEventPool.filter(function (t) { return t; }); // repack the deferred event pool
}
return result;
});
}
};
/**
* Performs an operation that may alter the active state configuration with a transaction.
* @param TReturn The return type of the transactional operation.
* @param operation The operation to perform within a transaction.
* @param transaction The current transaction being executed; if not passed explicitly, one will be created on demand.
* @return Returns the result of the operation.
*/
Instance.prototype.transactional = function (operation, transaction) {
if (transaction === void 0) { transaction = new Transaction_1.Transaction(this); }
var result = operation(transaction);
Object.assign(this.activeStateConfiguration, transaction.activeStateConfiguration);
return result;
Instance.prototype.transactional = function (operation) {
try {
this.transaction = new Transaction_1.Transaction(this);
var result = operation(this.transaction);
Object.assign(this.activeStateConfiguration, this.transaction.activeStateConfiguration);
return result;
}
finally {
this.transaction = undefined;
}
};
/**
* Add a trigger event to the deferred event pool.
Expand Down
4 changes: 2 additions & 2 deletions lib/node/State.d.ts
Expand Up @@ -31,13 +31,13 @@ export declare class State extends Vertex {
* @param actions One or callbacks that will be passed the trigger event.
* @return Returns the state thereby allowing a fluent style state construction.
*/
entry(...actions: types.Consumer<any>[]): this;
entry(...actions: types.Behaviour<any>[]): this;
/**
* Adds a user-defined behaviour to call on state exit.
* @param actions One or callbacks that will be passed the trigger event.
* @return Returns the state thereby allowing a fluent style state construction.
*/
exit(...actions: Array<types.Consumer<any>>): this;
exit(...actions: Array<types.Behaviour<any>>): this;
/**
* Adds the types of trigger event that can .
* @param actions One or callbacks that will be passed the trigger event.
Expand Down
4 changes: 2 additions & 2 deletions lib/node/State.js
Expand Up @@ -243,7 +243,7 @@ var State = /** @class */ (function (_super) {
}
_super.prototype.doEnterHead.call(this, transaction, history, trigger, next);
transaction.setState(this);
this.entryActions.forEach(function (action) { return action(trigger); });
this.entryActions.forEach(function (action) { return action(trigger, transaction.instance); });
};
/**
* Performs the final steps required to enter a state during a state transition including cascading the entry operation to child elements and completion transition.
Expand All @@ -268,7 +268,7 @@ var State = /** @class */ (function (_super) {
State.prototype.doExit = function (transaction, history, trigger) {
this.children.forEach(function (region) { return region.doExit(transaction, history, trigger); });
_super.prototype.doExit.call(this, transaction, history, trigger);
this.exitActions.forEach(function (action) { return action(trigger); });
this.exitActions.forEach(function (action) { return action(trigger, transaction.instance); });
};
/**
* Evaluates completion transitions at the state.
Expand Down
2 changes: 1 addition & 1 deletion lib/node/Transition.d.ts
Expand Up @@ -35,7 +35,7 @@ export declare class Transition<TTrigger = any> {
* @param actions The action, or actions to call with the trigger event as a parameter.
* @return Returns the transitions thereby allowing a fluent style transition construction.
*/
effect(...actions: Array<types.Consumer<TTrigger>>): this;
effect(...actions: Array<types.Behaviour<TTrigger>>): this;
/**
* Returns the transition in string form.
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/node/Transition.js
Expand Up @@ -143,7 +143,7 @@ var Transition = /** @class */ (function () {
var _this = this;
_1.log.write(function () { return transaction.instance + " traverse " + _this; }, _1.log.Transition);
this.strategy.doExitSource(transaction, history, trigger);
this.traverseActions.forEach(function (action) { return action(trigger); });
this.traverseActions.forEach(function (action) { return action(trigger, transaction.instance); });
this.strategy.doEnterTarget(transaction, history, trigger);
};
/**
Expand Down
3 changes: 3 additions & 0 deletions lib/node/types.d.ts
@@ -1,3 +1,4 @@
import { Instance } from "./Instance";
/**
* Generic types to simplify the specification of function prototypes used in callbacks.
*/
Expand All @@ -6,6 +7,8 @@ export declare namespace types {
type Constructor<T> = new (...args: any[]) => T;
/** Prototype of a function taking a single argument of a specific type and returning anything. */
type Consumer<T> = (arg: T) => any;
/** Prototype of a function taking a single argument of a specific type and returning anything. */
type Behaviour<T> = (arg: T, instance: Instance) => any;
/** Prototype for any function taking a single parameter. */
type Function<T, R> = (arg: T) => R;
/** Prototype of a function taking a single argument of a specific type and returning a boolean result. */
Expand Down
2 changes: 1 addition & 1 deletion lib/web/state.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "@steelbreeze/state",
"version": "8.1.2",
"version": "8.1.3",
"description": "Finite state machine for TypeScript and JavaScript",
"main": "lib/node/index.js",
"typings": "lib/node/index.d.ts",
Expand Down
50 changes: 36 additions & 14 deletions src/Instance.ts
Expand Up @@ -8,6 +8,9 @@ export class Instance {
/** The stable active state configuration of the state machine, conveying the last known state for each region. */
private activeStateConfiguration: Record<string, State> = {};

/** The currently active transaction */
private transaction: Transaction | undefined;

/**
* The deferred triggers awaiting evaluation once the current active state configuration changes.
* @internal
Expand All @@ -21,7 +24,15 @@ export class Instance {
* @param root The root state of the state machine instance.
*/
public constructor(public readonly name: string, public readonly root: State) {
this.transactional((transaction: Transaction) => this.root.doEnter(transaction, false, this.root)); // enter the root element
this.transactional((transaction: Transaction) => {
this.root.doEnter(transaction, false, this.root); // enter the root element

if (this.deferredEventPool.length !== 0) {
this.evaluateDeferred(transaction);

this.deferredEventPool = this.deferredEventPool.filter(t => t); // repack the deferred event pool
}
});
}

/**
Expand All @@ -32,32 +43,43 @@ export class Instance {
public evaluate(trigger: any): boolean {
log.write(() => `${this} evaluate ${trigger}`, log.Evaluate);

return this.transactional((transaction: Transaction) => {
const result = this.root.evaluate(transaction, false, trigger); // evaluate the trigger event
if (this.transaction) {
this.defer(trigger);

if (result && this.deferredEventPool.length !== 0) { // if there are deferred events, process them
this.evaluateDeferred(transaction);
return false;
} else {
return this.transactional((transaction: Transaction) => {
const result = this.root.evaluate(transaction, false, trigger); // evaluate the trigger event

this.deferredEventPool = this.deferredEventPool.filter(t => t); // repack the deferred event pool
}
if (result && this.deferredEventPool.length !== 0) { // if there are deferred events, process them
this.evaluateDeferred(transaction);

return result;
});
this.deferredEventPool = this.deferredEventPool.filter(t => t); // repack the deferred event pool
}

return result;
});
}
}

/**
* Performs an operation that may alter the active state configuration with a transaction.
* @param TReturn The return type of the transactional operation.
* @param operation The operation to perform within a transaction.
* @param transaction The current transaction being executed; if not passed explicitly, one will be created on demand.
* @return Returns the result of the operation.
*/
private transactional<TReturn>(operation: (transaction: Transaction) => TReturn, transaction: Transaction = new Transaction(this)): TReturn {
const result = operation(transaction);
private transactional<TReturn>(operation: (transaction: Transaction) => TReturn): TReturn {
try {
this.transaction = new Transaction(this);

Object.assign(this.activeStateConfiguration, transaction.activeStateConfiguration);
const result = operation(this.transaction);

return result;
Object.assign(this.activeStateConfiguration, this.transaction.activeStateConfiguration);

return result;
} finally {
this.transaction = undefined;
}
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/State.ts
Expand Up @@ -26,12 +26,12 @@ export class State extends Vertex {
/**
* The user-defined actions that will be called upon state entry.
*/
private entryActions: Array<types.Consumer<any>> = [];
private entryActions: Array<types.Behaviour<any>> = [];

/**
* The user-defined actions that will be called upon state exit.
*/
private exitActions: Array<types.Consumer<any>> = [];
private exitActions: Array<types.Behaviour<any>> = [];

/**
* Creates a new instance of the state class.
Expand All @@ -47,7 +47,7 @@ export class State extends Vertex {
* @param actions One or callbacks that will be passed the trigger event.
* @return Returns the state thereby allowing a fluent style state construction.
*/
public entry(...actions: types.Consumer<any>[]): this {
public entry(...actions: types.Behaviour<any>[]): this {
this.entryActions.push(...actions);

return this;
Expand All @@ -58,7 +58,7 @@ export class State extends Vertex {
* @param actions One or callbacks that will be passed the trigger event.
* @return Returns the state thereby allowing a fluent style state construction.
*/
public exit(...actions: Array<types.Consumer<any>>): this {
public exit(...actions: Array<types.Behaviour<any>>): this {
this.exitActions.push(...actions);

return this;
Expand Down Expand Up @@ -217,7 +217,7 @@ export class State extends Vertex {

transaction.setState(this);

this.entryActions.forEach(action => action(trigger));
this.entryActions.forEach(action => action(trigger, transaction.instance));
}

/**
Expand Down Expand Up @@ -247,7 +247,7 @@ export class State extends Vertex {

super.doExit(transaction, history, trigger);

this.exitActions.forEach(action => action(trigger));
this.exitActions.forEach(action => action(trigger, transaction.instance));
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/Transition.ts
Expand Up @@ -44,7 +44,7 @@ export class Transition<TTrigger = any> {
* @internal
* @hidden
*/
private traverseActions: Array<types.Consumer<TTrigger>> = [];
private traverseActions: Array<types.Behaviour<TTrigger>> = [];

/**
* The precise semantics of the transition traversal based on the transition type.
Expand Down Expand Up @@ -107,7 +107,7 @@ export class Transition<TTrigger = any> {
* @param actions The action, or actions to call with the trigger event as a parameter.
* @return Returns the transitions thereby allowing a fluent style transition construction.
*/
effect(...actions: Array<types.Consumer<TTrigger>>): this {
effect(...actions: Array<types.Behaviour<TTrigger>>): this {
this.traverseActions.push(...actions);

return this;
Expand Down Expand Up @@ -156,7 +156,7 @@ export class Transition<TTrigger = any> {

this.strategy.doExitSource(transaction, history, trigger);

this.traverseActions.forEach(action => action(trigger));
this.traverseActions.forEach(action => action(trigger, transaction.instance));

this.strategy.doEnterTarget(transaction, history, trigger);
}
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
@@ -1,3 +1,5 @@
import { Instance } from "./Instance";

/**
* Generic types to simplify the specification of function prototypes used in callbacks.
*/
Expand All @@ -8,6 +10,9 @@ export namespace types {
/** Prototype of a function taking a single argument of a specific type and returning anything. */
export type Consumer<T> = (arg: T) => any;

/** Prototype of a function taking a single argument of a specific type and returning anything. */
export type Behaviour<T> = (arg: T, instance: Instance) => any;

/** Prototype for any function taking a single parameter. */
export type Function<T, R> = (arg: T) => R;

Expand Down
29 changes: 29 additions & 0 deletions test/ogis-shinki.js
@@ -0,0 +1,29 @@
var state = require("../lib/node");

// create event class that a transition will respond to
class MyEvent {
constructor(fieldA, fieldB) { this.fieldA = fieldA; this.fieldB = fieldB; }

toString() { return JSON.stringify(this); }
}

// log state entry, exit and trigger event evaluation
state.log.add(message => console.info(message), state.log.Entry | state.log.Exit | state.log.Evaluate);

// create the state machine model elements
const model = new state.State("model");
const initial = new state.PseudoState("initial", model, state.PseudoStateKind.Initial);
const stateA = new state.State("stateA", model);
const stateB = new state.State("stateB", model).entry(() => { console.log("hellkow B") }).exit(() => { console.log("bye B") });

// HERE
stateA.entry((trigger, instance) => {
instance.evaluate(new MyEvent(1, 2));
});

stateA.on(MyEvent).when((e) => { return e.fieldA === 1; }).to(stateB);
stateB.on(MyEvent).when((e) => { return e.fieldA === 1; }).to(stateA);
// create the transition from initial pseudo state to stateA
initial.to(stateA);

let instance = new state.Instance("instance", model);

0 comments on commit 46176e8

Please sign in to comment.