Skip to content

Commit 5d41667

Browse files
author
Exoridus
committed
refactor(api)!: v0.15 Welle 1 — System.update(delta), serializer per-app, dead barrel removed
BREAKING: unifies the System.update protocol across all managers and removes a dead internal rendering barrel. 3a — System.update(delta: Time): void across all 5 app systems. InputManager update(): this -> update(_delta: Time): void (fluent return removed); InteractionManager + AudioManager gain the param. Tween/RenderingContext were already conform. 0 external call-sites broke. The param is `_delta` (unused but required by the System contract; lint:strict forbids unused non-_ args, the spec's "lint allows it" assumption was empirically wrong). 3b — registerSerializer + Prefab.from/instantiate gain an optional `registry` param (per-app serialization; default = global). New @internal _resetDefaultSerializers() + SerializationRegistry.clear() close a cross-suite global-registry leak (serialization.test.ts:273); afterEach reset + 2 new tests. 3d — deleted dead internal barrel src/rendering/index.ts (0 static imports; migrated its one dynamic test import to #rendering/public). Documented the abstract-vs-concrete renderer boundary in renderer-sdk.ts + custom-renderers guide. api-mdx regenerated (audio/input/interaction-manager, prefab). 3395 tests pass.
1 parent 78df6b7 commit 5d41667

17 files changed

Lines changed: 138 additions & 154 deletions

site/src/content/api/audio-manager.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ memberCount: 18
99
tier: "stable"
1010
sections: ["Import", "Constructors", "Methods", "Properties", "Events", "Source"]
1111
sourcePath: "src/audio/AudioManager.ts"
12-
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L24"
12+
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L25"
1313
---
1414
## Import
1515

@@ -41,7 +41,7 @@ AudioManager.muteOnHidden is enabled.
4141
- `play(source: Playable, options?: PlayOptions): Voice`
4242
- `registerBus(bus: AudioBus): this`
4343
- `unregisterBus(bus: AudioBus): this`
44-
- `update(): void`
44+
- `update(_delta: Time): void`
4545

4646
## Properties
4747

@@ -58,4 +58,4 @@ AudioManager.muteOnHidden is enabled.
5858

5959
## Source
6060

61-
[src/audio/AudioManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L24)
61+
[src/audio/AudioManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L25)

site/src/content/api/input-manager.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ memberCount: 38
99
tier: "stable"
1010
sections: ["Import", "Constructors", "Methods", "Properties", "Events", "Source"]
1111
sourcePath: "src/input/InputManager.ts"
12-
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L60"
12+
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L61"
1313
---
1414
## Import
1515

@@ -44,7 +44,7 @@ automatically — you do not instantiate this class yourself.
4444
- `onStart(channel: InputChannel | readonly InputChannel[], callback: object, options?: InputBindingOptions): InputBinding`
4545
- `onStop(channel: InputChannel | readonly InputChannel[], callback: object, options?: InputBindingOptions): InputBinding`
4646
- `onTrigger(channel: InputChannel | readonly InputChannel[], callback: object, options?: InputBindingOptions): InputBinding`
47-
- `update(): this`
47+
- `update(_delta: Time): void`
4848

4949
## Properties
5050

@@ -83,4 +83,4 @@ automatically — you do not instantiate this class yourself.
8383

8484
## Source
8585

86-
[src/input/InputManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L60)
86+
[src/input/InputManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L61)

site/src/content/api/interaction-manager.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ memberCount: 7
99
tier: "stable"
1010
sections: ["Import", "Constructors", "Methods", "Source"]
1111
sourcePath: "src/input/InteractionManager.ts"
12-
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L70"
12+
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L71"
1313
---
1414
## Import
1515

@@ -42,8 +42,8 @@ this class yourself.
4242
- `getHoveredNode(pointerId?: number): RenderNode | null`
4343
- `popInputCapture(): void`
4444
- `pushInputCapture(root: RenderNode): void`
45-
- `update(): void`
45+
- `update(_delta: Time): void`
4646

4747
## Source
4848

49-
[src/input/InteractionManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L70)
49+
[src/input/InteractionManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L71)

site/src/content/api/prefab.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ memberCount: 4
99
tier: "stable"
1010
sections: ["Import", "Methods", "Source"]
1111
sourcePath: "src/core/serialization/Prefab.ts"
12-
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L26"
12+
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L27"
1313
---
1414
## Import
1515

@@ -35,11 +35,11 @@ for (let i = 0; i \< 10; i++) \{
3535

3636
## Methods
3737

38-
- `instantiate(loader: Loader | null): SceneNode`
38+
- `instantiate(loader: Loader | null, registry?: SerializationRegistry): SceneNode`
3939
- `toJSON(): SerializedNode`
40-
- `from(node: SceneNode, loader: Loader | null): Prefab`
40+
- `from(node: SceneNode, loader: Loader | null, registry?: SerializationRegistry): Prefab`
4141
- `fromJSON(descriptor: SerializedNode): Prefab`
4242

4343
## Source
4444

45-
[src/core/serialization/Prefab.ts](https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L26)
45+
[src/core/serialization/Prefab.ts](https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L27)

site/src/content/guide/debugging/custom-renderers.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ Direct backend access is deliberately unabstracted — you are writing raw WebGP
129129

130130
ExoJS resolves a renderer for each drawable type through the `RendererRegistry` (available from `@codexo/exojs/renderer-sdk`). You can register a custom renderer for an existing drawable type via `backend.rendererRegistry.registerRenderer(drawableConstructor, renderer)`. A renderer implements `connect(backend)`, `disconnect()`, `render(drawable)`, and `flush()` — these map to GPU resource acquisition, release, per-drawable recording, and batch submission respectively.
131131

132+
In practice you build a custom renderer by extending one of the **abstract** base renderers from `@codexo/exojs/renderer-sdk``AbstractWebGl2Renderer` / `AbstractWebGl2BatchedRenderer` (WebGL2) or `AbstractWebGpuRenderer` (WebGPU) — which is exactly how the `@codexo/exojs-particles` and `@codexo/exojs-tilemap` packages build theirs. The engine's built-in *concrete* renderers (e.g. `WebGl2SpriteRenderer`) are internal: coupled to internal sprite/mesh data paths and intentionally not part of the SDK surface, so don't subclass them directly.
133+
132134
Registering a custom renderer is an advanced extension point. For most custom rendering needs — procedural geometry between draws, a single custom shape that doesn't fit `Mesh``CallbackRenderPass` is the simpler and more intentional path.
133135

134136
## When to use which

src/audio/AudioManager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Signal } from '#core/Signal';
22
import type { System } from '#core/System';
3+
import type { Time } from '#core/Time';
34

45
import { getAudioContext, isAudioContextReady, onAudioContextReady } from './audio-context';
56
import { AudioBus } from './AudioBus';
@@ -132,8 +133,8 @@ export class AudioManager implements System {
132133
});
133134
}
134135

