/** * Generic timed work queue — a pure-data model for discrete jobs that reserve * inputs, advance over game time, can be paused/cancelled, and emit an output on * completion. It backs unit training, crafting jobs, construction, research, * respawns, downloads, and fabrication without any of them re-implementing the * reservation → progress → completion → output-routing loop. * * The state ({@link WorkQueueState}) is plain serializable data; all policy — * duration, ordering, capacity, concurrency, input reservation, refund, and * output mapping — lives in {@link WorkQueueConfig}, which is supplied by the * caller and is never serialized. Side effects (charging a wallet, spawning an * entity) are the caller's: reservations and refunds are returned as data, and * completion emits a typed event carrying a pure output payload for an external * completion adapter to route. */ export type JobId = string; /** Lifecycle phase of a single queued job. Terminal jobs are removed from state. */ export type JobStatus = "queued" | "active" | "paused"; /** One unit of timed work. Plain serializable data (given serializable `TSpec`/`TReserve`). */ export interface Job { /** Stable id, unique within the queue for its lifetime. */ readonly id: JobId; /** Caller-owned description of the work (e.g. `{ unitId }`). */ readonly spec: TSpec; /** Reserved inputs computed at enqueue time (e.g. resource cost, population). */ readonly reservation: TReserve; readonly status: JobStatus; /** Seconds of work the job requires. */ readonly duration: number; /** Seconds of work completed so far (0…duration). */ readonly elapsed: number; /** Higher runs sooner under priority ordering; FIFO within equal priority. */ readonly priority: number; /** Monotonic enqueue index — the FIFO tiebreak. */ readonly seq: number; } /** Serializable queue state. Holds only non-terminal jobs, so it stays bounded. */ export interface WorkQueueState { readonly jobs: readonly Job[]; readonly nextId: number; readonly seq: number; } /** Comparator over jobs; negative means the first job runs sooner. */ export type JobOrdering = (a: Job, b: Job) => number; /** Result of pre-enqueue validation (population caps, prerequisites, affordability). */ export interface JobValidation { readonly ok: boolean; readonly reason?: string; } /** Optional per-enqueue overrides. */ export interface EnqueueOptions { /** Higher runs sooner under priority ordering. Defaults to 0. */ readonly priority?: number; /** Explicit id; defaults to a queue-assigned `job-`. */ readonly id?: string; } /** * Injected policy for a queue. Never serialized — pass the same config to every * {@link enqueue}/{@link tick}/{@link cancelJob} call for a given queue. */ export interface WorkQueueConfig { /** Seconds of work a spec requires. Negative values clamp to 0 (instant). */ duration: (spec: TSpec) => number; /** Max concurrent active jobs (multi-server queue). Defaults to 1. */ concurrency?: number; /** Max non-terminal jobs; further enqueues are rejected with `"capacity"`. */ capacity?: number; /** Selection order for queued jobs. Defaults to {@link priorityOrdering}. */ ordering?: JobOrdering; /** Compute the reservation stored on the job (charge-now is the caller's job). */ reserve?: (spec: TSpec) => TReserve; /** Validate before enqueue; a non-ok result rejects with its reason. */ validate?: (spec: TSpec, state: WorkQueueState) => JobValidation; /** Map a completed job to its output payload for a completion adapter to route. */ output?: (job: Job) => TOutput; /** Compute a refund on cancellation from the reservation and progress (0…1). */ refund?: (job: Job, progress: number) => TReserve | null; } /** Typed lifecycle event emitted from {@link tick}. */ export type WorkQueueEvent = { readonly type: "started"; readonly job: Job; } | { readonly type: "completed"; readonly job: Job; readonly output: TOutput; }; /** Outcome of {@link tick}: advanced state plus the events produced. */ export interface TickResult { readonly state: WorkQueueState; readonly events: readonly WorkQueueEvent[]; } /** Outcome of {@link enqueue}. On rejection the state is returned unchanged. */ export type EnqueueResult = { readonly ok: true; readonly state: WorkQueueState; readonly job: Job; } | { readonly ok: false; readonly state: WorkQueueState; readonly reason: string; }; /** Outcome of {@link cancelJob}, carrying the removed job and its computed refund. */ export type CancelResult = { readonly ok: true; readonly state: WorkQueueState; readonly job: Job; readonly refund: TReserve | null; } | { readonly ok: false; readonly state: WorkQueueState; readonly reason: string; }; /** * Pure FIFO ordering by enqueue sequence. * * @capability work-order-fifo order queued jobs by enqueue sequence */ export declare function fifoOrdering(a: Job, b: Job): number; /** * Priority-first ordering (higher priority sooner), FIFO within equal priority. * * @capability work-order-priority order queued jobs by priority, then FIFO within equal priority */ export declare function priorityOrdering(a: Job, b: Job): number; /** * Create an empty timed work queue. * * @capability work-queue a serializable timed job queue with reservation, cancellation, completion, and output routing */ export declare function createWorkQueue(): WorkQueueState; /** * Non-terminal job count (queued + active + paused). * * @capability work-queue-size count non-terminal jobs in a work queue */ export declare function queueSize(state: WorkQueueState): number; /** * Look up a job by id, or `null` if absent/terminal. * * @capability work-job-by-id look up a queued job by id */ export declare function jobById(state: WorkQueueState, id: JobId): Job | null; /** * Jobs waiting for a slot, in selection order. * * @capability work-queued-jobs list jobs waiting for a slot, in selection order */ export declare function queuedJobs(state: WorkQueueState, ordering?: JobOrdering): Job[]; /** * Jobs currently progressing. * * @capability work-active-jobs list jobs currently progressing in a work queue */ export declare function activeJobs(state: WorkQueueState): Job[]; /** * Fractional progress of a job (0…1); a zero-duration job reads as complete. * * @capability work-job-progress compute a job's fractional progress */ export declare function jobProgress(job: Job): number; /** * Enqueue a new job. Validates capacity and the injected `validate` policy, then * computes and stores the reservation. Charging the reservation (now or later) is * the caller's responsibility using {@link EnqueueResult.job}'s `reservation`. * * @capability enqueue-work reserve inputs and add a timed job, honoring capacity and validation policy */ export declare function enqueue(state: WorkQueueState, config: WorkQueueConfig, spec: TSpec, options?: EnqueueOptions): EnqueueResult; /** * Cancel a job and compute its refund. Removes the job from the queue and returns * a refund payload (full reservation by default, or `config.refund` applied to the * job's progress for partial/no refund). Applying the refund is the caller's job. * * @capability cancel-work remove a job and compute its refund from reservation and progress */ export declare function cancelJob(state: WorkQueueState, config: WorkQueueConfig, id: JobId): CancelResult; /** * Pause a queued or active job in place; a paused job keeps its progress and yields its slot. * * @capability pause-work pause a queued or active job in place, retaining its progress */ export declare function pauseJob(state: WorkQueueState, id: JobId): WorkQueueState; /** * Resume a paused job; it re-enters the queue and competes for a slot by ordering. * * @capability resume-work resume a paused job back into the queue's slot competition */ export declare function resumeJob(state: WorkQueueState, id: JobId): WorkQueueState; /** * Advance the queue by `dt` seconds. Promotes queued jobs into up to `concurrency` * active slots, advances active jobs, and completes those that reach their * duration — emitting `started`/`completed` events in deterministic order. A large * `dt` catches up across many jobs in one call: when a job completes with leftover * time, the freed slot promotes the next queued job and applies the remainder, so a * reconnect or save/load gap resolves in a single bounded pass. Completed jobs are * removed from the returned state. * * @capability tick-work advance jobs over time, completing work and emitting typed lifecycle events */ export declare function tick(state: WorkQueueState, config: WorkQueueConfig, dt: number): TickResult;