feat(runtime): implement phases 1 and 2
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
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`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user