Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions lib/tree/activateable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {BlueshellState} from '../nodes/BlueshellState';
import {Base as Action} from '../nodes/Base';
import {ResultCode, resultCodes} from '../utils/resultCodes';

function activationCheck<E>(storage: any) {
return storage.lastResult !== resultCodes.RUNNING;
}

function reactivationCheck<E>(storage: any, e: E) {
return (<any>e).type === 'reactivate' || storage.lastResult !== resultCodes.RUNNING;
}

class Activatable<S extends BlueshellState, E> extends Action<S, E> {
constructor(
name: string,
private readonly onActivateFn: RunningFn<S, E>,
private readonly onRunningFn: RunningFn<S, E>,
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 type ActivateFn<S extends BlueshellState, E> = (state: S, event: E) => ResultCode
export type RunningFn<S extends BlueshellState, E> = (state: S, event: E) => ResultCode

export function activatable<S extends BlueshellState, E>(
name: string,
onActivateFn: ActivateFn<S, E>,
onRunningFn: RunningFn<S, E>,
): Action<S, E> {
return new Activatable<S, E>(
name,
onActivateFn,
onRunningFn,
activationCheck);
}

export function reactivatable<S extends BlueshellState, E>(
name: string,
onActivateFn: ActivateFn<S, E>,
onRunningFn: RunningFn<S, E>,
): Action<S, E> {
return new Activatable(
name,
onActivateFn,
onRunningFn,
reactivationCheck);
}
28 changes: 28 additions & 0 deletions lib/tree/after.ts
Original file line number Diff line number Diff line change
@@ -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<S extends BlueshellState, E> extends Decorator<S, E> {
constructor(
name: string,
action: Action<S, E>,
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<S extends BlueshellState, E>(
action: Action<S, E>,
afterFn: DecoratorFn,
): Action<S, E> {
return new After(`before-$action.name`, action, afterFn);
}
27 changes: 27 additions & 0 deletions lib/tree/before.ts
Original file line number Diff line number Diff line change
@@ -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<S extends BlueshellState, E> extends Decorator<S, E> {
constructor(
name: string,
action: Action<S, E>,
private readonly before: DecoratorFn,
) {
super(name, action);
}
_beforeEvent(s: S, e: E) {
this.before(s, e);

return super._beforeEvent(s, e);
}
}

export function before<S extends BlueshellState, E>(
action: Action<S, E>,
beforeFn: DecoratorFn,
): Action<S, E> {
return new Before(`before-$action.name`, action, beforeFn);
}
3 changes: 3 additions & 0 deletions lib/tree/decoratorFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface DecoratorFn {
<S, E>(state: S, event: E): void;
}
130 changes: 130 additions & 0 deletions test/tree/complexTree.alt.spec.ts
Original file line number Diff line number Diff line change
@@ -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<S extends BlueshellState, E> = (state: S, event: E) => boolean;
export type onCompleteFn<S extends BlueshellState, E> = (state: S, event: E) => void;
export type onIncompleteFn<S extends BlueshellState, E> = (state: S, event: E) => void;

function mkRunningFn<S extends BlueshellState, E>(
isCompletionEvent: isCompletionEventFn<S, E>,
onComplete: onCompleteFn<S, E>,
onIncomplete: onIncompleteFn<S, E>,
): RunningFn<S, E> {
// 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<S extends BlueshellState, E extends TypedEvent> {
name: string,
running?: RunningFn<S, E>,
}


// commandAction
function commandAction<S extends BlueshellState, E extends TypedEvent>(
actionData: CommandActionData<S, E>,
) {
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<S, E>(
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<S extends BlueshellState, E extends TypedEvent> implements ActionData {
name: string,
doneEventType: string,
onComplete?: onCompleteFn<S, E>,
onIncomplete?: onIncompleteFn<S, E>,
}

// simpleAction
function simpleAction<S extends BlueshellState, E extends TypedEvent>(
actionData: SimpleActionData<S, E>,
) {
return commandAction<S, E>({
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<S extends BlueshellState, E extends TypedEvent>(
) {
return simpleAction<S, E>({
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();
});
});
116 changes: 116 additions & 0 deletions test/tree/complexTree.spec.ts
Original file line number Diff line number Diff line change
@@ -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<S extends BlueshellState, E>(
name: string,
running: RunningFn<S, E> = () => 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<S, E>(
name,
(s: S, event: E) => {
console.log(`${this.name}: Activate`);
setCommands(s, event);

return resultCodes.RUNNING;
},
running
)
}

export type isCompletionEventFn<S extends BlueshellState, E> = (state: S, event: E) => boolean;
export type onCompleteFn<S extends BlueshellState, E> = (state: S, event: E) => void;
export type onIncompleteFn<S extends BlueshellState, E> = (state: S, event: E) => void;

function running<S extends BlueshellState, E>(
isCompletionEvent: isCompletionEventFn<S, E>,
onComplete: onCompleteFn<S, E>,
onIncomplete: onIncompleteFn<S, E>,
): RunningFn<S, E> {
// 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<S extends BlueshellState, E extends TypedEvent>(
name: string,
doneEventType: string,
onComplete: onCompleteFn<S, E> = () => {},
onIncomplete: onIncompleteFn<S, E> = () => {},
) {
return commandAction<S, E>(
name,
running(
(s: S, event: E) => event.type === doneEventType,
onComplete,
onIncomplete,
)
);
}

class ButtonEvent implements TypedEvent {
type: 'button'
isAction: boolean
};

// buttonAction
function buttonAction<S extends BlueshellState, E extends TypedEvent>(
name: string,
doneEventType: string,
onComplete: onCompleteFn<S, E> = () => {},
onIncomplete: onIncompleteFn<S, E> = () => {},
) {
return simpleAction<S, E>(
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();
});
});