Skip to content

Commit

Permalink
feat: Execute workflow action
Browse files Browse the repository at this point in the history
Closes #35
  • Loading branch information
Alorel committed Jan 24, 2023
1 parent 165e04a commit 4ead535
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/actions/action-id.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const enum ActionId {
StartSkillSummoning = 31,
StartSkillThieving = 32,
StartSkillWoodcutting = 33,
ExecWorkflow = 34,
}

export default ActionId;
5 changes: 2 additions & 3 deletions src/actions/core/delay-action.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {noop} from 'lodash-es';
import {Fragment} from 'preact';
import {map, timer} from 'rxjs';
import {timer} from 'rxjs';
import {InternalCategory} from '../../lib/registries/action-registry.mjs';
import {defineLocalAction} from '../../lib/util/define-local.mjs';
import ActionId from '../action-id.mjs';
Expand All @@ -17,7 +16,7 @@ defineLocalAction<Props>({
<span class={'text-primary'}>{`${duration.toLocaleString()}ms`}</span>
</Fragment>
),
execute: ({duration}) => timer(duration).pipe(map(noop)),
execute: ({duration}) => timer(duration),
id: ActionId.CoreDelay,
label: 'Wait',
media: cdnMedia('assets/media/main/timer.svg'),
Expand Down
10 changes: 1 addition & 9 deletions src/actions/core/equip-item-action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {EquipSlotType} from 'melvor';
import {Fragment} from 'preact';
import {InternalCategory} from '../../lib/registries/action-registry.mjs';
import {defineLocalAction} from '../../lib/util/define-local.mjs';
import {objectFromArray} from '../../lib/util/obj-from-array.mjs';
import {BigNum} from '../../ui/components/big-num';
import {RenderNodeMedia} from '../../ui/pages/workflows-dashboard/render-node-media';
import ActionId from '../action-id.mjs';
Expand Down Expand Up @@ -76,15 +77,6 @@ defineLocalAction<Props>({
],
});

function objectFromArray<T extends string>(values: T[]): Record<T, T> {
const out: Record<T, T> = {} as any;
for (const v of values) {
out[v] = v;
}

return out;
}

