@waica/engine 0.22.0 → 0.24.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 +33 -0
- package/dist/assets/asset-loader.d.ts +1 -1
- package/dist/assets/asset-loader.js +1 -1
- package/dist/assets/texture-backend.d.ts +1 -1
- package/dist/assets/texture-backend.js +1 -1
- package/dist/camera.js +1 -1
- package/dist/collision-category.js +5 -2
- package/dist/collision-dispatch.d.ts +7 -1
- package/dist/collision-dispatch.js +18 -11
- package/dist/components/animated-sprite.d.ts +2 -0
- package/dist/components/animated-sprite.js +42 -7
- package/dist/components/sprite.d.ts +6 -0
- package/dist/components/sprite.js +53 -5
- package/dist/components/tilemap.js +3 -1
- package/dist/entity.d.ts +1 -1
- package/dist/entity.js +1 -1
- package/dist/game.d.ts +22 -3
- package/dist/game.js +68 -45
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -1
- package/dist/particle-batch.d.ts +1 -1
- package/dist/particle-batch.js +4 -1
- package/dist/particle-sort-bounds.d.ts +1 -1
- package/dist/particle-sort-bounds.js +1 -1
- package/dist/pointer.js +1 -1
- package/dist/render-output.d.ts +27 -0
- package/dist/render-output.js +44 -0
- package/dist/render-readiness.d.ts +37 -0
- package/dist/render-readiness.js +61 -0
- package/dist/render-sort.d.ts +14 -0
- package/dist/render-sort.js +37 -0
- package/dist/runtime-bridge.d.ts +18 -2
- package/dist/runtime-bridge.js +7 -1
- package/dist/scene.d.ts +10 -0
- package/dist/spatial-broadphase.d.ts +7 -1
- package/dist/spatial-broadphase.js +16 -45
- package/dist/spatial-pairs.d.ts +17 -0
- package/dist/spatial-pairs.js +79 -0
- package/dist/sprite-batch.d.ts +93 -0
- package/dist/sprite-batch.js +223 -0
- package/dist/sprite-batches.d.ts +107 -0
- package/dist/sprite-batches.js +260 -0
- package/dist/sprite-draw-collector.d.ts +42 -0
- package/dist/sprite-draw-collector.js +107 -0
- package/dist/sprite-runs.d.ts +40 -0
- package/dist/sprite-runs.js +49 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,25 @@ Waica's public engine core: entities and components, the game loop, scene and pr
|
|
|
6
6
|
import { Component, Game, loadScene } from '@waica/engine'
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
## Migrating to 0.24.0: WebGPURenderer and `game.ready()`
|
|
10
|
+
|
|
11
|
+
A `Game` draws through three's `WebGPURenderer` (ADR 0025): WebGPU when the browser offers it, otherwise the renderer's own WebGL2 fallback. The browser decides — there is no option to force either one — and `game.backend` reports which one it got (`'webgpu'` or `'webgl2'`; `null` until the renderer is ready).
|
|
12
|
+
|
|
13
|
+
- **Await `game.ready()` before the first frame.** `new Game(...)` stays synchronous, but the renderer initializes asynchronously. Frames before it finishes still simulate (steps, `onUpdate`) but draw nothing, so a host waits for it before `game.start()`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const game = new Game({ canvas })
|
|
17
|
+
loadScene(game, scene, registry)
|
|
18
|
+
await game.assets.ready()
|
|
19
|
+
await game.ready()
|
|
20
|
+
game.start()
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Every call returns the same promise. It rejects with an error naming both `webgpu` and `webgl2` when neither initializes; nothing is ever drawn then. A `Game` disposed while `ready()` is pending never starts, and its renderer is released once initialization settles.
|
|
24
|
+
- **`THREE` is the `three/webgpu` build.** `import { THREE } from '@waica/engine'` now re-exports `three/webgpu`, the same copy of three the engine draws with: `THREE.WebGPURenderer` exists and `THREE.WebGLRenderer` does not. Custom GLSL does not render under `WebGPURenderer` — `ShaderMaterial`, `RawShaderMaterial` and `material.onBeforeCompile` are not supported there — so write custom shading as node materials with TSL (`three/tsl`). Built-in materials such as `MeshBasicMaterial` keep working unchanged.
|
|
25
|
+
- **Tests under happy-dom.** happy-dom has no GPU: a project test that builds a `Game` replaces `WebGPURenderer` from `three/webgpu` with a fake (it previously replaced `WebGLRenderer` from `three`), whose `init()` resolves.
|
|
26
|
+
- **Run Sessions.** A Run Session waits for the Render Backend before it reports ready, steps or screenshots, and its snapshots carry `backend`. A renderer that cannot initialize ends `start_project` with a runtime error that names the failure.
|
|
27
|
+
|
|
9
28
|
## Hitbox Collision Layers and Masks
|
|
10
29
|
|
|
11
30
|
Every `Hitbox` belongs to one named Collision Layer and declares the other layers in which it is interested through a Collision Mask:
|
|
@@ -150,6 +169,20 @@ The cues that hook a `StateMachine` must come after it and after the `ParticleEm
|
|
|
150
169
|
|
|
151
170
|
`active`, `capacity`, and `emitting` are the only fields exposed by Runtime Snapshot. Particle arrays remain private. This first CPU implementation is intended for bounded 2D effects, not collision, per-particle scripting, GPU simulation, trails, rotation, or sub-emitters.
|
|
152
171
|
|
|
172
|
+
## Sprite Batches
|
|
173
|
+
|
|
174
|
+
`Sprite` and `AnimatedSprite` are drawn through **Sprite Batches** by default (ADR 0024). Every frame, after y-sort, the engine orders everything it draws exactly as three would without batching — layer bands, then y-sort, then spawn order, with particle batches, tilemaps and any other mesh in between — and each run of consecutive sprites that share a batch key becomes one instanced draw call. The key is the art and how it is sampled: the texture (an `AnimatedSprite`'s current sheet), `pixelArt` and `shape`. Each sprite is one instance carrying its own placement, color, frame and flip, so tinting, resizing, re-anchoring or animating a sprite never creates a material, and what is drawn in front of what never changes.
|
|
175
|
+
|
|
176
|
+
- **On by default.** A scene without a `render` block, or with `render.batch: true`, batches. Nothing to configure: sprites of one texture in a row draw in one call, a sprite of other art (or a particle batch, a tilemap) in between starts a new run.
|
|
177
|
+
- **Culling and Group order kept.** A sprite outside the camera is skipped for the frame, as three culls a sprite's own mesh, and an off-camera renderable never splits a run. A run hangs under a Group with its sprites' Group `renderOrder`, so `entity.node.renderOrder` orders batched sprites exactly as it orders their meshes without batching.
|
|
178
|
+
- **Shared art.** All live sprites of one key share one material and one texture clone, scene-scoped: released with the scene at `unloadScene()` and `game.dispose()`. A sprite whose texture fails moves alone to the untextured entry of its shape and keeps showing its `color`.
|
|
179
|
+
- **Reusable slots and buffers.** A destroyed sprite's slot goes to the next spawn of its key. Each run draws through a pooled instance buffer sized by that run: 16 instances at first, doubling when a longer run needs it, never shrinking during the scene. A key drawn as several runs keeps one buffer per run, so instance memory stays proportional to the sprites drawn. Once the buffers have grown to a scene's longest runs (and as many runs as it ever splits into), spawning, destroying and reordering sprites allocates no GPU objects.
|
|
180
|
+
- **Turning it off.** Scene JSON `"render": { "batch": false }` (the editor's **Sprite batching** toggle in the scene inspector) restores the per-sprite path: one mesh, one material and one draw call per sprite. Use it to rule batching out when a scene looks different than you expect, or while debugging draw order; the cost is one draw call per sprite, which is what dominates frame time with thousands of sprites.
|
|
181
|
+
|
|
182
|
+
Under batching, the object a sprite adds to its entity is a hidden placement anchor that is never drawn. Hide or show a batched sprite through its entity (`entity.node.visible`, as `Health` blinking does); the anchor's own `visible` is not read for the batched draw, and setting it to true makes three also draw the anchor as a plain quad (without the sprite's tint or frame) — leave it alone, or use `render.batch: false` when code needs one real mesh per sprite.
|
|
183
|
+
|
|
184
|
+
How much batching saves depends on how interleaved a scene is: one texture over the whole scene draws in one call, while a top-down scene that alternates many textures by y gains little. Runtime Snapshots are unchanged — batches are not entities, and sprites report the same state either way.
|
|
185
|
+
|
|
153
186
|
## Component lifecycle
|
|
154
187
|
|
|
155
188
|
Waica keeps the lifecycle boundaries distinct:
|
package/dist/camera.js
CHANGED
|
@@ -5,7 +5,10 @@ export function validCollisionLayer(value) {
|
|
|
5
5
|
}
|
|
6
6
|
/** Package-internal exact directional interest check. */
|
|
7
7
|
export function collisionMaskTargets(mask, targetLayer) {
|
|
8
|
-
if (!
|
|
8
|
+
if (!Array.isArray(mask))
|
|
9
9
|
return false;
|
|
10
|
-
|
|
10
|
+
// Membership first: with explicit masks it rejects most pairs of a dense
|
|
11
|
+
// scene before the layer's syntax check runs. A '*' mask still checks it.
|
|
12
|
+
const named = mask.some((entry) => typeof entry === 'string' && (entry === '*' || entry === targetLayer));
|
|
13
|
+
return named && validCollisionLayer(targetLayer);
|
|
11
14
|
}
|
|
@@ -4,5 +4,11 @@ export interface CollisionDispatchStats {
|
|
|
4
4
|
readonly candidatePairs: number;
|
|
5
5
|
readonly narrowphaseCalls: number;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Game's package-internal trigger dispatch implementation. Pairs come from a
|
|
9
|
+
* frozen grid; each pair is then checked live, most selective checks first
|
|
10
|
+
* (liveness, masks, then the snapshotted Hitbox still mounted). The checks
|
|
11
|
+
* have no side effects, so their order only changes how fast a pair is
|
|
12
|
+
* rejected.
|
|
13
|
+
*/
|
|
8
14
|
export declare function dispatchCollisions(game: Game): CollisionDispatchStats;
|
|
@@ -3,34 +3,41 @@ import { collisionMaskTargets } from './collision-category.js';
|
|
|
3
3
|
import { collisionOverlap } from './collision-shape.js';
|
|
4
4
|
import { Hitbox } from './components/hitbox.js';
|
|
5
5
|
import { createHitboxBroadphase } from './spatial-broadphase.js';
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Game's package-internal trigger dispatch implementation. Pairs come from a
|
|
8
|
+
* frozen grid; each pair is then checked live, most selective checks first
|
|
9
|
+
* (liveness, masks, then the snapshotted Hitbox still mounted). The checks
|
|
10
|
+
* have no side effects, so their order only changes how fast a pair is
|
|
11
|
+
* rejected.
|
|
12
|
+
*/
|
|
7
13
|
export function dispatchCollisions(game) {
|
|
8
|
-
|
|
14
|
+
let candidatePairs = 0;
|
|
9
15
|
let narrowphaseCalls = 0;
|
|
10
|
-
|
|
16
|
+
createHitboxBroadphase(game).forEachPair((first, second) => {
|
|
17
|
+
candidatePairs += 1;
|
|
11
18
|
const a = first.entity;
|
|
12
19
|
const b = second.entity;
|
|
13
20
|
if (!a.alive || !b.alive)
|
|
14
|
-
|
|
15
|
-
if (a.get(Hitbox) !== first.hitbox || b.get(Hitbox) !== second.hitbox)
|
|
16
|
-
continue;
|
|
21
|
+
return;
|
|
17
22
|
const notifyA = collisionMaskTargets(first.hitbox.collidesWith, second.hitbox.layer);
|
|
18
23
|
const notifyB = collisionMaskTargets(second.hitbox.collidesWith, first.hitbox.layer);
|
|
19
24
|
if (!notifyA && !notifyB)
|
|
20
|
-
|
|
25
|
+
return;
|
|
26
|
+
if (a.get(Hitbox) !== first.hitbox || b.get(Hitbox) !== second.hitbox)
|
|
27
|
+
return;
|
|
21
28
|
narrowphaseCalls += 1;
|
|
22
29
|
if (!collisionOverlap(collisionBody(first.hitbox), collisionBody(second.hitbox)))
|
|
23
|
-
|
|
30
|
+
return;
|
|
24
31
|
if (notifyA) {
|
|
25
32
|
for (const component of [...a.components])
|
|
26
33
|
component.onCollide?.(b);
|
|
27
34
|
}
|
|
28
35
|
if (!a.alive || !b.alive)
|
|
29
|
-
|
|
36
|
+
return;
|
|
30
37
|
if (notifyB) {
|
|
31
38
|
for (const component of [...b.components])
|
|
32
39
|
component.onCollide?.(a);
|
|
33
40
|
}
|
|
34
|
-
}
|
|
35
|
-
return { candidatePairs
|
|
41
|
+
});
|
|
42
|
+
return { candidatePairs, narrowphaseCalls };
|
|
36
43
|
}
|
|
@@ -113,6 +113,8 @@ export declare class AnimatedSprite extends Component implements YSortParticipan
|
|
|
113
113
|
* touches game.assets, so it counts nowhere and warns about nothing.
|
|
114
114
|
*/
|
|
115
115
|
private sheetTexture;
|
|
116
|
+
/** The batch key of a sheet: its art, this sprite's filtering, a rectangle. */
|
|
117
|
+
private sheetKey;
|
|
116
118
|
private showFrame;
|
|
117
119
|
/** Repositions/rescales the displayed frame inside its anchored full-size box. */
|
|
118
120
|
private syncQuad;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
2
|
import { Component } from '../component.js';
|
|
3
3
|
import { ClipPlayer } from '../animation/clip-player.js';
|
|
4
4
|
import { locateFrame, sheetCell } from '../animation/sheet.js';
|
|
5
5
|
import { spritePlacement } from '../sprite-placement.js';
|
|
6
6
|
import { reportRejection } from '../report-rejection.js';
|
|
7
|
+
import { spriteBatchesOf, spriteInstanceOf } from '../sprite-batches.js';
|
|
8
|
+
/** Where a frame on a failed sheet draws: the flat (white) untextured quad. */
|
|
9
|
+
const UNTEXTURED = { texture: null, pixelArt: false, shape: 'rectangle' };
|
|
7
10
|
const clampAnchor = (value) => Math.min(1, Math.max(0, value));
|
|
8
11
|
/**
|
|
9
12
|
* Sprite animated from one or more spritesheets. The main sheet is the
|
|
@@ -139,6 +142,8 @@ export class AnimatedSprite extends Component {
|
|
|
139
142
|
texs = [];
|
|
140
143
|
// Clones whose sheet failed to load: applyFrame never installs them (CA-4).
|
|
141
144
|
failedSheets = new Set();
|
|
145
|
+
// The drawn quad, or under Sprite Batches (ADR 0024) the hidden anchor of
|
|
146
|
+
// this sprite's instance, whose frame UV is read from the sheet clone.
|
|
142
147
|
mesh;
|
|
143
148
|
frame = 0;
|
|
144
149
|
// Current frame's quad scale relative to the sheet's largest cell: always
|
|
@@ -169,8 +174,17 @@ export class AnimatedSprite extends Component {
|
|
|
169
174
|
tex.repeat.set(1 / Math.max(1, sheet.cols), 1 / Math.max(1, sheet.rows));
|
|
170
175
|
return tex;
|
|
171
176
|
});
|
|
172
|
-
const
|
|
173
|
-
|
|
177
|
+
const batches = spriteBatchesOf(this.game);
|
|
178
|
+
if (batches?.enabled) {
|
|
179
|
+
const instance = batches.attach(this.sheetKey(0));
|
|
180
|
+
instance.shownSheet = 0;
|
|
181
|
+
instance.uvSource = this.texs[0] ?? instance.uvSource;
|
|
182
|
+
this.mesh = instance.anchor;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const material = new THREE.MeshBasicMaterial({ map: this.texs[0], transparent: true });
|
|
186
|
+
this.mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
|
|
187
|
+
}
|
|
174
188
|
this.mesh.position.z = this.layer * 0.01;
|
|
175
189
|
this.syncQuad();
|
|
176
190
|
this.entity.node.add(this.mesh);
|
|
@@ -193,9 +207,16 @@ export class AnimatedSprite extends Component {
|
|
|
193
207
|
this.showFrame(this.player.advance(dt));
|
|
194
208
|
}
|
|
195
209
|
onDestroy() {
|
|
196
|
-
this.mesh
|
|
197
|
-
|
|
198
|
-
|
|
210
|
+
const instance = spriteInstanceOf(this.mesh);
|
|
211
|
+
if (instance) {
|
|
212
|
+
instance.release();
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
this.mesh?.removeFromParent();
|
|
216
|
+
this.mesh?.geometry.dispose();
|
|
217
|
+
this.mesh?.material.dispose();
|
|
218
|
+
}
|
|
219
|
+
this.mesh = undefined;
|
|
199
220
|
// Only this sprite's clones: the cached bases live with the Game.
|
|
200
221
|
for (const tex of this.texs)
|
|
201
222
|
tex.dispose();
|
|
@@ -225,6 +246,10 @@ export class AnimatedSprite extends Component {
|
|
|
225
246
|
}), 'sheet texture settle');
|
|
226
247
|
return texture;
|
|
227
248
|
}
|
|
249
|
+
/** The batch key of a sheet: its art, this sprite's filtering, a rectangle. */
|
|
250
|
+
sheetKey(index) {
|
|
251
|
+
return { texture: this.sheets[index]?.texture ?? '', pixelArt: this.pixelArt, shape: 'rectangle' };
|
|
252
|
+
}
|
|
228
253
|
showFrame(index) {
|
|
229
254
|
this.frame = index;
|
|
230
255
|
this.applyFrame();
|
|
@@ -254,7 +279,17 @@ export class AnimatedSprite extends Component {
|
|
|
254
279
|
const tex = this.texs[located.sheet];
|
|
255
280
|
if (!sheet || !tex)
|
|
256
281
|
return;
|
|
257
|
-
|
|
282
|
+
const instance = spriteInstanceOf(this.mesh);
|
|
283
|
+
if (instance) {
|
|
284
|
+
const failed = this.failedSheets.has(tex);
|
|
285
|
+
if (instance.shownSheet !== located.sheet || instance.shownSheetFailed !== failed) {
|
|
286
|
+
instance.moveTo(failed ? UNTEXTURED : this.sheetKey(located.sheet));
|
|
287
|
+
instance.shownSheet = located.sheet;
|
|
288
|
+
instance.shownSheetFailed = failed;
|
|
289
|
+
}
|
|
290
|
+
instance.uvSource = tex;
|
|
291
|
+
}
|
|
292
|
+
else if (this.mesh) {
|
|
258
293
|
const map = this.failedSheets.has(tex) ? null : tex;
|
|
259
294
|
if (this.mesh.material.map !== map) {
|
|
260
295
|
this.mesh.material.map = map;
|
|
@@ -68,6 +68,12 @@ export declare class Sprite extends Component implements YSortParticipant {
|
|
|
68
68
|
set shape(value: SpriteShape);
|
|
69
69
|
private mesh?;
|
|
70
70
|
onReady(): void;
|
|
71
|
+
private buildMesh;
|
|
72
|
+
/**
|
|
73
|
+
* One instance in the batch of this sprite's key, tinted like the material
|
|
74
|
+
* the per-sprite path would build: white over a texture, `color` without.
|
|
75
|
+
*/
|
|
76
|
+
private joinBatch;
|
|
71
77
|
onDestroy(): void;
|
|
72
78
|
/**
|
|
73
79
|
* CA-4's failure rule: an image that never arrives leaves the flat
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
2
|
import { Component } from '../component.js';
|
|
3
3
|
import { spritePlacement } from '../sprite-placement.js';
|
|
4
4
|
import { reportRejection } from '../report-rejection.js';
|
|
5
|
+
import { spriteBatchesOf, spriteInstanceOf } from '../sprite-batches.js';
|
|
5
6
|
const clampAnchor = (value) => Math.min(1, Math.max(0, value));
|
|
6
7
|
/**
|
|
7
8
|
* Textured or flat-color quad. In the unified pipeline, a 2D sprite is a
|
|
@@ -41,7 +42,11 @@ export class Sprite extends Component {
|
|
|
41
42
|
}
|
|
42
43
|
set color(value) {
|
|
43
44
|
this._color = value;
|
|
44
|
-
this.mesh
|
|
45
|
+
const instance = spriteInstanceOf(this.mesh);
|
|
46
|
+
if (instance)
|
|
47
|
+
instance.color.setHex(value);
|
|
48
|
+
else
|
|
49
|
+
this.mesh?.material.color.setHex(value);
|
|
45
50
|
}
|
|
46
51
|
_offsetX = 0;
|
|
47
52
|
_offsetY = 0;
|
|
@@ -102,11 +107,30 @@ export class Sprite extends Component {
|
|
|
102
107
|
this._shape = value === 'circle' ? 'circle' : 'rectangle';
|
|
103
108
|
if (!this.mesh)
|
|
104
109
|
return;
|
|
110
|
+
const instance = spriteInstanceOf(this.mesh);
|
|
111
|
+
if (instance) {
|
|
112
|
+
instance.moveTo({ ...instance.key, shape: this._shape });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
105
115
|
this.mesh.geometry.dispose();
|
|
106
116
|
this.mesh.geometry = this.createGeometry();
|
|
107
117
|
}
|
|
118
|
+
// The drawn quad, or under Sprite Batches (ADR 0024) the hidden anchor of
|
|
119
|
+
// this sprite's instance: the same placement either way.
|
|
108
120
|
mesh;
|
|
109
121
|
onReady() {
|
|
122
|
+
const batches = spriteBatchesOf(this.game);
|
|
123
|
+
if (batches?.enabled)
|
|
124
|
+
this.joinBatch(batches);
|
|
125
|
+
else
|
|
126
|
+
this.buildMesh();
|
|
127
|
+
if (!this.mesh)
|
|
128
|
+
return;
|
|
129
|
+
this.mesh.position.z = this.layer * 0.01;
|
|
130
|
+
this.syncQuad();
|
|
131
|
+
this.entity.node.add(this.mesh);
|
|
132
|
+
}
|
|
133
|
+
buildMesh() {
|
|
110
134
|
const material = new THREE.MeshBasicMaterial({ color: this.color, transparent: true });
|
|
111
135
|
if (this.texture) {
|
|
112
136
|
// Its own clone of the cached base (game.assets, ADR 0019): filters
|
|
@@ -125,11 +149,35 @@ export class Sprite extends Component {
|
|
|
125
149
|
}), 'sprite texture settle');
|
|
126
150
|
}
|
|
127
151
|
this.mesh = new THREE.Mesh(this.createGeometry(), material);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* One instance in the batch of this sprite's key, tinted like the material
|
|
155
|
+
* the per-sprite path would build: white over a texture, `color` without.
|
|
156
|
+
*/
|
|
157
|
+
joinBatch(batches) {
|
|
158
|
+
const key = { texture: this.texture || null, pixelArt: this.pixelArt, shape: this._shape };
|
|
159
|
+
const instance = batches.attach(key);
|
|
160
|
+
instance.color.setHex(this.texture ? 0xffffff : this.color);
|
|
161
|
+
this.mesh = instance.anchor;
|
|
162
|
+
const settled = batches.settled(key);
|
|
163
|
+
if (!settled)
|
|
164
|
+
return;
|
|
165
|
+
reportRejection(settled.then((outcome) => {
|
|
166
|
+
// CA-4's failure rule, copy-on-write: only this sprite leaves the
|
|
167
|
+
// shared entry, for the untextured one of its shape, with its color.
|
|
168
|
+
if (outcome !== 'failed' || spriteInstanceOf(this.mesh) !== instance)
|
|
169
|
+
return;
|
|
170
|
+
instance.moveTo({ ...instance.key, texture: null });
|
|
171
|
+
instance.color.setHex(this.color);
|
|
172
|
+
}), 'sprite texture settle');
|
|
131
173
|
}
|
|
132
174
|
onDestroy() {
|
|
175
|
+
const instance = spriteInstanceOf(this.mesh);
|
|
176
|
+
if (instance) {
|
|
177
|
+
instance.release();
|
|
178
|
+
this.mesh = undefined;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
133
181
|
this.mesh?.removeFromParent();
|
|
134
182
|
this.mesh?.geometry.dispose();
|
|
135
183
|
// Only this sprite's clone: the cached base lives with the Game.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
2
|
import { sheetCell } from '../animation/sheet.js';
|
|
3
3
|
import { Component } from '../component.js';
|
|
4
4
|
import { projectIsometric } from '../projection.js';
|
|
@@ -283,6 +283,8 @@ export class Tilemap extends Component {
|
|
|
283
283
|
geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(buffers.indices), 1));
|
|
284
284
|
mesh.geometry.dispose();
|
|
285
285
|
mesh.geometry = geometry;
|
|
286
|
+
// WebGPURenderer issues a zero-count draw (WebGLRenderer skipped it): no tile, no draw.
|
|
287
|
+
mesh.visible = buffers.indices.length > 0;
|
|
286
288
|
}
|
|
287
289
|
/** The loaded sheet image's size, or one pixel per sheet cell until it arrives. */
|
|
288
290
|
sheetSize() {
|
package/dist/entity.d.ts
CHANGED
package/dist/entity.js
CHANGED
package/dist/game.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
2
|
import { AssetLoader } from './assets/asset-loader.js';
|
|
3
3
|
import type { TextureBackend } from './assets/texture-backend.js';
|
|
4
4
|
import type { AudioBackend } from './audio/backend.js';
|
|
@@ -11,6 +11,7 @@ import { Emitter } from './events.js';
|
|
|
11
11
|
import { GameTime } from './game-time.js';
|
|
12
12
|
import { Input, type InputBindings } from './input.js';
|
|
13
13
|
import { Pointer } from './pointer.js';
|
|
14
|
+
import { type RenderBackend } from './render-readiness.js';
|
|
14
15
|
import { type SceneJson, type SceneRegistry, type SceneRenderJson } from './scene.js';
|
|
15
16
|
import { type SpatialQuery } from './spatial-query.js';
|
|
16
17
|
import { Stats, type StatValue } from './stats.js';
|
|
@@ -101,8 +102,12 @@ export declare class Game {
|
|
|
101
102
|
* or collisions — the editor's edit mode.
|
|
102
103
|
*/
|
|
103
104
|
simulate: boolean;
|
|
105
|
+
/** WebGPU when the browser offers it, else three's WebGL2 fallback (ADR 0025). */
|
|
104
106
|
private readonly renderer;
|
|
107
|
+
private readonly readiness;
|
|
105
108
|
private readonly resizeObserver;
|
|
109
|
+
/** Scene-scoped Sprite Batches (ADR 0024); `render.batch: false` opts a scene out. */
|
|
110
|
+
private readonly spriteBatches;
|
|
106
111
|
private readonly updateFns;
|
|
107
112
|
private readonly invalidUpdateCompositions;
|
|
108
113
|
/**
|
|
@@ -133,9 +138,20 @@ export declare class Game {
|
|
|
133
138
|
private liveSceneName;
|
|
134
139
|
/** True for the whole extent of a runFrame() call, incl. its tail. */
|
|
135
140
|
private insideFrame;
|
|
141
|
+
/** Set by dispose(): a disposed Game never starts again nor reports its renderer (review #3). */
|
|
142
|
+
private disposed;
|
|
136
143
|
/** A loadSceneByName() enqueued while insideFrame; applied at the next runFrame's start. */
|
|
137
144
|
private pendingSceneLoad;
|
|
138
145
|
constructor(options: GameOptions);
|
|
146
|
+
/**
|
|
147
|
+
* Resolves once the renderer can draw; rejects, naming both Render
|
|
148
|
+
* Backends, when neither initializes. Every call returns the same promise.
|
|
149
|
+
* Frames before it simulate but draw nothing, so a host awaits it before
|
|
150
|
+
* its first frame (ADR 0025).
|
|
151
|
+
*/
|
|
152
|
+
ready(): Promise<void>;
|
|
153
|
+
/** The Render Backend the renderer settled on; null until ready() resolved. */
|
|
154
|
+
get backend(): RenderBackend | null;
|
|
139
155
|
/** Creates a live entity in the scene. */
|
|
140
156
|
spawn(name: string): Entity;
|
|
141
157
|
/** Instantiates a registered prefab after a scene has supplied the registry. */
|
|
@@ -203,6 +219,11 @@ export declare class Game {
|
|
|
203
219
|
setViewHeight(value: number): void;
|
|
204
220
|
/** Shuts the game down completely (loop, input, GPU). */
|
|
205
221
|
dispose(): void;
|
|
222
|
+
/**
|
|
223
|
+
* three installs the loop once its init settled; a failed init is
|
|
224
|
+
* reported by ready(), not by every loop change made before it.
|
|
225
|
+
*/
|
|
226
|
+
private setAnimationLoop;
|
|
206
227
|
/**
|
|
207
228
|
* Real-time playback for the Runtime Bridge: the same clock-driven loop
|
|
208
229
|
* as start(), sharing the accumulator, with `onStep` told after every
|
|
@@ -246,8 +267,6 @@ export declare class Game {
|
|
|
246
267
|
private runHostUpdates;
|
|
247
268
|
private flushPendingSceneLoad;
|
|
248
269
|
private unregisterRuntimeBridge;
|
|
249
|
-
/** Under y-sort, re-derives every participant's z from layer band + entity Y. */
|
|
250
|
-
private applyYSort;
|
|
251
270
|
private renderSurface;
|
|
252
271
|
private componentUpdateSchedule;
|
|
253
272
|
private updateSceneCamera;
|