50 lines
2.6 KiB
JavaScript
50 lines
2.6 KiB
JavaScript
import { RuntimeFault, isRecord } from './types.js';
|
|
|
|
export class ActionExecutor {
|
|
constructor(engine, { diagnostics } = {}) {
|
|
this.engine = engine;
|
|
this.diagnostics = diagnostics;
|
|
this.invocationOrdinal = 0;
|
|
}
|
|
|
|
execute(actions, context = {}) {
|
|
if (!Array.isArray(actions)) throw new RuntimeFault('ERR_SCHEMA_VALIDATION', 'Actions must be an array.');
|
|
const stream = this.engine.rng.stream('scenario', `actions:${++this.invocationOrdinal}`);
|
|
const results = [];
|
|
for (let index = 0; index < actions.length; index += 1) {
|
|
const action = actions[index];
|
|
try {
|
|
results.push(this.executeOne(action, stream, context, `$.actions[${index}]`));
|
|
} catch (error) {
|
|
const fault = error instanceof RuntimeFault ? error : new RuntimeFault('ERR_ACTION_FAILURE', error.message);
|
|
this.diagnostics?.error(fault.code, fault.message, { exhibitId: this.engine.document.meta.id, section: 'actions', objectId: action?.id ?? null, property: fault.path });
|
|
results.push({ status: 'failed', error: fault });
|
|
if (action?.critical !== false) throw fault;
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
executeOne(action, stream, context, path) {
|
|
if (!isRecord(action) || typeof action.type !== 'string') throw new RuntimeFault('ERR_SCHEMA_VALIDATION', 'Action requires a type.', path);
|
|
if (action.when !== undefined && !this.engine.evaluateCondition(action.when, stream, `${path}.when`)) return { status: 'skipped', reason: 'condition' };
|
|
if (action.chance !== undefined) {
|
|
if (!Number.isFinite(action.chance) || action.chance < 0 || action.chance > 1) throw new RuntimeFault('ERR_OUT_OF_BOUNDS', 'Action chance must be from 0 through 1.', `${path}.chance`);
|
|
if (stream.nextFloat() >= action.chance) return { status: 'skipped', reason: 'chance' };
|
|
}
|
|
|
|
if (action.type === 'set') {
|
|
const value = this.engine.evaluateValue(action.value, stream, `${path}.value`);
|
|
this.engine.setState(action.target, value, action.transition);
|
|
return { status: 'executed', type: 'set', target: action.target, value };
|
|
}
|
|
if (action.type === 'override') {
|
|
const value = this.engine.evaluateValue(action.value, stream, `${path}.value`);
|
|
const instanceId = this.engine.overrides.add(action, value, { owner: context.owner, inheritedPriority: context.priority });
|
|
this.engine.resolveAll();
|
|
return { status: 'executed', type: 'override', target: action.target, value, instanceId };
|
|
}
|
|
throw new RuntimeFault('ERR_UNSUPPORTED_TARGET', `Action '${action.type}' belongs to a later subsystem phase.`, `${path}.type`);
|
|
}
|
|
}
|