import { type CleanupControl } from './cleanup-control'; /** * Resource factory with declared dependencies and optional disposal callback. * The callback receives a CleanupControl for initiating owner disposal without * waiting on its own teardown barrier. */ export interface ResourceFactoryWithDeps { dependsOn?: readonly D[]; factory: (context: Context) => T | Promise; onDispose?: (resource: T, context: Context, cleanup: CleanupControl) => void | Promise; } /** @internal */ export declare const RESOURCE_DIRECT: unique symbol; /** * Branded wrapper for storing a value as-is, bypassing factory detection. * The value is carried on the symbol key to avoid structural conflicts * with user resource types that have a `value` property. * Create via the `directValue()` helper. */ export interface ResourceDirectValue { [RESOURCE_DIRECT]: T; } /** * Wrap a value to store it as-is, bypassing factory detection. * Use when the resource itself is a function or class that should not be invoked. * * @example * ```ts * import { directValue } from 'ecspresso'; * world.addResource('handler', directValue(myFunction)); * world.addResource('MyClass', directValue(MyClass)); * ``` */ export declare function directValue(value: T): ResourceDirectValue; /** * When Context is unknown (default), context args are optional. * When Context is a specific type (e.g. ECSpresso<...>), context is required. */ type ContextArgs = unknown extends Context ? [context?: Context] : [context: Context]; export default class ResourceManager = Record, Context = unknown> { private readonly requestOwnerDisposal?; private resources; private resourceFactories; private resourceDependencies; private resourceDisposers; private initializedResourceKeys; /** In-flight factory calls, preventing duplicate initialization and late races. */ private pendingInitializations; /** Resources currently inside an onDispose callback. */ private disposingResourceKeys; /** Shared promise for concurrent disposeResources() calls. */ private disposePromise; /** In-flight per-resource disposal operations, including partial disposal. */ private pendingDisposals; /** Failed partial disposals that completed before a bulk teardown began. */ private completedDisposalErrors; /** Prevent new factories from starting or storing values after world teardown begins. */ private closed; /** Prevent a late factory result from repopulating a manager that was cleared. */ private cleared; private _changeSubscribers; /** Shallow snapshots of observed resources, keyed by resource key */ private _observedSnapshots; constructor(requestOwnerDisposal?: (() => Promise) | undefined); /** * Add a resource to the manager. * * Resolution order: * 1. `{ factory, dependsOn?, onDispose? }` → factory with optional deps/disposal * 2. `{ value }` → direct value wrapper (use to store functions/classes as-is) * 3. `typeof === 'function'` → bare factory (no deps) * 4. Anything else → direct value * * @param label The resource key * @param resource The resource value, a factory function, or a factory with dependencies * @returns The resource manager instance for chaining */ add(label: K, resource: ResourceTypes[K] | ((context: Context) => ResourceTypes[K] | Promise) | ResourceFactoryWithDeps | ResourceDirectValue): this; /** * Try to get a resource from the manager. * Returns the resource value if it exists, or undefined if not found. * Like `get`, initializes factory resources on first access. * @param label The resource key * @param context Context to pass to factory functions (usually the ECSpresso instance) * @returns The resource value, or undefined if not found * @see get — the throwing alternative */ tryGet(label: K, ...args: ContextArgs): ResourceTypes[K] | undefined; /** * Get a resource from the manager * @param label The resource key * @param context Context to pass to factory functions (usually the ECSpresso instance) * @returns The resource value * @throws Error if resource not found * @see tryGet — the non-throwing alternative */ get(label: K, ...args: ContextArgs): ResourceTypes[K]; /** * Check if a resource exists * @param label The resource key * @returns True if the resource exists */ has(label: K): boolean; /** * Remove a resource (without calling onDispose) * @param label The resource key * @returns True if the resource was removed */ remove(label: K): boolean; /** * Get all resource keys * @returns Array of resource keys */ getKeys(): Array; /** * Check if a resource needs to be initialized * @param label The resource key * @returns True if the resource needs initialization */ needsInitialization(label: K): boolean; /** * Get all resource keys that need to be initialized * @returns Array of resource keys that need initialization */ getPendingInitializationKeys(): Array; /** * Initialize a specific resource if it's a factory function * @param label The resource key * @param context Context to pass to factory functions * @returns Promise that resolves when the resource is initialized */ initializeResource(label: K, ...args: ContextArgs): Promise; private initializeResourceValue; /** * Initialize specific resources or all resources that haven't been initialized yet. * Resources are initialized in topological order based on their dependencies. * @param context Context to pass to factory functions (usually the ECSpresso instance) * @param keys Optional array of resource keys to initialize * @returns Promise that resolves when the specified resources are initialized */ initializeResources(...args: [...ContextArgs, ...K[]]): Promise; /** * Get the dependencies of a resource * @param label The resource key * @returns Array of resource keys that this resource depends on */ getDependencies(label: K): readonly (keyof ResourceTypes & string)[]; /** * Dispose a single resource, calling its onDispose callback if it exists * @param label The resource key to dispose * @param context Context to pass to the onDispose callback * @returns True if the resource existed and was disposed, false if it didn't exist * External calls wait for an active bulk teardown. An onDispose callback that * needs to initiate owner teardown must use its CleanupControl. */ disposeResource(label: K, ...args: ContextArgs): Promise; private disposeResourceValue; private disposeResourceInternal; /** * Subscribe to changes for a specific resource key. * * Subscribing marks the resource as "observed." Observed resources are * shallow-diffed at the end of each frame via `flushObserved()`, so in-place * mutations are detected and subscribers notified. * * When the last subscriber unsubscribes, per-frame diffing stops. * * @param key The resource key to watch * @param callback Function called with (newValue, oldValue) when the resource changes * @returns Unsubscribe function */ onResourceChange(key: K, callback: (newValue: ResourceTypes[K], oldValue: ResourceTypes[K]) => void): () => void; /** * Notify subscribers of a resource value change. * Skips notification if the value is unchanged (via Object.is). * @param key The resource key that changed * @param newValue The new resource value * @param oldValue The previous resource value */ notifyChange(key: K, newValue: ResourceTypes[K], oldValue: ResourceTypes[K]): void; /** * Whether a resource has active change subscribers. */ isObserved(key: K): boolean; /** * Diff all observed resources against their snapshots. * Fires subscribers for any resource whose shallow properties changed * since the last snapshot, then updates the snapshot. * Call once per frame after all systems have run. */ flushObserved(): void; /** * Dispose all initialized resources in reverse dependency order. * Resources that depend on others are disposed first. * @param context Context to pass to onDispose callbacks * External concurrent calls share the active teardown, and already-started * partial disposal operations finish before dependencies are removed. */ disposeResources(...args: ContextArgs): Promise; private disposeResourcesInternal; /** @internal Prevent new factory work while the owning world is tearing down. */ close(): void; /** @internal Release uninitialized factories and any remaining manager state. */ clear(): void; } export {};