@simpludify/web 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # @simpludify/web
2
2
 
3
- Experimental v0.1.6 browser SDK for Simpludify, a code-first TypeScript 2D engine. APIs may change between 0.x releases. Includes Game, PixiJS rendering, browser input and Canvas/DOM UI adapters; re-exports the host-independent Core API. Core and PixiJS are installed as runtime dependencies, not bundled into this package.
3
+ Experimental v0.2.0 browser SDK for Simpludify, a code-first TypeScript 2D engine. APIs may change between 0.x releases. Includes Game, PixiJS rendering, browser input and Canvas/DOM UI adapters; re-exports the host-independent Core API. Core and PixiJS are installed as runtime dependencies, not bundled into this package.
4
4
 
5
- Registry installation (check availability with `npm view @simpludify/web@0.1.6 version`):
5
+ Registry installation (check availability with `npm view @simpludify/web@0.2.0 version`):
6
6
 
7
7
  ```sh
8
- npm install @simpludify/web@0.1.6
8
+ npm install @simpludify/web@0.2.0
9
9
  npm install --save-dev vite typescript
10
10
  ```
11
11
 
@@ -0,0 +1,6 @@
1
+ import type { AssetAdapter } from "./assets.js";
2
+ import { type ImageResource, SharedImages } from "./shared-images.js";
3
+ export declare function assetResponse(url: string, signal: AbortSignal, reload: boolean): Promise<Response>;
4
+ export declare function loadImageResource(url: string, signal: AbortSignal, mode?: "explicit" | "reload"): Promise<ImageResource>;
5
+ export declare function createImagePool(load?: typeof loadImageResource): SharedImages;
6
+ export declare function browserAssetAdapter(images: SharedImages, decode: (data: ArrayBuffer) => Promise<AudioBuffer>): AssetAdapter;
@@ -0,0 +1,158 @@
1
+ import { type LifetimeOwner, type Logger } from "@simpludify/core";
2
+ import type { ImageLease } from "./shared-images.js";
3
+ export interface AssetData {
4
+ image: Readonly<{
5
+ width: number;
6
+ height: number;
7
+ }>;
8
+ audio: AudioBuffer | null;
9
+ font: string;
10
+ json: unknown;
11
+ }
12
+ export type AssetKind = keyof AssetData;
13
+ export type AssetStatus = "loading" | "ready" | "fallback" | "failed" | "released";
14
+ export interface AssetRetry {
15
+ /** One retry for a non-network failure. A promise can represent a caller-owned event. */
16
+ readonly once?: "immediate" | {
17
+ readonly delayMs: number;
18
+ } | {
19
+ readonly until: PromiseLike<unknown>;
20
+ };
21
+ /** Additional attempts after network failures, from zero to five. */
22
+ readonly network?: number;
23
+ readonly networkDelayMs?: number;
24
+ }
25
+ export interface AssetLoadOptions {
26
+ readonly retry?: AssetRetry;
27
+ /** Deadline for the load and its retries, including any wait for a signal. */
28
+ readonly timeoutMs?: number;
29
+ }
30
+ export interface AssetRequest<K extends AssetKind = AssetKind> extends AssetLoadOptions {
31
+ readonly kind: K;
32
+ readonly url: string | URL;
33
+ }
34
+ export interface AssetOptions {
35
+ /** Maximum number of idle, successfully loaded Assets per Game. */
36
+ readonly cacheEntries?: number;
37
+ readonly fallback?: Readonly<Partial<Record<Exclude<AssetKind, "json">, string | URL>>>;
38
+ }
39
+ export interface AssetProgress {
40
+ readonly total: number;
41
+ readonly completed: number;
42
+ readonly failed: number;
43
+ readonly fallback: number;
44
+ readonly loading: number;
45
+ readonly ratio: number;
46
+ readonly state: "idle" | "loading" | "complete";
47
+ }
48
+ export interface ImageAssetSource {
49
+ readonly kind: "image";
50
+ readonly url: string;
51
+ }
52
+ export interface AssetResource {
53
+ readonly value: unknown;
54
+ readonly image?: ImageLease;
55
+ release(): void;
56
+ }
57
+ export interface AssetAdapter {
58
+ load(kind: AssetKind, url: string, signal: AbortSignal, reload: boolean): Promise<AssetResource>;
59
+ fallback(kind: AssetKind, url: string | undefined, signal: AbortSignal): Promise<AssetResource | undefined>;
60
+ }
61
+ interface Entry {
62
+ readonly key: string;
63
+ readonly kind: AssetKind;
64
+ readonly url: string;
65
+ readonly id: string;
66
+ readonly listeners: Set<() => void>;
67
+ readonly validators: Set<(asset: AssetResource) => void>;
68
+ readonly users: Set<() => void>;
69
+ readonly first: Promise<void>;
70
+ usable(): void;
71
+ fail(error: unknown): void;
72
+ status: Exclude<AssetStatus, "released">;
73
+ asset: AssetResource | undefined;
74
+ task: Promise<void> | undefined;
75
+ controller: AbortController | undefined;
76
+ error: unknown;
77
+ }
78
+ export declare class AssetHandle<K extends AssetKind> {
79
+ readonly kind: K;
80
+ private readonly entry;
81
+ private readonly store;
82
+ private readonly remove;
83
+ readonly ready: Promise<this>;
84
+ private readonly cancelled;
85
+ private readonly imageSource;
86
+ /** @internal */
87
+ constructor(kind: K, entry: Entry, store: AssetStore, remove: () => void);
88
+ get id(): string;
89
+ get url(): string;
90
+ get status(): AssetStatus;
91
+ get loading(): boolean;
92
+ get value(): AssetData[K] | undefined;
93
+ get error(): unknown;
94
+ get progress(): AssetProgress;
95
+ /** Stable Renderable source. SpriteSheet accepts its URL. */
96
+ get source(): K extends "image" ? ImageAssetSource : undefined;
97
+ onChange(listener: (asset: this) => void): () => void;
98
+ /** @internal A playback or Game font owns a separate reference. */
99
+ retain(store: AssetStore): AssetHandle<K>;
100
+ reload(options?: AssetLoadOptions): Promise<this>;
101
+ release(): void;
102
+ }
103
+ export declare class AssetScope implements LifetimeOwner {
104
+ private readonly store;
105
+ private readonly global;
106
+ private readonly lifetime;
107
+ private readonly handles;
108
+ private readonly listeners;
109
+ private previous;
110
+ /** @internal */
111
+ constructor(store: AssetStore, owner?: LifetimeOwner, global?: boolean);
112
+ get disposed(): boolean;
113
+ onDispose(callback: () => void): () => void;
114
+ scope(owner?: LifetimeOwner): AssetScope;
115
+ load<K extends AssetKind>(request: AssetRequest<K>): AssetHandle<K>;
116
+ get progress(): AssetProgress;
117
+ onProgress(listener: (value: AssetProgress) => void): () => void;
118
+ private notify;
119
+ dispose(): void;
120
+ }
121
+ /** @internal One resource directory per Game. The adapter owns decoded resources. */
122
+ export declare class AssetStore {
123
+ private readonly adapter;
124
+ private readonly logger;
125
+ private readonly base;
126
+ private readonly prefix;
127
+ private readonly entries;
128
+ private readonly images;
129
+ private readonly idle;
130
+ private readonly listeners;
131
+ private readonly scopes;
132
+ private notifying;
133
+ private notificationScheduled;
134
+ private readonly capacity;
135
+ private readonly customFallback;
136
+ private sequence;
137
+ private dead;
138
+ constructor(adapter: AssetAdapter, logger: Logger, base: string, options?: AssetOptions);
139
+ register(scope: AssetScope): () => void;
140
+ observe(listener: () => void): () => void;
141
+ deliver(listener: () => unknown): void;
142
+ changed(): void;
143
+ get progress(): AssetProgress;
144
+ entry<K extends AssetKind>(request: AssetRequest<K>): Entry;
145
+ retain(entry: Entry, dispose: () => void): () => void;
146
+ private release;
147
+ private evict;
148
+ ready(entry: Entry, signal: AbortSignal): Promise<void>;
149
+ start(entry: Entry, options: AssetLoadOptions, reload: boolean): Promise<void>;
150
+ private attempt;
151
+ private commit;
152
+ private fallback;
153
+ private load;
154
+ image(id: string, signal: AbortSignal): Promise<ImageLease>;
155
+ dispose(): void;
156
+ }
157
+ export declare function isAssetImage(url: string): boolean;
158
+ export {};
@@ -0,0 +1,97 @@
1
+ import { type LifetimeOwner, type Logger } from "@simpludify/core";
2
+ import type { AssetHandle, AssetStore } from "./assets.js";
3
+ export interface AudioGroupOptions {
4
+ readonly parent?: string;
5
+ readonly volume?: number;
6
+ readonly maxVoices?: number;
7
+ }
8
+ export interface AudioPlayOptions {
9
+ readonly group?: string;
10
+ readonly owner?: LifetimeOwner;
11
+ readonly volume?: number;
12
+ readonly loop?: boolean;
13
+ /** Optional cap for simultaneous instances of this asset. */
14
+ readonly maxVoices?: number;
15
+ }
16
+ export type AudioEndReason = "ended" | "stopped" | "replaced" | "failed";
17
+ export type AudioPlaybackState = "pending" | "playing" | "paused" | AudioEndReason;
18
+ export declare class AudioGroup {
19
+ readonly name: string;
20
+ readonly parent: AudioGroup | undefined;
21
+ private gain;
22
+ private level;
23
+ readonly maxVoices: number | undefined;
24
+ /** @internal */
25
+ constructor(name: string, parent: AudioGroup | undefined, options?: AudioGroupOptions);
26
+ get volume(): number;
27
+ set volume(value: number);
28
+ /** @internal */
29
+ node(context: AudioContext): GainNode;
30
+ /** @internal */
31
+ disconnect(): void;
32
+ }
33
+ export declare class AudioPlayback {
34
+ private readonly audio;
35
+ private readonly use;
36
+ readonly group: AudioGroup;
37
+ readonly finished: Promise<AudioEndReason>;
38
+ readonly assetId: string;
39
+ private resolve;
40
+ private phase;
41
+ private done;
42
+ private source;
43
+ private gain;
44
+ private buffer;
45
+ private position;
46
+ private startedAt;
47
+ private level;
48
+ private looping;
49
+ private offOwner;
50
+ /** @internal */
51
+ constructor(audio: GameAudio, use: AssetHandle<"audio">, group: AudioGroup, options: AudioPlayOptions);
52
+ get state(): AudioPlaybackState;
53
+ get volume(): number;
54
+ set volume(value: number);
55
+ get loop(): boolean;
56
+ set loop(value: boolean);
57
+ pause(): void;
58
+ resume(): void;
59
+ stop(): void;
60
+ /** @internal */
61
+ replace(): void;
62
+ /** @internal */
63
+ startIfReady(): void;
64
+ private clearSource;
65
+ private fail;
66
+ private finish;
67
+ }
68
+ /** One Web Audio context per Game, created only when audio is requested. */
69
+ export declare class GameAudio {
70
+ private readonly assets;
71
+ private readonly logger;
72
+ private readonly shutdown;
73
+ private readonly groups;
74
+ private readonly voices;
75
+ private context;
76
+ readonly master: AudioGroup;
77
+ /** @internal */
78
+ constructor(host: HTMLElement, assets: AssetStore, logger: Logger);
79
+ get state(): AudioContextState | "idle";
80
+ group(name: string, options?: AudioGroupOptions): AudioGroup;
81
+ play(asset: AssetHandle<"audio">, options?: AudioPlayOptions): AudioPlayback;
82
+ private enforceLimit;
83
+ private assertAlive;
84
+ /** @internal */
85
+ device(): AudioContext;
86
+ /** @internal */
87
+ decode(bytes: ArrayBuffer): Promise<AudioBuffer>;
88
+ /** Call from a trusted user gesture to start pending playback. */
89
+ unlock(): Promise<boolean>;
90
+ private readonly drain;
91
+ /** @internal */
92
+ remove(voice: AudioPlayback): void;
93
+ /** @internal */
94
+ report(cause: unknown, message: string): void;
95
+ /** @internal */
96
+ dispose(): void;
97
+ }
@@ -16,6 +16,7 @@ export declare class CanvasVisual implements CanvasPresentation {
16
16
  private readonly entries;
17
17
  private readonly metrics;
18
18
  private dirty;
19
+ private defaultFont;
19
20
  private dead;
20
21
  private active;
21
22
  private cacheResolution;
@@ -42,6 +43,7 @@ export declare class CanvasVisual implements CanvasPresentation {
42
43
  private native;
43
44
  private button;
44
45
  private updateButton;
46
+ private fontFamily;
45
47
  update(width: number, height: number): void;
46
48
  private releaseEntry;
47
49
  dispose(): void;
package/dist/game.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import type { CanvasNode } from "@simpludify/core";
2
- import { type ActionBindings, ActionController, type ActionSet, type Actor, Aspect, Camera, CanvasUI, type CanvasUIOptions, Engine, type FixedStepOptions, type FrameDriver, type InputCapture, type InputCaptureOptions, type JsonValue, type LogicalResolution, type RendererPort, type ReplayControllers, ReplayPlayer, Scene, ScenePointer, type ScreenPoint, type TweenScheduler, type Viewport, type ViewTransform, VirtualInput, type WorldPoint } from "@simpludify/core";
2
+ import { type ActionBindings, ActionController, type ActionSet, type Actor, Aspect, Camera, CanvasUI, type CanvasUIOptions, Engine, type FixedStepOptions, type FrameDriver, type InputCapture, type InputCaptureOptions, type JsonValue, Logger, type LoggerOptions, type LogicalResolution, type RendererPort, type ReplayControllers, ReplayPlayer, Scene, ScenePointer, type SceneRetention, type ScreenPoint, type TweenScheduler, type Viewport, type ViewTransform, VirtualInput, type WorldPoint } from "@simpludify/core";
3
+ import { type AssetHandle, type AssetOptions, AssetScope } from "./assets.js";
4
+ import { GameAudio } from "./audio.js";
3
5
  import { GamepadInput } from "./gamepads.js";
4
6
  import { Theme, type ThemeTokens, type UINode } from "./ui.js";
5
7
  export interface SceneContext {
6
8
  readonly scene: Scene;
9
+ readonly assets: AssetScope;
7
10
  readonly signal: AbortSignal;
8
11
  readonly tweens: TweenScheduler;
9
12
  readonly presentationTweens: TweenScheduler;
@@ -19,6 +22,13 @@ export interface SceneContext {
19
22
  onDispose(callback: () => void): () => void;
20
23
  }
21
24
  export type SceneSetup = (context: SceneContext) => void | Promise<void>;
25
+ export interface SceneOptions {
26
+ readonly retention?: SceneRetention;
27
+ }
28
+ export interface GameLoggingOptions extends Omit<LoggerOptions, "sink"> {
29
+ /** Controls Console output only. Diagnostic subscribers remain active. */
30
+ readonly console?: boolean;
31
+ }
22
32
  export interface GameOptions {
23
33
  parent?: HTMLElement;
24
34
  resolution?: LogicalResolution;
@@ -33,6 +43,10 @@ export interface GameOptions {
33
43
  fixedStep?: FixedStepOptions;
34
44
  renderer?: (host: HTMLElement) => RendererPort;
35
45
  frames?: FrameDriver;
46
+ logging?: GameLoggingOptions;
47
+ assets?: AssetOptions;
48
+ sceneRetention?: SceneRetention;
49
+ /** @deprecated Subscribe to Game.logger for structured diagnostics. */
36
50
  onError?: (error: unknown) => void;
37
51
  }
38
52
  export declare class Game<SceneId extends string = string> {
@@ -40,6 +54,13 @@ export declare class Game<SceneId extends string = string> {
40
54
  readonly element: HTMLDivElement;
41
55
  readonly camera: Camera;
42
56
  readonly gamepads: GamepadInput;
57
+ readonly logger: Logger;
58
+ readonly assets: AssetScope;
59
+ readonly audio: GameAudio;
60
+ private readonly defaultFont;
61
+ private fontUse;
62
+ private readonly imagePool;
63
+ private readonly assetStore;
43
64
  private readonly renderer;
44
65
  private readonly keyboard;
45
66
  private readonly captures;
@@ -89,7 +110,11 @@ export declare class Game<SceneId extends string = string> {
89
110
  private height;
90
111
  private ratio;
91
112
  private readonly scenes;
92
- private readonly pendingScenes;
113
+ private readonly retainedScenes;
114
+ private readonly sceneRetention;
115
+ private activeRetention;
116
+ private detachPresentation;
117
+ private pendingScene;
93
118
  private readonly defaultUI;
94
119
  private ui;
95
120
  private prepared;
@@ -105,6 +130,8 @@ export declare class Game<SceneId extends string = string> {
105
130
  private readonly shutdown;
106
131
  private readonly errorBox;
107
132
  constructor(options?: GameOptions);
133
+ /** Choose the Game default font for DOM and Canvas UI. */
134
+ setFont(asset?: AssetHandle<"font">): void;
108
135
  private assertAlive;
109
136
  get tweens(): TweenScheduler;
110
137
  get presentationTweens(): TweenScheduler;
@@ -124,11 +151,12 @@ export declare class Game<SceneId extends string = string> {
124
151
  remove(actor: Actor | string): boolean;
125
152
  canvasUI(root: CanvasNode, options?: CanvasUIOptions): CanvasUI;
126
153
  uiOverlay(...nodes: UINode[]): void;
127
- scene(id: SceneId, setup: SceneSetup): this;
154
+ scene(id: SceneId, setup: SceneSetup, options?: SceneOptions): this;
128
155
  private wait;
129
156
  run(initialScene?: SceneId): Promise<void>;
130
- goTo(id: SceneId): Promise<void>;
157
+ changeScene(id: SceneId): Promise<void>;
131
158
  private navigate;
159
+ private reportPreparation;
132
160
  private present;
133
161
  get viewport(): Viewport;
134
162
  get view(): ViewTransform;
@@ -24,7 +24,7 @@ export declare class GeometryTilemapVisual {
24
24
  constructor(map: GeometryTilemap, released: () => void);
25
25
  private remove;
26
26
  private cell;
27
- render(view: ViewTransform): void;
27
+ render(view: ViewTransform, enabled?: boolean): void;
28
28
  private bulk;
29
29
  dispose(): void;
30
30
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import "./style.css";
2
2
  export * from "@simpludify/core";
3
+ export type { AssetData, AssetHandle, AssetKind, AssetLoadOptions, AssetOptions, AssetProgress, AssetRequest, AssetRetry, AssetScope, AssetStatus, ImageAssetSource, } from "./assets.js";
4
+ export type { AudioEndReason, AudioGroup, AudioGroupOptions, AudioPlayback, AudioPlaybackState, AudioPlayOptions, GameAudio, } from "./audio.js";
3
5
  export { browserAbortSignal, CanvasButton } from "./canvas-node.js";
4
6
  export type { CanvasPresentation } from "./canvas-visual.js";
5
7
  export * from "./game.js";