ecspresso
Version:
A minimal Entity-Component-System library for typescript and javascript.
126 lines (125 loc) • 5.7 kB
TypeScript
/**
* Coroutine Plugin for ECSpresso
*
* ES6 generator-based coroutines for multi-step, frame-spanning scripted sequences.
* A `coroutine` component holds a live generator. A system ticks all generators each
* frame via `.next(dt)`. Helper generators (`waitSeconds`, `waitFrames`, `waitUntil`,
* `waitForEvent`, `parallel`, `race`) compose via `yield*`.
*/
import { type BasePluginOptions } from 'ecspresso';
import type { EventsOfWorld, AnyECSpresso } from 'ecspresso';
/**
* Yields void, returns void, receives deltaTime (number) via `.next(dt)`.
* First `.next(dt)` initializes the generator (runs to first yield, dt discarded per JS spec).
* Subsequent `.next(dt)` resume from yield with dt as the yield expression value.
*/
export type CoroutineGenerator = Generator<void, void, number>;
export interface CoroutineEventData {
entityId: number;
}
export interface CoroutineState {
generator: CoroutineGenerator;
onComplete?: (data: CoroutineEventData) => void;
}
export interface CoroutineComponentTypes {
coroutine: CoroutineState;
}
export interface CoroutinePluginOptions<G extends string = 'coroutines'> extends BasePluginOptions<G> {
}
export interface CoroutineOptions {
onComplete?: (data: CoroutineEventData) => void;
}
/**
* Create a coroutine component for spawning or adding to an entity.
*
* @param generator - The generator function to drive
* @param options - Optional configuration (onComplete event)
* @returns Component object suitable for spreading into spawn()
*/
export declare function createCoroutine(generator: CoroutineGenerator, options?: CoroutineOptions): Pick<CoroutineComponentTypes, 'coroutine'>;
/**
* Wait for a specified number of seconds. Accumulates dt until elapsed >= seconds.
* If seconds <= 0, returns immediately.
*/
export declare function waitSeconds(seconds: number): CoroutineGenerator;
/**
* Wait for a specified number of frames. Yields `frames` times.
* If frames <= 0, returns immediately.
*/
export declare function waitFrames(frames: number): CoroutineGenerator;
/**
* Wait until a predicate returns true. Yields each frame until predicate is satisfied.
* User closes over ecs if needed for state checks.
*/
export declare function waitUntil(predicate: () => boolean): CoroutineGenerator;
/**
* Run multiple coroutines in parallel. Completes when all finish.
* Initializes all sub-generators, ticks all each frame.
* Empty array = immediate return.
*/
export declare function parallel(...coroutines: CoroutineGenerator[]): CoroutineGenerator;
/**
* Run multiple coroutines, completing when the first one finishes.
* Calls `.return()` on remaining generators (triggers finally blocks).
* Empty array = immediate return.
*/
export declare function race(...coroutines: CoroutineGenerator[]): CoroutineGenerator;
/**
* Wait until a matching event fires on the event bus.
* Subscribes via eventBus.subscribe, yields until event received, unsubscribes in finally block.
*
* @param eventBus - Object with subscribe method (typically ecs.eventBus)
* @param eventType - Event type name to listen for
* @param filter - Optional predicate to filter events
*/
export declare function waitForEvent<ET extends Record<string, any>, E extends keyof ET & string>(eventBus: {
subscribe(type: E, cb: (data: ET[E]) => void): () => void;
}, eventType: E, filter?: (data: ET[E]) => boolean): CoroutineGenerator;
/**
* Structural interface for ECS methods used by cancelCoroutine.
*/
export interface CoroutineWorld {
getComponent(entityId: number, componentName: string): unknown | undefined;
commands: {
removeComponent(entityId: number, componentName: string): void;
};
}
/**
* Cancel a running coroutine on an entity. Calls generator.return() (triggers finally blocks)
* and queues component removal.
*
* @returns true if the entity had a coroutine that was cancelled, false otherwise
*/
export declare function cancelCoroutine(ecs: CoroutineWorld, entityId: number): boolean;
/**
* Type-safe coroutine helpers that validate event names against a world's event types.
* Use `createCoroutineHelpers<typeof ecs>()` to get compile-time validation.
*/
export interface CoroutineHelpers<W extends AnyECSpresso> {
createCoroutine: (generator: CoroutineGenerator, options?: {
onComplete?: (data: CoroutineEventData) => void;
}) => Pick<CoroutineComponentTypes, 'coroutine'>;
waitForEvent: <E extends keyof EventsOfWorld<W> & string>(eventBus: {
subscribe(type: E, cb: (data: EventsOfWorld<W>[E]) => void): () => void;
}, eventType: E, filter?: (data: EventsOfWorld<W>[E]) => boolean) => CoroutineGenerator;
}
/**
* Create typed coroutine helpers that validate event names at compile time.
*
* @example
* ```typescript
* const { createCoroutine, waitForEvent } = createCoroutineHelpers<typeof ecs>();
* ecs.spawn({ ...createCoroutine(myGen(), { onComplete: (data) => console.log(data.entityId) }) });
* ```
*/
export declare function createCoroutineHelpers<W extends AnyECSpresso>(_world?: W): CoroutineHelpers<W>;
/**
* Create a coroutine plugin for ECSpresso.
*
* This plugin provides:
* - Coroutine system that ticks all generator-based coroutines each frame
* - Automatic cleanup via dispose callback (triggers generator finally blocks)
* - `onComplete` callback invocation
* - Component removal on completion
*/
export declare function createCoroutinePlugin<G extends string = 'coroutines'>(options?: CoroutinePluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, CoroutineComponentTypes>, import("ecspresso").EmptyConfig, "coroutine-update", G, never, never>;