@aethervtt/shard-runtime 0.0.1 → 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/dist/app.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type ComponentDef, type ResourceDef, type ScheduleLabel, type SystemConfig, type SystemDef, type SystemDescription, type SystemSetConfig, World } from '@aethervtt/shard-core';
1
+ import { type ComponentDef, type ProfilerSettingsData, type ResourceDef, type ScheduleLabel, type SystemConfig, type SystemDef, type SystemDescription, type SystemSetConfig, World } from '@aethervtt/shard-core';
2
2
  import type { Plugin } from './plugin.js';
3
3
  import type { Runner } from './runners.js';
4
4
  import { type StateDef } from './state.js';
@@ -11,6 +11,24 @@ export interface AppOptions {
11
11
  now?: () => number;
12
12
  /** Seed for the `GlobalRng` resource. Default 0. */
13
13
  seed?: number;
14
+ /**
15
+ * A development build: missing assets show loudly (checkers), and dev-only checks and issues
16
+ * run (shader cache misses, color-space mismatches, GL error checks). Read it from `DevMode`.
17
+ * Default false.
18
+ */
19
+ dev?: boolean;
20
+ /**
21
+ * The profiler (0074): `enabled: false` turns spans off for builds that want every microsecond;
22
+ * `window` is how many runs aggregates cover. Also `ProfilerSettings` at runtime. Default on, 120.
23
+ */
24
+ profiler?: Partial<ProfilerSettingsData>;
25
+ /**
26
+ * The budget scenario the app runs as (0075, `bench/perf/budgets.json`): features that adapt
27
+ * (0045's foliage) default their target to their slice of its frame, and the `perf` overlay shows
28
+ * its slices. Default none: nothing adapts. Also `App.perfScenario` and the `PerfScenario`
29
+ * resource at runtime.
30
+ */
31
+ perfScenario?: string | null;
14
32
  }
15
33
  export interface AppDescription {
16
34
  plugins: {
@@ -39,6 +57,39 @@ export interface AppMethod {
39
57
  world: World;
40
58
  }, params: Record<string, unknown>): unknown;
41
59
  }
60
+ /**
61
+ * Code the app runs happens inside its scopes: plugin builds and ready hooks, frames, pumps, and
62
+ * disposal. The render plugin uses one to count GPU objects against the app (0052).
63
+ */
64
+ export interface AppScope {
65
+ enter(): void;
66
+ exit(): void;
67
+ }
68
+ /** What a loop runner gives the app while it drives it (0052). */
69
+ export interface FrameDriver {
70
+ /** Schedules a frame if none is coming (on-demand); a no-op for runners that run every frame. */
71
+ requestFrame(): void;
72
+ /** Stops the loop for good. */
73
+ stop(): void;
74
+ }
75
+ /**
76
+ * Waits until what the last frame drew is on screen (0062), resolving with when that was on the
77
+ * app's clock if it knows better than "now". The render plugin sets one: the frame's GPU work done,
78
+ * then the next animation frame, whose timestamp it resolves with. Without one, a frame counts as
79
+ * presented when it ends.
80
+ */
81
+ export type FramePresenter = () => Promise<number | undefined>;
82
+ /** When the app started and became usable, in ms on its clock (`AppOptions.now`) (0062). */
83
+ export interface StartupTimings {
84
+ /** `init()` was called. NaN before. */
85
+ initStart: number;
86
+ /** `init()` resolved. NaN before. */
87
+ initEnd: number;
88
+ /** The host called `markUsable()`. NaN before. */
89
+ usableMarked: number;
90
+ /** The first frame after `markUsable()` was presented. NaN before. */
91
+ usable: number;
92
+ }
42
93
  /**
43
94
  * Owns the world, plugins, and schedules, and runs frames. Hosts drive it with a runner
44
95
  * (animation frame, headless, or manual `update` calls).
@@ -50,6 +101,11 @@ export declare class App {
50
101
  private readonly now;
51
102
  private readonly plugins;
52
103
  private readonly pending;
104
+ /** Plugins in the order they were built; disposal runs it backwards. */
105
+ private readonly built;
106
+ private readonly scopes;
107
+ private driver;
108
+ private disposing;
53
109
  private readonly schedules;
54
110
  private readonly systemNames;
55
111
  private readonly states;
