Files
XZBT/src/runtime/actions.js
T

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`);
}
}