UNPKG

narraleaf-react

Version:

A React visual novel player framework

122 lines (121 loc) 6.9 kB
import { Awaitable } from "../../../util/data"; import { LiveGame } from "../common/game"; import { CalledActionResult, StackModelWaiting } from "../gameTypes"; import { LogicAction } from "./logicAction"; export declare enum StackModelItemType { Action = "action", Link = "link" } export type StackModelRawData = ({ type: StackModelItemType.Action; actionType: string | null; action: string | null; } | { type: StackModelItemType.Link; actionType: string | null; action: string | null; stacks: StackModelRawData[]; stackWaitType: StackModelWaiting["type"] | null; })[]; /** * Nested Stack Model is a new concept designed to control serialization/deserialization of complex nested operations * * Core concepts for saving state: * 1. Do not save operations that cannot be immediately resolved, such as Awaitables * 2. If an action returns an Awaitable, to prevent re-execution of the previous operation after deserialization: * - Store the operation in waitingAction and add it to the tail stack during serialization * - When restoring data, retrying the tail stack operation will retry this operation * - Awaitables should not be saved due to their scope and complex behavior, instead save their parent operation * 3. If an action returns a regular child (synchronous operation), add it to the tail stack * - The child will continue on the next stack operation * 4. If an action returns multiple children, add them sequentially to the tail stack * - These children are treated as having a call relationship, e.g. in [a,b], a waits for b to complete before continuing * - This requires all elements except the stack top to be fully synchronous operations * 5. If an action returns a StackAction (not yet implemented), wait according to the StackAction definition * - This operation is considered semi-synchronous since it contains child information * - Serialization mechanism: treat as synchronous operation, including async info and stack contents * - When restored, operation remains at stack top and continues waiting for stack operations to complete * - This ensures stack operations are not abnormally re-executed or skipped after deserialization * * Example scenarios: * 1. Action returns Awaitable: * - Async operation: add Awaitable to tail stack, set sync operation as waitingAction * - During save: exclude Awaitable, add waitingAction to stack for retry on deserialize * - During runtime: wait for resolution, pop self and add return value to stack * 2. Action returns direct child: * - Sync operation: add operation to tail stack * - During save: add operation to stack * - During runtime: pop self and add child to stack * 3. Action returns multiple children: * - Sync/async nature determined by last child * - Push all children to stack in order, last child on top * - Save behavior follows above rules * 4. Action returns StackAction: * - Semi-sync operation treated as sync, includes async info and stack contents * - During save: includes direct children, wait info (type e.g. any, all) and stack * - Runtime with non-empty stack: continue waiting for stack operations * - Runtime with empty stack: resolve operation, pop self and add direct children * 5. Action returns direct child but async executes StackModel: * - Serialize StackModel and execute directly on deserialize */ export declare class StackModel { private liveGame; __tag: string | undefined; static isStackModel(action: CalledActionResult | Awaitable<CalledActionResult> | StackModel): action is StackModel; static createStackModel(liveGame: LiveGame, data: StackModelRawData, actionMap: Map<string, LogicAction.Actions>): StackModel; static isCalledActionResult(action: CalledActionResult | Awaitable<CalledActionResult> | StackModel | undefined | null): action is CalledActionResult; static fromAction(action: LogicAction.Actions): CalledActionResult; static executeStackModelGroup(type: StackModelWaiting["type"], stackModels: StackModel[]): Awaitable<void>; static isStackModelsAwaiting(type: StackModelWaiting["type"], stackModels: StackModel[]): boolean; private stack; private waitingAction; constructor(liveGame: LiveGame, tag?: string | undefined); /** * Executes the next operation in the stack * * Main responsibilities: * 1. Check and handle waiting states at the top of the stack * 2. Execute current operation and handle its results * 3. Manage asynchronous operations and nested stack models * * Execution flow: * 1. If stack is empty, return null * 2. Check top element: * - If it's an unsettled Awaitable, return the Awaitable * - If it's a waiting operation (with nested stack models), check nested stack status * 3. Pop and execute current operation: * - If it's an Awaitable, wait for completion and handle result * - If it's a regular operation, execute and handle return value * * @returns One of the following: * - CalledActionResult: Execution result (returned a synchronous operation) * - Awaitable<CalledActionResult>: Asynchronous operation * - null: No more operations if the stack is empty, or the top element is exited */ rollNext(): CalledActionResult | Awaitable<CalledActionResult> | null; execute(): Awaitable<void>; abortStackTop(): CalledActionResult | null; getTopSync(): CalledActionResult | null; executeActions(result: CalledActionResult): CalledActionResult | Awaitable<CalledActionResult> | null; isWaiting(): boolean; /** * Serialize current StackModel into a plain JSON-serialisable structure. * * @param frozen - When true (default), the snapshot also contains the * action currently executing at the top of the stack * (waitingAction). This is required by save/load so that * reloading a save resumes exactly at the current dialog or * async node, keeping the runtime state self-consistent. * When false, waitingAction is excluded and the snapshot * reflects the state *before* the current action started. * The undo/history system will then re-insert the action * manually (see LiveGame.undo) to avoid having two copies * of the same action after deserialisation. * @returns Snapshot that can be passed to {@link StackModel.deserialize}. */ serialize(frozen?: boolean): StackModelRawData; reset(): void; deserialize(data: StackModelRawData, actionMap: Map<string, LogicAction.Actions>): this; isEmpty(): boolean; push(...items: (CalledActionResult | Awaitable<CalledActionResult>)[]): this; }