@@ -59,6 +115,12 @@ export declare class App {
59
115
  private startupDone;
60
116
  private accumulator;
61
117
  private readonly frameListeners;
118
+ private presenter;
119
+ /** Traces stamped since the last frame started; the next frame carries them. */
120
+ private readonly traces;
121
+ private readonly traceListeners;
122
+ private readonly usableWaiters;
123
+ private readonly startup_;
62
124
  private readonly appMethods;
63
125
  /** The plugin whose `build` is running, so registrations can be attributed to it. */
64
126
  private building;
@@ -68,6 +130,12 @@ export declare class App {
68
130
  addPlugin(...plugins: Plugin[]): this;
69
131
  /** Adds protocol methods (see `AppMethod`). A second method with a taken name throws. */
70
132
  addMethod(...methods: AppMethod[]): this;
133
+ /**
134
+ * The budget scenario the app declares (0075): features with a budget resource adapt to their
135
+ * slice of its frame, and the `perf` overlay shows its slices. null (the default): none.
136
+ */
137
+ get perfScenario(): string | null;
138
+ set perfScenario(name: string | null);
71
139
  /** Methods plugins added, in the order they were added. */
72
140
  get methods(): readonly AppMethod[];
73
141
  addSystems(schedule: ScheduleLabel, ...systems: SystemInput[]): this;
@@ -95,8 +163,70 @@ export declare class App {
95
163
  init(): Promise<void>;
96
164
  /** Initializes, then hands control to the runner (manual if none was set). */
97
165
  run(): Promise<void>;
166
+ /**
167
+ * Stops the runner, waits for every plugin's work in flight (`beforeDispose`), then disposes
168
+ * plugins in reverse build order, so each releases what it created: GPU objects, surfaces,
169
+ * listeners, workers (0052). Idempotent; afterwards any other
170
+ * call throws `runtime/disposed`. A plugin whose dispose throws is logged and the rest still run.
171
+ */
172
+ dispose(): Promise<void>;
173
+ /** True once `dispose()` was called. */
174
+ get disposed(): boolean;
175
+ /**
176
+ * Adds a scope around everything the app runs (see `AppScope`). Returns a function that removes
177
+ * it; don't call that from inside the app's own code (a frame, a plugin's dispose): disposal
178
+ * drops every scope once plugins are done. Scopes enter in the order added and exit in reverse.
179
+ */
180
+ addScope(scope: AppScope): () => void;
181
+ /**
182
+ * Called by a loop runner as it starts. `requestFrame` and `dispose` reach the loop through it.
183
+ */
184
+ attachDriver(driver: FrameDriver): void;
185
+ /**
186
+ * Asks for a frame: an on-demand runner schedules one if none is coming (0052). Host code that
187
+ * changes what's on screen without writing the world (a canvas style, a DOM overlay) calls it.
188
+ */
189
+ requestFrame(): void;
190
+ /** Sets how a frame waits to be on screen (see `FramePresenter`). The render plugin calls it. */
191
+ setPresenter(presenter: FramePresenter | undefined): void;
192
+ /** When init started and ended and when the app became usable, in ms on the app's clock. */
193
+ get startup(): Readonly<StartupTimings>;
194
+ /**
195
+ * The host says the app is usable (for a VTT, once the scene is interactive). The first complete
196
+ * frame presented after this is the first usable frame (`startup.usable`, `whenUsable()`). Later
197
+ * calls do nothing.
198
+ */
199
+ markUsable(): void;
200
+ /** Resolves with `startup.usable` once the first usable frame is presented. */
201
+ whenUsable(): Promise<number>;
202
+ /**
203
+ * Stamps a host write (a patch, a token move) for latency: resolves with the ms from now until the
204
+ * frame that carries it is presented. Call it right after the write. The next frame to start
205
+ * carries every trace stamped before it; a frame that ends still loading (`LOADING_DEMAND`: a
206
+ * pipeline compiling, a mesh not uploaded) hands them on, so the latency runs until the write is
207
+ * visible. `onTrace` listeners see each result.
208
+ */
209
+ trace(label: string): Promise<number>;
210
+ /**
211
+ * Requests a frame and resolves (with the ms waited) once a complete frame is presented, as
212
+ * `trace` does, but no `onTrace` listener sees it: for tools waiting on the screen, not for
213
+ * latency.
214
+ */
215
+ whenPresented(): Promise<number>;
216
+ /** Called with every resolved trace: its label and latency in ms. Returns an unsubscribe function. */
217
+ onTrace(listener: (label: string, ms: number) => void): () => void;
218
+ /**
219
+ * Drops accumulated fixed-step time, so the next frame runs FixedUpdate for its own delta only.
220
+ * On-demand runners call it on waking, so a scene idle for a minute doesn't simulate the minute.
221
+ */
222
+ resetFixedTime(): void;
98
223
  /** Runs one frame. `delta` is in seconds. */
99
224
  update(delta: number): void;
225
+ /** One frame, timed as the `frame` span (0074). */
226
+ private frame;
227
+ private frameBody;
228
+ /** Resolves traces and the usable mark once this frame is on screen. */
229
+ private present;
100
230
  /** Called after every frame with the number of frames completed. Returns an unsubscribe function. */
101
231
  onFrame(listener: (frame: number) => void): () => void;
102
232
  /**
@@ -106,6 +236,20 @@ export declare class App {
106
236
  pump(): void;
107
237
  runSchedule(label: ScheduleLabel): void;
108
238
  describe(): AppDescription;
239
+ private assertLive;
240
+ /**
241
+ * Runs `fn` inside the app's scopes, as build and ready hooks run. For the part of an async ready
242
+ * hook after its first await, which runs outside them: what it makes counts against the app.
243
+ */
244
+ scoped<T>(fn: () => T): T;
245
+ /** Enters every scope; returns how many, for the matching `exit`. */
246
+ private enter;
247
+ /** Exits the first `n` scopes, so one added inside the section never sees an unmatched exit. */
248
+ private exit;
249
+ private buildPlugin;
250
+ /** Awaits a ready hook; the part that runs before its first await is inside the app's scopes. */
251
+ private readyPlugin;
252
+ private disposeAll;
109
253
  private schedule;
110
254
  private applyStateTransitions;
111
255
  private dependencyError;