const SLOTS_WITH_QTY = new Set<EquipSlotType>([
EquipSlotType.Consumable,
EquipSlotType.Quiver,
Expand Down
114 changes: 114 additions & 0 deletions src/actions/core/exec-workflow-action.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {takeUntil} from 'rxjs';
import {take} from 'rxjs/operators';
import type {WorkflowStep} from '../../lib/data/workflow-step.mjs';
import type {Workflow} from '../../lib/data/workflow.mjs';
import {WorkflowExecution} from '../../lib/execution/workflow-execution.mjs';
import {InternalCategory} from '../../lib/registries/action-registry.mjs';
import WorkflowRegistry from '../../lib/registries/workflow-registry.mjs';
import {defineLocalAction} from '../../lib/util/define-local.mjs';
import {memoOutput} from '../../lib/util/memo.mjs';
import type {Obj} from '../../public_api';
import {mainIcon} from '../../ui/ui.mjs';
import ActionId from '../action-id.mjs';

interface Props {
name: string;
stop: StopCondition;
}

const enum StopCondition {
NextStepTrigger = 's',
WorkflowCompletion = 'w',
}

const reduceWorkflowNames = (acc: Obj<string>, {name}: Workflow): Obj<string> => {
acc[name] = name;

return acc;
};
const getWorkflowNames = memoOutput((): Obj<string> => (
WorkflowRegistry.inst.workflows.reduce(reduceWorkflowNames, {})
));

defineLocalAction<Props>({
category: InternalCategory.CORE,
execute({name, stop}) {
const reg = WorkflowRegistry.inst;
const workflow = reg.workflows.find(wf => wf.name === name);

if (!workflow) {
throw new Error(`Workflow not found: ${name}`);
}

switch (stop) {
case StopCondition.WorkflowCompletion:
return new WorkflowExecution(workflow);
case StopCondition.NextStepTrigger: {
const exec = reg.primaryExecution;
if (!exec) {
throw new Error('There is no primary execution running');
}

const activeIdx = exec.activeStepIdx;
const steps = exec.workflow.steps;

let nextStep: WorkflowStep | undefined = steps[activeIdx + 1];
if (!nextStep) { // Check for "jump to step" action
const currStep = steps[activeIdx];
if (!currStep) {
throw new Error('Screwy primary execution: current step not found in steps array');
}

const jumpToStepAction = currStep.actions.find(a => (

// @ts-expect-error
a.action.id === ActionId.CoreSetStepIdx
));
if (!jumpToStepAction) {
throw new Error('There is no next step');
}

const tryIdx: number | undefined = jumpToStepAction.opts.idx;
if (typeof tryIdx !== 'number') {
throw new Error('Can\'t resolve next step: jump to step index not a number');
}

nextStep = steps[tryIdx];
if (!nextStep) {
throw new Error('Can\'t resolve next step: jump to step index leads to nothing');
}
}

return new WorkflowExecution(workflow).pipe(
takeUntil(nextStep.trigger.listen().pipe(take(1)))
);
}
default:
throw new Error(`Unknown stop condition: ${stop}`);
}
},
id: ActionId.ExecWorkflow,
initOptions: () => ({stop: StopCondition.NextStepTrigger}),
label: 'Execute workflow',
media: mainIcon,
options: [
{
enum: getWorkflowNames,
id: 'name',
label: 'Name',
required: true,
type: String,
},
{
description: '"Workflow completion" will execute the inner workflow until it\'s done; "Next step\'s trigger" will execute it until it completes or until the trigger for the next step fires. You CAN use a "Jump to step" action immediately following this one with the "next step trigger" option.',
enum: {
[StopCondition.NextStepTrigger]: 'Next step\'s trigger',
[StopCondition.WorkflowCompletion]: 'Workflow completion',
},
id: 'stop',
label: 'Stop condition',
required: true,
type: String,
},
],
});
1 change: 1 addition & 0 deletions src/actions/core/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './buy-item-action';
import './delay-action';
import './equip-food-action';
import './equip-item-action';
import './exec-workflow-action.mjs';
import './sell-item-action.mjs';
import './set-step-idx-action.mjs';
import './switch-equipment-set-action.mjs';
Expand Down
14 changes: 9 additions & 5 deletions src/lib/execution/workflow-execution.mts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export class WorkflowExecution extends ShareReplayLike<Out> {
@AutoIncrement()
public readonly id!: number;

/** Set to true when it's run as part of, e.g. the exec workflow action */
public isEmbeddedRun = false;

/** Currently executed step index */
private readonly _activeStepIdx$: BehaviorSubject<number>;

Expand Down Expand Up @@ -116,6 +119,11 @@ export class WorkflowExecution extends ShareReplayLike<Out> {
return this.workflow.steps[this.activeStepIdx];
}

/** Should this workflow be removed on completion? */
private get shouldRm(): boolean {
return !this.isEmbeddedRun && Boolean(ctx.accountStorage.getItem(ConfigCheckboxKey.RM_WORKFLOW_ON_COMPLETE));
}

/** Set the currently executed step index. */
@BoundMethod()
public setActiveStepIdx(v: number): void {
Expand Down Expand Up @@ -172,7 +180,7 @@ export class WorkflowExecution extends ShareReplayLike<Out> {
}
}

if (shouldRemoveWorkflowOnCompletion()) {
if (this.shouldRm) {
unsetRemoveWorkflowOnCompletion();
WorkflowRegistry.inst.rmByListId(this.workflow.listId);
} else {
Expand Down Expand Up @@ -370,10 +378,6 @@ export class WorkflowExecution extends ShareReplayLike<Out> {
}
}

function shouldRemoveWorkflowOnCompletion(): boolean {
return Boolean(ctx.accountStorage.getItem(ConfigCheckboxKey.RM_WORKFLOW_ON_COMPLETE));
}

function unsetRemoveWorkflowOnCompletion(): void {
ctx.accountStorage.removeItem(ConfigCheckboxKey.RM_WORKFLOW_ON_COMPLETE);
}
25 changes: 25 additions & 0 deletions src/lib/util/memo.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {isEqual} from 'lodash-es';

type This<T> = T extends (this: infer E) => any ? E : never;

/**
* Returns the previously returned object if calling the function again yields the same output
* @param fn The function
* @param checkFn Function used for output equality checking
*/
export function memoOutput<T extends(...args: any[]) => any>(
fn: T,
checkFn: (a: ReturnType<T>, b: ReturnType<T>) => boolean = isEqual
): T {
let prevReturn: ReturnType<T>;

return function memoOutputFn(this: This<T>): ReturnType<T> {
const out = fn.apply(this, arguments as unknown as Parameters<T>);
if (checkFn(out, prevReturn)) {
return prevReturn;
}

prevReturn = out;
return out;
} as T;
}
8 changes: 8 additions & 0 deletions src/lib/util/obj-from-array.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function objectFromArray<T extends string>(values: T[]): Record<T, T> {
const out: Record<T, T> = {} as any;
for (const v of values) {
out[v] = v;
}

return out;
}
2 changes: 1 addition & 1 deletion src/public_api/action.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export interface ActionNodeDefinition<T extends object> extends NodeDefinition<T
execContext?: boolean;

/** Execute the action */
execute(data: T, executionContext?: WorkflowExecutionCtx): void | ObservableInput<void>;
execute(data: T, executionContext?: WorkflowExecutionCtx): void | ObservableInput<any>;
}
2 changes: 1 addition & 1 deletion src/public_api/option.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface StringNodeOption extends NodeOptionBase {
* Render a `<select/>` of these options instead of an `<input type="text"/>`.
* key = model value, value = display label
*/
enum?: DynamicOption<Obj<string> | undefined>;
enum?: DynamicOption<Obj<string>>;

/** The `placeholder` attribute for the `<input/>` element */
placeholder?: string;
Expand Down
4 changes: 2 additions & 2 deletions src/ui/ui.mts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ interface BaseCat {
itemID: string;
}

export const mainIcon = githubAsset('src/ui/assets/icon.png', '0.5.0');

// Init package
{
const mainIcon = githubAsset('src/ui/assets/icon.png', '0.5.0');

const cat = <T extends object = {}>(config?: T): T & BaseCat => ({
categoryID: '',
itemID: startCase(namespace),
Expand Down

0 comments on commit 4ead535

Please sign in to comment.