From 48a7f164c2a1148d3c9d7728734f6ea45d26bdfc Mon Sep 17 00:00:00 2001 From: Joshua Chaitin-Pollak Date: Mon, 7 Oct 2019 11:38:30 -0400 Subject: [PATCH 1/2] feat: experimental compositional api # ------------------------------------------------| <- 50 characters | # ---------------------------------------------------------------------| <- 72 characters # # 1. Separate subject from body with a blank line # 2. Limit the subject line to 50 characters # 3. Capitalize the subject line # 4. Do not end the subject line with a period # 5. Use the imperative mood in the subject line # 6. Wrap the body at 72 characters # 7. Use the body to explain what and why vs. how # --- lib/tree/activateable.ts | 69 +++++++++++++++++++++++++++++++++++ lib/tree/after.ts | 28 ++++++++++++++ lib/tree/before.ts | 27 ++++++++++++++ lib/tree/decoratorFunction.ts | 3 ++ 4 files changed, 127 insertions(+) create mode 100644 lib/tree/activateable.ts create mode 100644 lib/tree/after.ts create mode 100644 lib/tree/before.ts create mode 100644 lib/tree/decoratorFunction.ts diff --git a/lib/tree/activateable.ts b/lib/tree/activateable.ts new file mode 100644 index 0000000..af0ea9b --- /dev/null +++ b/lib/tree/activateable.ts @@ -0,0 +1,69 @@ +import {BlueshellState} from '../nodes/BlueshellState'; +import {Base as Action} from '../nodes/Base'; +import {ResultCode, resultCodes} from '../utils/resultCodes'; + + +function activationCheck(storage: any) { + return storage.lastResult !== resultCodes.RUNNING; +} + +function reactivationCheck(storage: any, e: E) { + return (e).type === 'reactivate' || storage.lastResult !== resultCodes.RUNNING; +} + +class Activatable extends Action { + constructor( + name: string, + private readonly onActivateFn: RunningFn, + private readonly onRunningFn: RunningFn, + private readonly activationCheck: (storage: any, e: E) => boolean, + ) { + super(name); + } + + onEvent(state: S, event: E): ResultCode { + const storage = this.getNodeStorage(state); + + if (storage.lastResult !== resultCodes.ERROR) { + if (this.activationCheck) { + return this.onActivateFn(state, event); + } else { + return this.onRunningFn(state, event); + } + } else { + return resultCodes.ERROR; + } + } +} + +export interface ActivateFn { + (state: S, event: E): ResultCode; +} + +export interface RunningFn { + (state: S, event: E): ResultCode; +} + +export function activatable( + name: string, + onActivateFn: ActivateFn, + onRunningFn: RunningFn, +): Action { + return new Activatable( + name, + onActivateFn, + onRunningFn, + activationCheck); +} + +export function reactivatable( + name: string, + onActivateFn: ActivateFn, + onRunningFn: RunningFn, +): Action { + return new Activatable( + name, + onActivateFn, + onRunningFn, + reactivationCheck); +} diff --git a/lib/tree/after.ts b/lib/tree/after.ts new file mode 100644 index 0000000..5f2d637 --- /dev/null +++ b/lib/tree/after.ts @@ -0,0 +1,28 @@ +import {BlueshellState} from '../nodes/BlueshellState'; +import {Decorator} from '../nodes/Decorator'; +import {DecoratorFn} from './decoratorFunction'; +import {Base as Action} from '../nodes/Base'; +import {ResultCode} from '../utils/resultCodes'; + + +class After extends Decorator { + constructor( + name: string, + action: Action, + private readonly after: DecoratorFn, + ) { + super(name, action); + } + _afterEvent(res: ResultCode, s: S, e: E) { + this.after(s, e); + + return super._afterEvent(res, s, e); + } +} + +export function after( + action: Action, + afterFn: DecoratorFn, +): Action { + return new After(`before-$action.name`, action, afterFn); +} diff --git a/lib/tree/before.ts b/lib/tree/before.ts new file mode 100644 index 0000000..0af6b8d --- /dev/null +++ b/lib/tree/before.ts @@ -0,0 +1,27 @@ +import {BlueshellState} from '../nodes/BlueshellState'; +import {Decorator} from '../nodes/Decorator'; +import {DecoratorFn} from './decoratorFunction'; +import {Base as Action} from '../nodes/Base'; + + +class Before extends Decorator { + constructor( + name: string, + action: Action, + private readonly before: DecoratorFn, + ) { + super(name, action); + } + _beforeEvent(s: S, e: E) { + this.before(s, e); + + return super._beforeEvent(s, e); + } +} + +export function before( + action: Action, + beforeFn: DecoratorFn, +): Action { + return new Before(`before-$action.name`, action, beforeFn); +} diff --git a/lib/tree/decoratorFunction.ts b/lib/tree/decoratorFunction.ts new file mode 100644 index 0000000..f7e96ff --- /dev/null +++ b/lib/tree/decoratorFunction.ts @@ -0,0 +1,3 @@ +export interface DecoratorFn { + (state: S, event: E): void; +} From 8e61a6b73a16a1180737a726118feb3cf2cdc5e6 Mon Sep 17 00:00:00 2001 From: Joshua Chaitin-Pollak Date: Tue, 22 Oct 2019 10:31:33 -0400 Subject: [PATCH 2/2] ehh --- lib/tree/activateable.ts | 24 +++--- test/tree/complexTree.alt.spec.ts | 130 ++++++++++++++++++++++++++++++ test/tree/complexTree.spec.ts | 116 ++++++++++++++++++++++++++ 3 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 test/tree/complexTree.alt.spec.ts create mode 100644 test/tree/complexTree.spec.ts diff --git a/lib/tree/activateable.ts b/lib/tree/activateable.ts index af0ea9b..d88b771 100644 --- a/lib/tree/activateable.ts +++ b/lib/tree/activateable.ts @@ -2,7 +2,6 @@ import {BlueshellState} from '../nodes/BlueshellState'; import {Base as Action} from '../nodes/Base'; import {ResultCode, resultCodes} from '../utils/resultCodes'; - function activationCheck(storage: any) { return storage.lastResult !== resultCodes.RUNNING; } @@ -14,8 +13,8 @@ function reactivationCheck(storage: any, e: E) { class Activatable extends Action { constructor( name: string, - private readonly onActivateFn: RunningFn, - private readonly onRunningFn: RunningFn, + private readonly onActivateFn: RunningFn, + private readonly onRunningFn: RunningFn, private readonly activationCheck: (storage: any, e: E) => boolean, ) { super(name); @@ -36,20 +35,15 @@ class Activatable extends Action { } } -export interface ActivateFn { - (state: S, event: E): ResultCode; -} - -export interface RunningFn { - (state: S, event: E): ResultCode; -} +export type ActivateFn = (state: S, event: E) => ResultCode +export type RunningFn = (state: S, event: E) => ResultCode export function activatable( name: string, - onActivateFn: ActivateFn, - onRunningFn: RunningFn, + onActivateFn: ActivateFn, + onRunningFn: RunningFn, ): Action { - return new Activatable( + return new Activatable( name, onActivateFn, onRunningFn, @@ -58,8 +52,8 @@ export function activatable( export function reactivatable( name: string, - onActivateFn: ActivateFn, - onRunningFn: RunningFn, + onActivateFn: ActivateFn, + onRunningFn: RunningFn, ): Action { return new Activatable( name, diff --git a/test/tree/complexTree.alt.spec.ts b/test/tree/complexTree.alt.spec.ts new file mode 100644 index 0000000..deffb59 --- /dev/null +++ b/test/tree/complexTree.alt.spec.ts @@ -0,0 +1,130 @@ +import {activatable, RunningFn} from '../../lib/tree/activateable'; +import {resultCodes, ResultCode} from '../../lib/utils/resultCodes'; +import {BlueshellState} from '../../lib/nodes/BlueshellState'; + +export type isCompletionEventFn = (state: S, event: E) => boolean; +export type onCompleteFn = (state: S, event: E) => void; +export type onIncompleteFn = (state: S, event: E) => void; + +function mkRunningFn( + isCompletionEvent: isCompletionEventFn, + onComplete: onCompleteFn, + onIncomplete: onIncompleteFn, +): RunningFn { + // log.debug(`${this.name}: runningEvent`); + + return (state: S, event: E) => { + if (isCompletionEvent(state, event)) { + // console.log(`${this.name}: Event type '${event.type}' matches done condition`); + onComplete(state, event); + return resultCodes.SUCCESS; + } else { + onIncomplete(state, event); + return resultCodes.RUNNING; + } + } + +} + +interface TypedEvent { + type: string +}; + +interface ActionData { + +} + +interface CommandActionData { + name: string, + running?: RunningFn, +} + + +// commandAction +function commandAction( + actionData: CommandActionData, +) { + const setCommands = function(mfp: S, event: E) { + // const makeResult = this.makeCommand(mfp, event); + // let cmds = Array.isArray(makeResult) ? makeResult : [makeResult]; + + // if (mfp.debug && app.serviceConfig.enableVisualDebug) { + // cmds = cmds.concat(DebugCommand.debug(mfp.id, mfp.debug)); + // } + // mfp.outgoingCommands = cmds; + }; + + return activatable( + actionData.name, + (s: S, event: E) => { + console.log(`${this.name}: Activate`); + setCommands(s, event); + + return resultCodes.RUNNING; + }, + // not works + actionData.running || () => resultCodes.SUCCESS, + // works + // actionData.running ? actionData.running : () => resultCodes.SUCCESS, + ) +} + + +interface SimpleActionData implements ActionData { + name: string, + doneEventType: string, + onComplete?: onCompleteFn, + onIncomplete?: onIncompleteFn, +} + +// simpleAction +function simpleAction( + actionData: SimpleActionData, +) { + return commandAction({ + name, + running: mkRunningFn( + (s: S, event: E) => event.type === actionData.doneEventType, + actionData.onComplete || actionData.onComplete, + actionData.onIncomplete || actionData.onIncomplete, + ) + }); +} + + +class ButtonEvent implements TypedEvent { + type: 'button' + isAction: boolean +}; + +// buttonAction +function buttonAction( +) { + return simpleAction({ + name, + 'button', + (state: S, event: E) => { + return super.isCompletionEvent(event) && + (event instanceof ButtonEvent) && + event.isAction; + } + }); +} + +describe('complexTree', function() { + it('should build a complex tree', function() { + const bootup = activatable( + 'bootup', + () => resultCodes.RUNNING, + () => resultCodes.SUCCESS, + ); + + const waitForClick = + + const fullTree = latchedSequence([ + bootup, + ]); + + fullTree.handleEvent(); + }); +}); diff --git a/test/tree/complexTree.spec.ts b/test/tree/complexTree.spec.ts new file mode 100644 index 0000000..bd47564 --- /dev/null +++ b/test/tree/complexTree.spec.ts @@ -0,0 +1,116 @@ +import {activatable, RunningFn} from '../../lib/tree/activateable'; +import {resultCodes, ResultCode} from '../../lib/utils/resultCodes'; +import {BlueshellState} from '../../lib/nodes/BlueshellState'; + +// commandAction +function commandAction( + name: string, + running: RunningFn = () => resultCodes.SUCCESS, +) { + const setCommands = function(mfp: S, event: E) { + // const makeResult = this.makeCommand(mfp, event); + // let cmds = Array.isArray(makeResult) ? makeResult : [makeResult]; + + // if (mfp.debug && app.serviceConfig.enableVisualDebug) { + // cmds = cmds.concat(DebugCommand.debug(mfp.id, mfp.debug)); + // } + // mfp.outgoingCommands = cmds; + }; + + return activatable( + name, + (s: S, event: E) => { + console.log(`${this.name}: Activate`); + setCommands(s, event); + + return resultCodes.RUNNING; + }, + running + ) +} + +export type isCompletionEventFn = (state: S, event: E) => boolean; +export type onCompleteFn = (state: S, event: E) => void; +export type onIncompleteFn = (state: S, event: E) => void; + +function running( + isCompletionEvent: isCompletionEventFn, + onComplete: onCompleteFn, + onIncomplete: onIncompleteFn, +): RunningFn { + // log.debug(`${this.name}: runningEvent`); + + return (state: S, event: E) => { + if (isCompletionEvent(state, event)) { + // console.log(`${this.name}: Event type '${event.type}' matches done condition`); + onComplete(state, event); + return resultCodes.SUCCESS; + } else { + onIncomplete(state, event); + return resultCodes.RUNNING; + } + } + +} + +interface TypedEvent { + type: string +}; + +// simpleAction +function simpleAction( + name: string, + doneEventType: string, + onComplete: onCompleteFn = () => {}, + onIncomplete: onIncompleteFn = () => {}, +) { + return commandAction( + name, + running( + (s: S, event: E) => event.type === doneEventType, + onComplete, + onIncomplete, + ) + ); +} + +class ButtonEvent implements TypedEvent { + type: 'button' + isAction: boolean +}; + +// buttonAction +function buttonAction( + name: string, + doneEventType: string, + onComplete: onCompleteFn = () => {}, + onIncomplete: onIncompleteFn = () => {}, +) { + return simpleAction( + name, + 'button', + (state: S, event: E) => { + return super.isCompletionEvent(event) && + (event instanceof ButtonEvent) && + event.isAction; + } + ); +} + +describe('complexTree', function() { + it('should build a complex tree', function() { + const bootup = activatable( + 'bootup', + () => resultCodes.RUNNING, + () => resultCodes.SUCCESS, + ); + + const waitForClick = + + const fullTree = latchedSequence([ + bootup, + ]); + + fullTree.handleEvent(); + }); +});