Skip to content

Commit

Permalink
Merge pull request #458 from StoneCypher/ApiExitHooks
Browse files Browse the repository at this point in the history
API exit hooks, fixes StoneCypher/fsl#664
  • Loading branch information
StoneCypher committed May 9, 2022
2 parents e7fdb06 + 4ce87db commit 99fccf0
Show file tree
Hide file tree
Showing 17 changed files with 129 additions and 46 deletions.
14 changes: 7 additions & 7 deletions benchmark/results/general.chart.html
Expand Up @@ -28,7 +28,7 @@
</head>
<body>
<div class="container">
<canvas id="chart1652112845461" width="16" height="9"></canvas>
<canvas id="chart1652115341663" width="16" height="9"></canvas>
</div>
<script>
const format = (num) => {
Expand All @@ -51,18 +51,18 @@
chunked.map((chunk) => chunk.join('')).join(' ') + fractionStr
)
}
const ctx1652112845461 = document
.getElementById('chart1652112845461')
const ctx1652115341663 = document
.getElementById('chart1652115341663')
.getContext('2d')
const chart1652112845461 = new Chart(ctx1652112845461, {
const chart1652115341663 = new Chart(ctx1652115341663, {
type: 'bar',
data: {
labels: ["Blind cycle a traffic light 500 times by transition","Blind cycle a hooked traffic light 500 times by transition","Blind cycle a traffic light 500 times by action","Blind cycle a hooked traffic light 500 times by action"],
datasets: [
{
data: [20703,1737,14324,835],
backgroundColor: ["hsl(120, 85%, 55%)","hsl(10.068, 85%, 55%)","hsl(83.02799999999999, 85%, 55%)","hsl(4.836000000000001, 85%, 55%)"],
borderColor: ["hsl(120, 85%, 55%)","hsl(10.068, 85%, 55%)","hsl(83.02799999999999, 85%, 55%)","hsl(4.836000000000001, 85%, 55%)"],
data: [18730,1543,14281,805],
backgroundColor: ["hsl(120, 85%, 55%)","hsl(9.887999999999993, 85%, 55%)","hsl(91.5, 85%, 55%)","hsl(5.159999999999997, 85%, 55%)"],
borderColor: ["hsl(120, 85%, 55%)","hsl(9.887999999999993, 85%, 55%)","hsl(91.5, 85%, 55%)","hsl(5.159999999999997, 85%, 55%)"],
borderWidth: 2,
},
],
Expand Down
24 changes: 12 additions & 12 deletions benchmark/results/general.json
@@ -1,31 +1,31 @@
{
"name": "General performance suite",
"date": "2022-05-09T16:14:05.461Z",
"date": "2022-05-09T16:55:41.663Z",
"version": "1.1.0",
"results": [
{
"name": "Blind cycle a traffic light 500 times by transition",
"ops": 20703,
"margin": 1.34,
"ops": 18730,
"margin": 1.2,
"percentSlower": 0
},
{
"name": "Blind cycle a hooked traffic light 500 times by transition",
"ops": 1737,
"margin": 0.48,
"percentSlower": 91.61
"ops": 1543,
"margin": 5.49,
"percentSlower": 91.76
},
{
"name": "Blind cycle a traffic light 500 times by action",
"ops": 14324,
"margin": 4.06,
"percentSlower": 30.81
"ops": 14281,
"margin": 1.96,
"percentSlower": 23.75
},
{
"name": "Blind cycle a hooked traffic light 500 times by action",
"ops": 835,
"margin": 2.35,
"percentSlower": 95.97
"ops": 805,
"margin": 1.05,
"percentSlower": 95.7
}
],
"fastest": {
Expand Down
2 changes: 2 additions & 0 deletions dist/es6/jssm.d.ts
Expand Up @@ -41,9 +41,11 @@ declare class Machine<mDT> {
_has_basic_hooks: boolean;
_has_named_hooks: boolean;
_has_entry_hooks: boolean;
_has_exit_hooks: boolean;
_hooks: Map<string, Function>;
_named_hooks: Map<string, Function>;
_entry_hooks: Map<string, Function>;
_exit_hooks: Map<string, Function>;
_any_transition_hook: HookHandler | undefined;
constructor({ start_states, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout }: JssmGenericConfig<mDT>);
_new_state(state_config: JssmGenericState): StateType;
Expand Down
16 changes: 12 additions & 4 deletions dist/es6/jssm.js
Expand Up @@ -349,10 +349,12 @@ class Machine {
this._has_basic_hooks = false;
this._has_named_hooks = false;
this._has_entry_hooks = false;
this._has_exit_hooks = false;
// no need for a boolean has any transition hook, as it's one or nothing, so just test that for undefinedness
this._hooks = new Map();
this._named_hooks = new Map();
this._entry_hooks = new Map();
this._exit_hooks = new Map();
this._any_transition_hook = undefined;
if (state_declaration) {
state_declaration.map((state_decl) => {
Expand Down Expand Up @@ -724,9 +726,10 @@ class Machine {
this._entry_hooks.set(HookDesc.to, HookDesc.handler);
this._has_hooks = true;
break;
// case 'exit':
// console.log('TODO: Should add exit hook here');
// throw 'TODO: Should add exit hook here';
case 'exit':
this._exit_hooks.set(HookDesc.from, HookDesc.handler);
this._has_hooks = true;
break;
default:
console.log(`Unknown hook type ${HookDesc.kind}, should be impossible`);
throw new RangeError(`Unknown hook type ${HookDesc.kind}, should be impossible`);
Expand Down Expand Up @@ -789,7 +792,12 @@ class Machine {
}
}
// 3. exit hook
// not yet implemented
const maybe_ex_hook = this._exit_hooks.get(this._state);
if (maybe_ex_hook !== undefined) {
if (maybe_ex_hook({ from: this._state, forced: wasForced }) === false) {
return false;
}
}
// 4. named transition / action hook
if (wasAction) {
const nhn = named_hook_name(this._state, newState, newStateOrAction), n_maybe_hook = this._named_hooks.get(nhn);
Expand Down
2 changes: 1 addition & 1 deletion dist/es6/version.js
@@ -1,2 +1,2 @@
const version = "5.56.2";
const version = "5.57.0";
export { version };
2 changes: 1 addition & 1 deletion dist/jssm.es5.cjs.js

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions dist/jssm.es5.cjs.nonmin.js
Expand Up @@ -15886,7 +15886,7 @@ function peg$parse(input, options) {
}
}

const version = "5.56.2";
const version = "5.57.0";

// whargarbl lots of these return arrays could/should be sets
/* eslint-disable complexity */
Expand Down Expand Up @@ -16235,10 +16235,12 @@ class Machine {
this._has_basic_hooks = false;
this._has_named_hooks = false;
this._has_entry_hooks = false;
this._has_exit_hooks = false;
// no need for a boolean has any transition hook, as it's one or nothing, so just test that for undefinedness
this._hooks = new Map();
this._named_hooks = new Map();
this._entry_hooks = new Map();
this._exit_hooks = new Map();
this._any_transition_hook = undefined;
if (state_declaration) {
state_declaration.map((state_decl) => {
Expand Down Expand Up @@ -16610,9 +16612,10 @@ class Machine {
this._entry_hooks.set(HookDesc.to, HookDesc.handler);
this._has_hooks = true;
break;
// case 'exit':
// console.log('TODO: Should add exit hook here');
// throw 'TODO: Should add exit hook here';
case 'exit':
this._exit_hooks.set(HookDesc.from, HookDesc.handler);
this._has_hooks = true;
break;
default:
console.log(`Unknown hook type ${HookDesc.kind}, should be impossible`);
throw new RangeError(`Unknown hook type ${HookDesc.kind}, should be impossible`);
Expand Down Expand Up @@ -16675,7 +16678,12 @@ class Machine {
}
}
// 3. exit hook
// not yet implemented
const maybe_ex_hook = this._exit_hooks.get(this._state);
if (maybe_ex_hook !== undefined) {
if (maybe_ex_hook({ from: this._state, forced: wasForced }) === false) {
return false;
}
}
// 4. named transition / action hook
if (wasAction) {
const nhn = named_hook_name(this._state, newState, newStateOrAction), n_maybe_hook = this._named_hooks.get(nhn);
Expand Down
2 changes: 1 addition & 1 deletion dist/jssm.es5.iife.js

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions dist/jssm.es5.iife.nonmin.js
Expand Up @@ -15885,7 +15885,7 @@ var jssm = (function (exports) {
}
}

const version = "5.56.2";
const version = "5.57.0";

// whargarbl lots of these return arrays could/should be sets
/* eslint-disable complexity */
Expand Down Expand Up @@ -16234,10 +16234,12 @@ var jssm = (function (exports) {
this._has_basic_hooks = false;
this._has_named_hooks = false;
this._has_entry_hooks = false;
this._has_exit_hooks = false;
// no need for a boolean has any transition hook, as it's one or nothing, so just test that for undefinedness
this._hooks = new Map();
this._named_hooks = new Map();
this._entry_hooks = new Map();
this._exit_hooks = new Map();
this._any_transition_hook = undefined;
if (state_declaration) {
state_declaration.map((state_decl) => {
Expand Down Expand Up @@ -16609,9 +16611,10 @@ var jssm = (function (exports) {
this._entry_hooks.set(HookDesc.to, HookDesc.handler);
this._has_hooks = true;
break;
// case 'exit':
// console.log('TODO: Should add exit hook here');
// throw 'TODO: Should add exit hook here';
case 'exit':
this._exit_hooks.set(HookDesc.from, HookDesc.handler);
this._has_hooks = true;
break;
default:
console.log(`Unknown hook type ${HookDesc.kind}, should be impossible`);
throw new RangeError(`Unknown hook type ${HookDesc.kind}, should be impossible`);
Expand Down Expand Up @@ -16674,7 +16677,12 @@ var jssm = (function (exports) {
}
}
// 3. exit hook
// not yet implemented
const maybe_ex_hook = this._exit_hooks.get(this._state);
if (maybe_ex_hook !== undefined) {
if (maybe_ex_hook({ from: this._state, forced: wasForced }) === false) {
return false;
}
}
// 4. named transition / action hook
if (wasAction) {
const nhn = named_hook_name(this._state, newState, newStateOrAction), n_maybe_hook = this._named_hooks.get(nhn);
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/assets/search.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/docs/classes/Machine.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/docs/modules.html

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions jssm.d.ts
Expand Up @@ -41,9 +41,11 @@ declare class Machine<mDT> {
_has_basic_hooks: boolean;
_has_named_hooks: boolean;
_has_entry_hooks: boolean;
_has_exit_hooks: boolean;
_hooks: Map<string, Function>;
_named_hooks: Map<string, Function>;
_entry_hooks: Map<string, Function>;
_exit_hooks: Map<string, Function>;
_any_transition_hook: HookHandler | undefined;
constructor({ start_states, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout }: JssmGenericConfig<mDT>);
_new_state(state_config: JssmGenericState): StateType;
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "jssm",
"version": "5.56.2",
"version": "5.57.0",
"engines": {
"node": ">=10.0.0"
},
Expand Down
19 changes: 15 additions & 4 deletions src/ts/jssm.ts
Expand Up @@ -481,11 +481,13 @@ class Machine<mDT> {
_has_basic_hooks : boolean;
_has_named_hooks : boolean;
_has_entry_hooks : boolean;
_has_exit_hooks : boolean;
// no boolean for _has_any_transition_hook

_hooks : Map<string, Function>;
_named_hooks : Map<string, Function>;
_entry_hooks : Map<string, Function>;
_exit_hooks : Map<string, Function>;
_any_transition_hook : HookHandler | undefined;


Expand Down Expand Up @@ -548,11 +550,13 @@ class Machine<mDT> {
this._has_basic_hooks = false;
this._has_named_hooks = false;
this._has_entry_hooks = false;
this._has_exit_hooks = false;
// no need for a boolean has any transition hook, as it's one or nothing, so just test that for undefinedness

this._hooks = new Map();
this._named_hooks = new Map();
this._entry_hooks = new Map();
this._exit_hooks = new Map();
this._any_transition_hook = undefined;


Expand Down Expand Up @@ -1027,9 +1031,10 @@ class Machine<mDT> {
this._has_hooks = true;
break;

// case 'exit':
// console.log('TODO: Should add exit hook here');
// throw 'TODO: Should add exit hook here';
case 'exit':
this._exit_hooks.set(HookDesc.from, HookDesc.handler);
this._has_hooks = true;
break;

default:
console.log(`Unknown hook type ${(HookDesc as any).kind}, should be impossible`);
Expand Down Expand Up @@ -1126,7 +1131,13 @@ class Machine<mDT> {
}

// 3. exit hook
// not yet implemented
const maybe_ex_hook: Function | undefined = this._exit_hooks.get(this._state);

if (maybe_ex_hook !== undefined) {
if (maybe_ex_hook({ from: this._state, forced: wasForced }) === false) {
return false;
}
}

// 4. named transition / action hook
if (wasAction) {
Expand Down
46 changes: 45 additions & 1 deletion src/ts/tests/hooks.spec.ts
Expand Up @@ -183,7 +183,7 @@ test('All-transition hook rejection works on actions', () => {



test('Entry hook rejection works on actions', () => {
test('Entry hook rejection works', () => {

const foo = sm`a => b => c;`;

Expand Down Expand Up @@ -227,6 +227,50 @@ test('Fluent entry hook works', () => {



test('Exit hook rejection works', () => {

const foo = sm`a => b => c;`;

// test that an unused handler is never hit, for coverage
foo.set_hook({ kind: 'exit', from: 'c', handler: () => { throw 'Should never hit, should be unused'; } });

foo.set_hook({ kind: 'exit', from: 'a', handler: () => false });
expect(foo.transition('b')).toBe(false);
expect(foo.state()).toBe('a');

foo.set_hook({ kind: 'exit', from: 'a', handler: () => true });
expect(foo.transition('b')).toBe(true);
expect(foo.state()).toBe('b');

// test that a third step doesn't inappropriately fire the handler, for coverage
expect(foo.transition('c')).toBe(true);

});





// test('Fluent exit hook works', () => {

// const cnt = jest.fn(x => true),
// nope = jest.fn(x => true);

// const foo = sm`a => b => c;`
// .hook_exit('a', cnt)
// .hook_exit('b', nope);

// foo.transition('b');

// expect(cnt.mock.calls.length).toBe(1);
// expect(nope.mock.calls.length).toBe(0);

// });





test('All-transition hook rejection works on transitions', () => {

const foo = sm`a => b;`;
Expand Down
2 changes: 1 addition & 1 deletion src/ts/version.ts
@@ -1,3 +1,3 @@

const version: string = "5.56.2";
const version: string = "5.57.0";
export { version };

0 comments on commit 99fccf0

Please sign in to comment.