135-
/** Called once per frame from Application.update(). */
136-
public update(): void {
136+
/** Called once per frame from Application.update(). The frame delta is unused here (hence `_delta`); present for {@link System} contract compliance. */
137+
public update(_delta: Time): void {
137138
this.listener._tick();
138139
// Tick spatial voices and prune ended ones.
139140
for (const voice of this._spatial) {

src/core/serialization/Prefab.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { SceneNode } from '#core/SceneNode';
22
import type { Loader } from '#resources/Loader';
33

4+
import type { SerializationRegistry } from './SerializationRegistry';
45
import { deserializeTree, serializeTree } from './serialize';
56
import type { SerializedNode } from './types';
67

@@ -28,10 +29,12 @@ export class Prefab {
2829

2930
/**
3031
* Capture `node`'s subtree as a prefab. Pass the {@link Loader} so texture and
31-
* other asset references resolve to their source keys.
32+
* other asset references resolve to their source keys. Pass `app.serializers`
33+
* as `registry` to resolve app-scoped (extension) serializers; defaults to the
34+
* global registry.
3235
*/
33-
public static from(node: SceneNode, loader: Loader | null = null): Prefab {
34-
return new Prefab(serializeTree(node, loader));
36+
public static from(node: SceneNode, loader: Loader | null = null, registry?: SerializationRegistry): Prefab {
37+
return new Prefab(serializeTree(node, loader, registry));
3538
}
3639

3740
/**
@@ -46,9 +49,11 @@ export class Prefab {
4649
/**
4750
* Instantiate a fresh, independent copy of the captured subtree. Referenced
4851
* assets must be pre-loaded into `loader`. Call repeatedly for many instances.
52+
* Pass `app.serializers` as `registry` to resolve app-scoped (extension)
53+
* serializers; defaults to the global registry.
4954
*/
50-
public instantiate(loader: Loader | null = null): SceneNode {
51-
return deserializeTree(this._descriptor, loader);
55+
public instantiate(loader: Loader | null = null, registry?: SerializationRegistry): SceneNode {
56+
return deserializeTree(this._descriptor, loader, registry);
5257
}
5358

5459
/** The underlying JSON descriptor (JSON-serialisable). Treat as read-only. The standard `JSON.stringify(prefab)` hook. */

src/core/serialization/SerializationRegistry.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ export class SerializationRegistry {
9393
public hasType(typeName: string): boolean {
9494
return this._byName.has(typeName) || (this._fallback?.hasType(typeName) ?? false);
9595
}
96+
97+
/**
98+
* Remove every own registration (both the name and constructor maps). The
99+
* fallback chain is left untouched. Test infrastructure only — used to reset
100+
* the {@link defaultSerializationRegistry} between suites so process-wide
101+
* registrations do not leak across them.
102+
* @internal
103+
*/
104+
public clear(): void {
105+
this._byName.clear();
106+
this._byCtor.destroy();
107+
}
96108
}
97109

98110
/**
@@ -103,7 +115,10 @@ export class SerializationRegistry {
103115
export const defaultSerializationRegistry = new SerializationRegistry();
104116

105117
/**
106-
* Register a custom {@link NodeSerializer} on the {@link defaultSerializationRegistry}.
118+
* Register a custom {@link NodeSerializer}. Defaults to the
119+
* {@link defaultSerializationRegistry}; pass `app.serializers` to register the
120+
* type only for that {@link Application} (it stays isolated from other apps and
121+
* from the global registry, resolving through the fallback chain).
107122
*
108123
* Use this to make your own {@link SceneNode} subclasses serializable. Delegate
109124
* to a base type's behaviour by composing with the framework helpers, or
@@ -116,6 +131,11 @@ export const defaultSerializationRegistry = new SerializationRegistry();
116131
* });
117132
* ```
118133
*/
119-
export function registerSerializer<T extends SceneNode>(typeName: string, ctor: SceneNodeConstructor<T>, serializer: NodeSerializer<T>): void {
120-
defaultSerializationRegistry.register(typeName, ctor, serializer);
134+
export function registerSerializer<T extends SceneNode>(
135+
typeName: string,
136+
ctor: SceneNodeConstructor<T>,
137+
serializer: NodeSerializer<T>,
138+
registry: SerializationRegistry = defaultSerializationRegistry,
139+
): void {
140+
registry.register(typeName, ctor, serializer);
121141
}

src/core/serialization/serialize.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ function ensureCoreSerializers(): void {
2626
registerCoreSerializers(defaultSerializationRegistry);
2727
}
2828

29+
/**
30+
* Reset the process-wide serialization state so test suites do not leak
31+
* registrations into one another. Clears both module-level states: the
32+
* {@link defaultSerializationRegistry} **and** the `_coreRegistered` latch. Both
33+
* are mandatory — clearing the registry alone would leave the latch `true`, so
34+
* the core serializers would never re-register and later round-trips would fail
35+
* with spurious "No serializer registered" errors.
36+
*
37+
* Not exported from the public barrel; import via the direct module path in
38+
* tests.
39+
* @internal — For unit tests only.
40+
*/
41+
export function _resetDefaultSerializers(): void {
42+
_coreRegistered = false;
43+
defaultSerializationRegistry.clear();
44+
}
45+
2946
function createSerializeContext(loader: Loader | null, registry: SerializationRegistry): SerializeContext {
3047
const ctx: SerializeContext = {
3148
version: SERIALIZATION_VERSION,

src/input/InputManager.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Application } from '#core/Application';
22
import { Signal } from '#core/Signal';
33
import type { System } from '#core/System';
4+
import type { Time } from '#core/Time';
45
import { stopEvent } from '#core/utils';
56
import { Flags } from '#math/Flags';
67
import { getDistance } from '#math/utils';
@@ -333,7 +334,7 @@ export class InputManager implements System {
333334
* the channel buffer, fires the corresponding Signals, then evaluates
334335
* each registered binding.
335336
*/
336-
public update(): this {
337+
public update(_delta: Time): void {
337338
this.updateGamepads();
338339

339340
for (const binding of this.bindings) {
@@ -343,8 +344,6 @@ export class InputManager implements System {
343344
if (this.flags.value !== InputManagerFlag.None) {
344345
this.updateEvents();
345346
}
346-
347-
return this;
348347
}
349348

350349
public destroy(): void {

0 commit comments

Comments
 (0)