feat(engine): add action queue state and enqueue APIs

This commit is contained in:
ginnoir
2026-06-11 18:18:17 -05:00
parent 923a7a3dac
commit ea2fb0afab
4 changed files with 127 additions and 4 deletions
+37 -2
View File
@@ -14,6 +14,8 @@ export interface GameState {
activeActionId: string | null;
/** Progress of the active action, in milliseconds. */
actionElapsedMs: number;
/** Action ids waiting to run after the active action finishes. */
actionQueue: string[];
}
export function createGameState(content: Content): GameState {
@@ -21,10 +23,14 @@ export function createGameState(content: Content): GameState {
for (const resource of content.resources) {
resources[resource.id] = resource.startAmount;
}
return { resources, activeActionId: null, actionElapsedMs: 0 };
return { resources, activeActionId: null, actionElapsedMs: 0, actionQueue: [] };
}
/** Begin running an action, resetting its progress. Throws on an unknown id. */
/**
* Begin running an action, resetting its progress. Throws on an unknown id.
*
* @deprecated Prefer `enqueueAction` — it starts immediately when idle and queues otherwise.
*/
export function startAction(state: GameState, content: Content, actionId: string): void {
if (!content.actionsById[actionId]) {
throw new Error(`Unknown action "${actionId}"`);
@@ -33,6 +39,35 @@ export function startAction(state: GameState, content: Content, actionId: string
state.actionElapsedMs = 0;
}
/**
* Start an action immediately when idle, otherwise append it to the queue.
* Throws on an unknown id. Cost/unlock checks are not applied yet.
*/
export function enqueueAction(state: GameState, content: Content, actionId: string): void {
if (!content.actionsById[actionId]) {
throw new Error(`Unknown action "${actionId}"`);
}
if (state.activeActionId === null) {
state.activeActionId = actionId;
state.actionElapsedMs = 0;
return;
}
state.actionQueue.push(actionId);
}
/** Remove a queued action by index. Throws RangeError when out of range. */
export function cancelQueuedAction(state: GameState, index: number): void {
if (index < 0 || index >= state.actionQueue.length) {
throw new RangeError(`Queue index ${index} is out of range`);
}
state.actionQueue.splice(index, 1);
}
/** Clear all queued actions without stopping the active action. */
export function clearQueue(state: GameState): void {
state.actionQueue.length = 0;
}
/**
* Advance the active action by `tickMs`. Each time it reaches its duration it
* grants its yield and repeats, carrying the remainder — so one large tick (the