Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions site/src/content/api/audio-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ memberCount: 18
tier: "stable"
sections: ["Import", "Constructors", "Methods", "Properties", "Events", "Source"]
sourcePath: "src/audio/AudioManager.ts"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L24"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L25"
---
## Import

Expand Down Expand Up @@ -41,7 +41,7 @@ AudioManager.muteOnHidden is enabled.
- `play(source: Playable, options?: PlayOptions): Voice`
- `registerBus(bus: AudioBus): this`
- `unregisterBus(bus: AudioBus): this`
- `update(): void`
- `update(_delta: Time): void`

## Properties

Expand All @@ -58,4 +58,4 @@ AudioManager.muteOnHidden is enabled.

## Source

[src/audio/AudioManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L24)
[src/audio/AudioManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/audio/AudioManager.ts#L25)
6 changes: 3 additions & 3 deletions site/src/content/api/input-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ memberCount: 38
tier: "stable"
sections: ["Import", "Constructors", "Methods", "Properties", "Events", "Source"]
sourcePath: "src/input/InputManager.ts"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L60"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L61"
---
## Import

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

## Properties

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

## Source

[src/input/InputManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L60)
[src/input/InputManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InputManager.ts#L61)
6 changes: 3 additions & 3 deletions site/src/content/api/interaction-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ memberCount: 7
tier: "stable"
sections: ["Import", "Constructors", "Methods", "Source"]
sourcePath: "src/input/InteractionManager.ts"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L70"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L71"
---
## Import

Expand Down Expand Up @@ -42,8 +42,8 @@ this class yourself.
- `getHoveredNode(pointerId?: number): RenderNode | null`
- `popInputCapture(): void`
- `pushInputCapture(root: RenderNode): void`
- `update(): void`
- `update(_delta: Time): void`

## Source

[src/input/InteractionManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L70)
[src/input/InteractionManager.ts](https://github.com/Exoridus/ExoJS/blob/main/src/input/InteractionManager.ts#L71)
8 changes: 4 additions & 4 deletions site/src/content/api/prefab.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ memberCount: 4
tier: "stable"
sections: ["Import", "Methods", "Source"]
sourcePath: "src/core/serialization/Prefab.ts"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L26"
sourceUrl: "https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L27"
---
## Import

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

## Methods

- `instantiate(loader: Loader | null): SceneNode`
- `instantiate(loader: Loader | null, registry?: SerializationRegistry): SceneNode`
- `toJSON(): SerializedNode`
- `from(node: SceneNode, loader: Loader | null): Prefab`
- `from(node: SceneNode, loader: Loader | null, registry?: SerializationRegistry): Prefab`
- `fromJSON(descriptor: SerializedNode): Prefab`

## Source

[src/core/serialization/Prefab.ts](https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L26)
[src/core/serialization/Prefab.ts](https://github.com/Exoridus/ExoJS/blob/main/src/core/serialization/Prefab.ts#L27)
2 changes: 2 additions & 0 deletions site/src/content/guide/debugging/custom-renderers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ Direct backend access is deliberately unabstracted — you are writing raw WebGP

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.

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.

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.

## When to use which
Expand Down
5 changes: 3 additions & 2 deletions src/audio/AudioManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Signal } from '#core/Signal';
import type { System } from '#core/System';
import type { Time } from '#core/Time';

import { getAudioContext, isAudioContextReady, onAudioContextReady } from './audio-context';
import { AudioBus } from './AudioBus';
Expand Down Expand Up @@ -132,8 +133,8 @@ export class AudioManager implements System {
});
}

/** Called once per frame from Application.update(). */
public update(): void {
/** Called once per frame from Application.update(). The frame delta is unused here (hence `_delta`); present for {@link System} contract compliance. */
public update(_delta: Time): void {
this.listener._tick();
// Tick spatial voices and prune ended ones.
for (const voice of this._spatial) {
Expand Down
15 changes: 10 additions & 5 deletions src/core/serialization/Prefab.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { SceneNode } from '#core/SceneNode';
import type { Loader } from '#resources/Loader';

import type { SerializationRegistry } from './SerializationRegistry';
import { deserializeTree, serializeTree } from './serialize';
import type { SerializedNode } from './types';

Expand Down Expand Up @@ -28,10 +29,12 @@ export class Prefab {

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

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

/** The underlying JSON descriptor (JSON-serialisable). Treat as read-only. The standard `JSON.stringify(prefab)` hook. */
Expand Down
26 changes: 23 additions & 3 deletions src/core/serialization/SerializationRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ export class SerializationRegistry {
public hasType(typeName: string): boolean {
return this._byName.has(typeName) || (this._fallback?.hasType(typeName) ?? false);
}

/**
* Remove every own registration (both the name and constructor maps). The
* fallback chain is left untouched. Test infrastructure only — used to reset
* the {@link defaultSerializationRegistry} between suites so process-wide
* registrations do not leak across them.
* @internal
*/
public clear(): void {
this._byName.clear();
this._byCtor.destroy();
}
}

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

/**
* Register a custom {@link NodeSerializer} on the {@link defaultSerializationRegistry}.
* Register a custom {@link NodeSerializer}. Defaults to the
* {@link defaultSerializationRegistry}; pass `app.serializers` to register the
* type only for that {@link Application} (it stays isolated from other apps and
* from the global registry, resolving through the fallback chain).
*
* Use this to make your own {@link SceneNode} subclasses serializable. Delegate
* to a base type's behaviour by composing with the framework helpers, or
Expand All @@ -116,6 +131,11 @@ export const defaultSerializationRegistry = new SerializationRegistry();
* });
* ```
*/
export function registerSerializer<T extends SceneNode>(typeName: string, ctor: SceneNodeConstructor<T>, serializer: NodeSerializer<T>): void {
defaultSerializationRegistry.register(typeName, ctor, serializer);
export function registerSerializer<T extends SceneNode>(
typeName: string,
ctor: SceneNodeConstructor<T>,
serializer: NodeSerializer<T>,
registry: SerializationRegistry = defaultSerializationRegistry,
): void {
registry.register(typeName, ctor, serializer);
}
17 changes: 17 additions & 0 deletions src/core/serialization/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ function ensureCoreSerializers(): void {
registerCoreSerializers(defaultSerializationRegistry);
}

/**
* Reset the process-wide serialization state so test suites do not leak
* registrations into one another. Clears both module-level states: the
* {@link defaultSerializationRegistry} **and** the `_coreRegistered` latch. Both
* are mandatory — clearing the registry alone would leave the latch `true`, so
* the core serializers would never re-register and later round-trips would fail
* with spurious "No serializer registered" errors.
*
* Not exported from the public barrel; import via the direct module path in
* tests.
* @internal — For unit tests only.
*/
export function _resetDefaultSerializers(): void {
_coreRegistered = false;
defaultSerializationRegistry.clear();
}

function createSerializeContext(loader: Loader | null, registry: SerializationRegistry): SerializeContext {
const ctx: SerializeContext = {
version: SERIALIZATION_VERSION,
Expand Down
5 changes: 2 additions & 3 deletions src/input/InputManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Application } from '#core/Application';
import { Signal } from '#core/Signal';
import type { System } from '#core/System';
import type { Time } from '#core/Time';
import { stopEvent } from '#core/utils';
import { Flags } from '#math/Flags';
import { getDistance } from '#math/utils';
Expand Down Expand Up @@ -333,7 +334,7 @@ export class InputManager implements System {
* the channel buffer, fires the corresponding Signals, then evaluates
* each registered binding.
*/
public update(): this {
public update(_delta: Time): void {
this.updateGamepads();

for (const binding of this.bindings) {
Expand All @@ -343,8 +344,6 @@ export class InputManager implements System {
if (this.flags.value !== InputManagerFlag.None) {
this.updateEvents();
}

return this;
}

public destroy(): void {
Expand Down
3 changes: 2 additions & 1 deletion src/input/InteractionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Application } from '#core/Application';
import type { Signal } from '#core/Signal';
import type { InteractionHooks, Stage } from '#core/Stage';
import type { System } from '#core/System';
import type { Time } from '#core/Time';
import type { PointLike } from '#math/PointLike';
import type { QuadtreeItem } from '#math/Quadtree';
import { Quadtree } from '#math/Quadtree';
Expand Down Expand Up @@ -252,7 +253,7 @@ export class InteractionManager implements InteractionHooks, System {
* activity; every signal handler that enqueues an event sets `_dirty =
* true`, and `update()` clears it at the top before draining the queue.
*/
public update(): void {
public update(_delta: Time): void {
if (!this._dirty) return;
this._dirty = false;

Expand Down
7 changes: 7 additions & 0 deletions src/renderer-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
// backends, low-level GL/GPU building blocks). Ordinary application code should
// import from the root `@codexo/exojs` barrel; these symbols are intentionally
// kept out of it (see src/rendering/public.ts).
//
// Custom renderers extend the ABSTRACT renderers below (AbstractWebGl2Renderer /
// AbstractWebGl2BatchedRenderer / AbstractWebGpuRenderer) — the subclass-stable
// contract used by the exojs-particles and exojs-tilemap packages. The engine's
// built-in CONCRETE renderers (WebGl2SpriteRenderer, WebGpuMeshRenderer, …) are
// internal and intentionally NOT exported here: they are coupled to internal
// sprite/mesh data paths and are not a stable subclassing surface pre-1.0.

export { Drawable } from '#rendering/Drawable';
export type { PixelSnapMode } from '#rendering/pixelSnap';
Expand Down
Loading
Loading