@bornengine/engine 0.4.16 → 0.5.1
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 +24 -14
- package/native/android/Cargo.lock +577 -28
- package/native/android/Cargo.toml +2 -0
- package/native/android/src/lib.rs +39 -3
- package/native/ios/Cargo.lock +573 -24
- package/native/ios/Cargo.toml +2 -0
- package/native/ios/src/lib.rs +72 -9
- package/native/ios/src/text_input.rs +48 -0
- package/native/linux/Cargo.lock +623 -35
- package/native/linux/Cargo.toml +3 -0
- package/native/macos/Cargo.lock +630 -35
- package/native/macos/Cargo.toml +3 -0
- package/native/shared/Cargo.lock +430 -27
- package/native/shared/Cargo.toml +5 -0
- package/native/shared/build.rs +41 -1
- package/native/shared/src/engine.rs +54 -2
- package/native/shared/src/ffi.rs +54 -1
- package/native/shared/src/ffi_core/mod.rs +2 -0
- package/native/shared/src/ffi_core/ui.rs +182 -0
- package/native/shared/src/input.rs +317 -12
- package/native/shared/src/lib.rs +1 -0
- package/native/shared/src/renderer/mod.rs +47 -201
- package/native/shared/src/renderer/texture_store.rs +5 -0
- package/native/shared/src/renderer/ui_pass.rs +742 -0
- package/native/shared/src/textures.rs +2 -0
- package/native/shared/src/ui/commands.rs +213 -0
- package/native/shared/src/ui/egui.rs +1282 -0
- package/native/shared/src/ui/imgui.rs +1056 -0
- package/native/shared/src/ui/input.rs +173 -0
- package/native/shared/src/ui/mod.rs +252 -0
- package/native/shared/src/ui/responses.rs +61 -0
- package/native/tvos/Cargo.lock +576 -27
- package/native/tvos/Cargo.toml +2 -0
- package/native/visionos/Cargo.lock +571 -22
- package/native/visionos/Cargo.toml +2 -0
- package/native/watchos/src/ffi_stubs.rs +27 -0
- package/native/web/Cargo.lock +346 -11
- package/native/web/Cargo.toml +6 -0
- package/native/web/bloom_glue.js +84 -11
- package/native/web/build.sh +150 -34
- package/native/web/src/lib.rs +2 -0
- package/native/web/src/ui_ffi.rs +148 -0
- package/native/windows/Cargo.lock +638 -38
- package/native/windows/Cargo.toml +3 -0
- package/native/windows/src/lib.rs +50 -1
- package/native/windows/src/windows_text.rs +69 -0
- package/package.json +91 -2
- package/src/debug-ui/index.ts +4 -0
- package/src/debug-ui/types.ts +1 -0
- package/src/game/adapters/audio-source-component.ts +67 -0
- package/src/game/adapters/rigid-body-component.ts +69 -0
- package/src/game/adapters/scene-node-component.ts +49 -0
- package/src/game/game-component.ts +89 -0
- package/src/game/game-object.ts +338 -0
- package/src/game/game-scene.ts +303 -0
- package/src/game/index.ts +7 -0
- package/src/game/transform.ts +315 -0
- package/src/index.ts +27 -0
- package/src/ui/api.ts +240 -0
- package/src/ui/index.ts +5 -0
- package/src/ui/opcodes.ts +66 -0
- package/src/ui/types.ts +82 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { Vec3, Quat } from '../core/types';
|
|
2
|
+
import { mat4Multiply } from '../math';
|
|
3
|
+
import { GameComponent, GameComponentType } from './game-component';
|
|
4
|
+
import {
|
|
5
|
+
decomposeTransformMatrix,
|
|
6
|
+
invertTransformMatrix,
|
|
7
|
+
Transform,
|
|
8
|
+
TransformOptions,
|
|
9
|
+
} from './transform';
|
|
10
|
+
import type { GameScene } from './game-scene';
|
|
11
|
+
|
|
12
|
+
export interface GameObjectOptions extends TransformOptions {
|
|
13
|
+
name?: string;
|
|
14
|
+
active?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ParentOptions {
|
|
18
|
+
preserveWorldTransform?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let nextGameObjectId = 1;
|
|
22
|
+
|
|
23
|
+
function copyVec3(value: Vec3): Vec3 {
|
|
24
|
+
return { x: value.x, y: value.y, z: value.z };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function copyQuat(value: Quat): Quat {
|
|
28
|
+
return { x: value.x, y: value.y, z: value.z, w: value.w };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function removeAt<T>(values: T[], index: number): void {
|
|
32
|
+
for (let current = index; current + 1 < values.length; current++) {
|
|
33
|
+
values[current] = values[current + 1];
|
|
34
|
+
}
|
|
35
|
+
values.pop();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class GameObject {
|
|
39
|
+
readonly id: number;
|
|
40
|
+
readonly transform: Transform;
|
|
41
|
+
name: string;
|
|
42
|
+
active: boolean;
|
|
43
|
+
|
|
44
|
+
private ownerScene: GameScene | null = null;
|
|
45
|
+
private parentObject: GameObject | null = null;
|
|
46
|
+
private childObjects: GameObject[] = [];
|
|
47
|
+
private components: GameComponent[] = [];
|
|
48
|
+
private wasDestroyed = false;
|
|
49
|
+
private isDestroying = false;
|
|
50
|
+
private wasAwake = false;
|
|
51
|
+
private awakeningDepth = 0;
|
|
52
|
+
private wasStarted = false;
|
|
53
|
+
private attachmentGeneration = 0;
|
|
54
|
+
|
|
55
|
+
constructor(options: GameObjectOptions = {}) {
|
|
56
|
+
this.id = nextGameObjectId++;
|
|
57
|
+
this.name = options.name || '';
|
|
58
|
+
this.active = options.active === undefined ? true : options.active;
|
|
59
|
+
this.transform = new Transform({
|
|
60
|
+
position: options.position === undefined ? undefined : copyVec3(options.position),
|
|
61
|
+
rotation: options.rotation === undefined ? undefined : copyQuat(options.rotation),
|
|
62
|
+
scale: options.scale === undefined ? undefined : copyVec3(options.scale),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get activeInHierarchy(): boolean {
|
|
67
|
+
if (this.destroyed || !this.active) return false;
|
|
68
|
+
if (this.parentObject === null) return true;
|
|
69
|
+
return this.parentObject.activeInHierarchy;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get destroyed(): boolean {
|
|
73
|
+
return this.wasDestroyed || this.isDestroying;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get scene(): GameScene | null {
|
|
77
|
+
return this.ownerScene;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get parent(): GameObject | null {
|
|
81
|
+
return this.parentObject;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get children(): readonly GameObject[] {
|
|
85
|
+
return this.childObjects.slice();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onAwake(): void {}
|
|
89
|
+
|
|
90
|
+
onStart(): void {}
|
|
91
|
+
|
|
92
|
+
update(_dt: number): void {}
|
|
93
|
+
|
|
94
|
+
fixedUpdate(_dt: number): void {}
|
|
95
|
+
|
|
96
|
+
onDestroy(): void {}
|
|
97
|
+
|
|
98
|
+
addChild<T extends GameObject>(child: T, options: ParentOptions = {}): T | null {
|
|
99
|
+
if (this.destroyed || child.destroyed || child === this) return null;
|
|
100
|
+
if (child.parentObject === this) return child;
|
|
101
|
+
|
|
102
|
+
let ancestor: GameObject | null = this;
|
|
103
|
+
while (ancestor !== null) {
|
|
104
|
+
if (ancestor === child) return null;
|
|
105
|
+
ancestor = ancestor.parentObject;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const targetScene = this.ownerScene;
|
|
109
|
+
const childScene = child.ownerScene;
|
|
110
|
+
if (childScene !== null && childScene !== targetScene) return null;
|
|
111
|
+
if (targetScene === null && childScene !== null) return null;
|
|
112
|
+
if (targetScene !== null && childScene === null &&
|
|
113
|
+
!targetScene._canAttachSubtree(child)) return null;
|
|
114
|
+
|
|
115
|
+
let nextLocalPosition: Vec3 | null = null;
|
|
116
|
+
let nextLocalRotation: Quat | null = null;
|
|
117
|
+
let nextLocalScale: Vec3 | null = null;
|
|
118
|
+
if (options.preserveWorldTransform !== false) {
|
|
119
|
+
const childWorld = child.transform.worldMatrix;
|
|
120
|
+
const inverseParent = invertTransformMatrix(this.transform.worldMatrix);
|
|
121
|
+
if (inverseParent === null) return null;
|
|
122
|
+
const nextLocalMatrix = mat4Multiply(inverseParent, childWorld);
|
|
123
|
+
const localTRS = decomposeTransformMatrix(nextLocalMatrix);
|
|
124
|
+
if (localTRS === null) return null;
|
|
125
|
+
nextLocalPosition = localTRS.position;
|
|
126
|
+
nextLocalRotation = localTRS.rotation;
|
|
127
|
+
nextLocalScale = localTRS.scale;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const previousParent = child.parentObject;
|
|
131
|
+
if (!child._setParent(this)) return null;
|
|
132
|
+
if (previousParent !== null) {
|
|
133
|
+
const previousIndex = previousParent.childObjects.indexOf(child);
|
|
134
|
+
if (previousIndex >= 0) removeAt(previousParent.childObjects, previousIndex);
|
|
135
|
+
}
|
|
136
|
+
this.childObjects.push(child);
|
|
137
|
+
if (nextLocalPosition !== null && nextLocalRotation !== null &&
|
|
138
|
+
nextLocalScale !== null) {
|
|
139
|
+
child.transform._setLocalTRS(nextLocalPosition, nextLocalRotation, nextLocalScale);
|
|
140
|
+
}
|
|
141
|
+
if (targetScene !== null && childScene === null) {
|
|
142
|
+
targetScene._attachSubtree(child);
|
|
143
|
+
}
|
|
144
|
+
return child;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
removeChild(child: GameObject, options: ParentOptions = {}): boolean {
|
|
148
|
+
if (this.destroyed || child.destroyed || child.parentObject !== this) return false;
|
|
149
|
+
|
|
150
|
+
let nextLocalPosition: Vec3 | null = null;
|
|
151
|
+
let nextLocalRotation: Quat | null = null;
|
|
152
|
+
let nextLocalScale: Vec3 | null = null;
|
|
153
|
+
if (options.preserveWorldTransform !== false) {
|
|
154
|
+
const worldTRS = decomposeTransformMatrix(child.transform.worldMatrix);
|
|
155
|
+
if (worldTRS === null) return false;
|
|
156
|
+
nextLocalPosition = worldTRS.position;
|
|
157
|
+
nextLocalRotation = worldTRS.rotation;
|
|
158
|
+
nextLocalScale = worldTRS.scale;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const index = this.childObjects.indexOf(child);
|
|
162
|
+
if (index < 0 || !child._setParent(null)) return false;
|
|
163
|
+
removeAt(this.childObjects, index);
|
|
164
|
+
if (nextLocalPosition !== null && nextLocalRotation !== null &&
|
|
165
|
+
nextLocalScale !== null) {
|
|
166
|
+
child.transform._setLocalTRS(nextLocalPosition, nextLocalRotation, nextLocalScale);
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
addComponent<T extends GameComponent>(component: T): T | null {
|
|
172
|
+
if (this.destroyed || component.destroyed || component.gameObject !== null) return null;
|
|
173
|
+
if (!component._setGameObject(this)) return null;
|
|
174
|
+
this.components.push(component);
|
|
175
|
+
if (this.wasAwake && this.awakeningDepth === 0 && component._markAwake()) {
|
|
176
|
+
const dynamicComponent: any = component;
|
|
177
|
+
dynamicComponent.onAwake();
|
|
178
|
+
}
|
|
179
|
+
return component;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
getComponent<T extends GameComponent>(type: GameComponentType<T>): T | null {
|
|
183
|
+
for (let index = 0; index < this.components.length; index++) {
|
|
184
|
+
const component = this.components[index];
|
|
185
|
+
if (component instanceof type) return component as T;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getComponents<T extends GameComponent>(type: GameComponentType<T>): T[] {
|
|
191
|
+
const result: T[] = [];
|
|
192
|
+
for (let index = 0; index < this.components.length; index++) {
|
|
193
|
+
const component = this.components[index];
|
|
194
|
+
if (component instanceof type) result.push(component as T);
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
removeComponent(component: GameComponent): boolean {
|
|
200
|
+
if (component.gameObject !== this || !component._beginDestroy()) return false;
|
|
201
|
+
if (component._beginDestroyCallback()) {
|
|
202
|
+
// Perry's statically-known cross-module calls can bypass subclass
|
|
203
|
+
// overrides. Keep this callback dynamic so user component classes dispatch.
|
|
204
|
+
const dynamicComponent: any = component;
|
|
205
|
+
dynamicComponent.onDestroy();
|
|
206
|
+
}
|
|
207
|
+
component._finishDestroy();
|
|
208
|
+
const index = this.components.indexOf(component);
|
|
209
|
+
if (index >= 0) removeAt(this.components, index);
|
|
210
|
+
if (this.ownerScene !== null) this.ownerScene._syncSubtreeAdapters(this);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
destroy(): boolean {
|
|
215
|
+
if (!this._beginDestroy()) return false;
|
|
216
|
+
this._markDescendantsDestroying();
|
|
217
|
+
this._destroyMarkedSubtree();
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** @internal Links the transform hierarchy after GameObject validation. */
|
|
222
|
+
_setParent(parent: GameObject | null): boolean {
|
|
223
|
+
let current = parent;
|
|
224
|
+
while (current !== null) {
|
|
225
|
+
if (current === this) return false;
|
|
226
|
+
current = current.parentObject;
|
|
227
|
+
}
|
|
228
|
+
if (!this.transform._setParent(parent === null ? null : parent.transform)) return false;
|
|
229
|
+
this.parentObject = parent;
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @internal Assigns scene ownership to the object. */
|
|
234
|
+
_setScene(scene: GameScene | null): void {
|
|
235
|
+
this.ownerScene = scene;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** @internal Returns a stable component list for scene dispatch. */
|
|
239
|
+
_componentsSnapshot(): GameComponent[] {
|
|
240
|
+
return this.components.slice();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** @internal Returns a stable child list for scene traversal. */
|
|
244
|
+
_childrenSnapshot(): GameObject[] {
|
|
245
|
+
return this.childObjects.slice();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** @internal Marks the current scene attachment generation. */
|
|
249
|
+
_setAttachmentGeneration(generation: number): void {
|
|
250
|
+
this.attachmentGeneration = generation;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @internal Reads the generation captured in a dispatch snapshot. */
|
|
254
|
+
_getAttachmentGeneration(): number {
|
|
255
|
+
return this.attachmentGeneration;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** @internal Marks awake once per object lifetime. */
|
|
259
|
+
_markAwake(): boolean {
|
|
260
|
+
if (this.wasAwake) return false;
|
|
261
|
+
this.wasAwake = true;
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** @internal Tracks the hierarchy traversal that dispatches awake callbacks. */
|
|
266
|
+
_beginAwakening(): void {
|
|
267
|
+
this.awakeningDepth++;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** @internal Ends one hierarchy traversal that dispatches awake callbacks. */
|
|
271
|
+
_endAwakening(): void {
|
|
272
|
+
if (this.awakeningDepth > 0) this.awakeningDepth--;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** @internal Marks start once per object lifetime. */
|
|
276
|
+
_markStarted(): boolean {
|
|
277
|
+
if (this.wasStarted) return false;
|
|
278
|
+
this.wasStarted = true;
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** @internal Starts the object's exactly-once destruction path. */
|
|
283
|
+
_beginDestroy(): boolean {
|
|
284
|
+
if (this.wasDestroyed || this.isDestroying) return false;
|
|
285
|
+
this.isDestroying = true;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** @internal Completes destruction after all hooks return. */
|
|
290
|
+
_finishDestroy(): void {
|
|
291
|
+
this.wasDestroyed = true;
|
|
292
|
+
this.isDestroying = false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private _markDescendantsDestroying(): void {
|
|
296
|
+
const components = this.components.slice();
|
|
297
|
+
for (let index = 0; index < components.length; index++) {
|
|
298
|
+
components[index]._beginDestroy();
|
|
299
|
+
}
|
|
300
|
+
const children = this.childObjects.slice();
|
|
301
|
+
for (let index = 0; index < children.length; index++) {
|
|
302
|
+
if (children[index]._beginDestroy()) {
|
|
303
|
+
children[index]._markDescendantsDestroying();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private _destroyMarkedSubtree(): void {
|
|
309
|
+
const children = this.childObjects.slice();
|
|
310
|
+
for (let index = 0; index < children.length; index++) {
|
|
311
|
+
children[index]._destroyMarkedSubtree();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const dynamicObject: any = this;
|
|
315
|
+
dynamicObject.onDestroy();
|
|
316
|
+
|
|
317
|
+
const components = this.components.slice();
|
|
318
|
+
for (let index = components.length - 1; index >= 0; index--) {
|
|
319
|
+
const component = components[index];
|
|
320
|
+
if (component._beginDestroyCallback()) {
|
|
321
|
+
const dynamicComponent: any = component;
|
|
322
|
+
dynamicComponent.onDestroy();
|
|
323
|
+
}
|
|
324
|
+
component._finishDestroy();
|
|
325
|
+
const componentIndex = this.components.indexOf(component);
|
|
326
|
+
if (componentIndex >= 0) removeAt(this.components, componentIndex);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const previousParent = this.parentObject;
|
|
330
|
+
if (previousParent !== null) {
|
|
331
|
+
const childIndex = previousParent.childObjects.indexOf(this);
|
|
332
|
+
if (childIndex >= 0) removeAt(previousParent.childObjects, childIndex);
|
|
333
|
+
}
|
|
334
|
+
this._setParent(null);
|
|
335
|
+
if (this.ownerScene !== null) this.ownerScene._removeDestroyedObject(this);
|
|
336
|
+
this._finishDestroy();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { GameComponent } from './game-component';
|
|
2
|
+
import { GameObject } from './game-object';
|
|
3
|
+
import type { WorldHandle } from '../physics';
|
|
4
|
+
|
|
5
|
+
function removeAt<T>(values: T[], index: number): void {
|
|
6
|
+
for (let current = index; current + 1 < values.length; current++) {
|
|
7
|
+
values[current] = values[current + 1];
|
|
8
|
+
}
|
|
9
|
+
values.pop();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class GameScene {
|
|
13
|
+
private sceneObjects: GameObject[] = [];
|
|
14
|
+
private pendingAwakeRoots: GameObject[] = [];
|
|
15
|
+
private isAwakening = false;
|
|
16
|
+
private nextAttachmentGeneration = 1;
|
|
17
|
+
private wasDestroyed = false;
|
|
18
|
+
|
|
19
|
+
get objects(): readonly GameObject[] {
|
|
20
|
+
return this.sceneObjects.slice();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
add<T extends GameObject>(object: T): T | null {
|
|
24
|
+
if (this.wasDestroyed || object.destroyed || object.scene !== null || object.parent !== null ||
|
|
25
|
+
!this._canAttachSubtree(object)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return this._attachSubtree(object) ? object : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
remove(object: GameObject): boolean {
|
|
32
|
+
if (this.wasDestroyed || object.destroyed || object.scene !== this) return false;
|
|
33
|
+
if (object.parent !== null && !object.parent.removeChild(object)) return false;
|
|
34
|
+
|
|
35
|
+
const subtree = this._collectSubtree(object);
|
|
36
|
+
for (let index = this.sceneObjects.length - 1; index >= 0; index--) {
|
|
37
|
+
if (subtree.indexOf(this.sceneObjects[index]) >= 0) {
|
|
38
|
+
removeAt(this.sceneObjects, index);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (let index = 0; index < subtree.length; index++) {
|
|
42
|
+
subtree[index]._setScene(null);
|
|
43
|
+
}
|
|
44
|
+
this._syncSubtreeAdapters(object);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
update(dt: number): void {
|
|
49
|
+
if (this.wasDestroyed) return;
|
|
50
|
+
const objects = this.sceneObjects.slice();
|
|
51
|
+
const generations: number[] = [];
|
|
52
|
+
const componentSnapshots: GameComponent[][] = [];
|
|
53
|
+
for (let index = 0; index < objects.length; index++) {
|
|
54
|
+
generations.push(objects[index]._getAttachmentGeneration());
|
|
55
|
+
componentSnapshots.push(objects[index]._componentsSnapshot());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex++) {
|
|
59
|
+
const object = objects[objectIndex];
|
|
60
|
+
const generation = generations[objectIndex];
|
|
61
|
+
if (!this._isEligibleObject(object, generation)) continue;
|
|
62
|
+
|
|
63
|
+
if (object._markStarted()) {
|
|
64
|
+
const dynamicObject: any = object;
|
|
65
|
+
dynamicObject.onStart();
|
|
66
|
+
}
|
|
67
|
+
if (this._isEligibleObject(object, generation)) {
|
|
68
|
+
const dynamicObject: any = object;
|
|
69
|
+
dynamicObject.update(dt);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const components = componentSnapshots[objectIndex];
|
|
73
|
+
for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
|
|
74
|
+
const component = components[componentIndex];
|
|
75
|
+
if (!this._isEligibleComponent(object, component, generation)) continue;
|
|
76
|
+
if (component._markStarted()) {
|
|
77
|
+
const dynamicComponent: any = component;
|
|
78
|
+
dynamicComponent.onStart();
|
|
79
|
+
}
|
|
80
|
+
if (this._isEligibleComponent(object, component, generation)) {
|
|
81
|
+
const dynamicComponent: any = component;
|
|
82
|
+
dynamicComponent.update(dt);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this._syncRuntimeAdapters();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
updateFixed(fixedDt: number): void {
|
|
90
|
+
if (this.wasDestroyed) return;
|
|
91
|
+
const objects = this.sceneObjects.slice();
|
|
92
|
+
const generations: number[] = [];
|
|
93
|
+
const componentSnapshots: GameComponent[][] = [];
|
|
94
|
+
for (let index = 0; index < objects.length; index++) {
|
|
95
|
+
generations.push(objects[index]._getAttachmentGeneration());
|
|
96
|
+
componentSnapshots.push(objects[index]._componentsSnapshot());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex++) {
|
|
100
|
+
const object = objects[objectIndex];
|
|
101
|
+
const generation = generations[objectIndex];
|
|
102
|
+
if (!this._isEligibleObject(object, generation)) continue;
|
|
103
|
+
|
|
104
|
+
const dynamicObject: any = object;
|
|
105
|
+
dynamicObject.fixedUpdate(fixedDt);
|
|
106
|
+
|
|
107
|
+
const components = componentSnapshots[objectIndex];
|
|
108
|
+
for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
|
|
109
|
+
const component = components[componentIndex];
|
|
110
|
+
if (!this._isEligibleComponent(object, component, generation)) continue;
|
|
111
|
+
const dynamicComponent: any = component;
|
|
112
|
+
dynamicComponent.fixedUpdate(fixedDt);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this._syncRuntimeAdapters();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
syncPhysicsBeforeStep(world: WorldHandle, fixedDt: number): void {
|
|
119
|
+
if (this.wasDestroyed) return;
|
|
120
|
+
const objects = this.sceneObjects.slice();
|
|
121
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex++) {
|
|
122
|
+
const object = objects[objectIndex];
|
|
123
|
+
if (object.scene !== this || object.destroyed) continue;
|
|
124
|
+
const components = object._componentsSnapshot();
|
|
125
|
+
for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
|
|
126
|
+
const component = components[componentIndex];
|
|
127
|
+
if (component.gameObject !== object || component.destroyed) continue;
|
|
128
|
+
const dynamicComponent: any = component;
|
|
129
|
+
dynamicComponent._syncPhysicsBeforeStep(world, fixedDt);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
syncPhysicsAfterStep(world: WorldHandle): void {
|
|
135
|
+
if (this.wasDestroyed) return;
|
|
136
|
+
const objects = this.sceneObjects.slice();
|
|
137
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex++) {
|
|
138
|
+
const object = objects[objectIndex];
|
|
139
|
+
if (object.scene !== this || object.destroyed) continue;
|
|
140
|
+
const components = object._componentsSnapshot();
|
|
141
|
+
for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
|
|
142
|
+
const component = components[componentIndex];
|
|
143
|
+
if (component.gameObject !== object || component.destroyed) continue;
|
|
144
|
+
const dynamicComponent: any = component;
|
|
145
|
+
dynamicComponent._syncPhysicsAfterStep(world);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this._syncRuntimeAdapters();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
destroy(): void {
|
|
152
|
+
if (this.wasDestroyed) return;
|
|
153
|
+
this.wasDestroyed = true;
|
|
154
|
+
const objects = this.sceneObjects.slice();
|
|
155
|
+
for (let index = 0; index < objects.length; index++) {
|
|
156
|
+
const object = objects[index];
|
|
157
|
+
if (object.scene === this && object.parent === null) object.destroy();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @internal Validates a detached subtree before an atomic scene attachment. */
|
|
162
|
+
_canAttachSubtree(root: GameObject): boolean {
|
|
163
|
+
const subtree = this._collectSubtree(root);
|
|
164
|
+
for (let index = 0; index < subtree.length; index++) {
|
|
165
|
+
if (subtree[index].destroyed || subtree[index].scene !== null) return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @internal Attaches a detached subtree in parent-first insertion order. */
|
|
171
|
+
_attachSubtree(root: GameObject): boolean {
|
|
172
|
+
if (this.wasDestroyed || !this._canAttachSubtree(root)) return false;
|
|
173
|
+
const subtree = this._collectSubtree(root);
|
|
174
|
+
for (let index = 0; index < subtree.length; index++) {
|
|
175
|
+
const object = subtree[index];
|
|
176
|
+
object._setScene(this);
|
|
177
|
+
object._setAttachmentGeneration(this.nextAttachmentGeneration++);
|
|
178
|
+
this.sceneObjects.push(object);
|
|
179
|
+
}
|
|
180
|
+
if (this.isAwakening) {
|
|
181
|
+
this.pendingAwakeRoots.push(root);
|
|
182
|
+
} else {
|
|
183
|
+
this.isAwakening = true;
|
|
184
|
+
this._awakenSubtree(root);
|
|
185
|
+
let pendingIndex = 0;
|
|
186
|
+
while (pendingIndex < this.pendingAwakeRoots.length) {
|
|
187
|
+
this._awakenSubtree(this.pendingAwakeRoots[pendingIndex]);
|
|
188
|
+
pendingIndex++;
|
|
189
|
+
}
|
|
190
|
+
this.pendingAwakeRoots.length = 0;
|
|
191
|
+
this.isAwakening = false;
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** @internal Removes an object after all of its destruction callbacks return. */
|
|
197
|
+
_removeDestroyedObject(object: GameObject): void {
|
|
198
|
+
const index = this.sceneObjects.indexOf(object);
|
|
199
|
+
if (index >= 0) removeAt(this.sceneObjects, index);
|
|
200
|
+
object._setScene(null);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @internal Synchronizes renderer nodes in a detached hierarchy. */
|
|
204
|
+
_syncSubtreeAdapters(root: GameObject): void {
|
|
205
|
+
const subtree = this._collectSubtree(root);
|
|
206
|
+
for (let index = 0; index < subtree.length; index++) {
|
|
207
|
+
if (!subtree[index].destroyed) this._syncObjectAdapters(subtree[index]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private _awakenSubtree(object: GameObject): void {
|
|
212
|
+
if (object.destroyed || object.scene !== this) return;
|
|
213
|
+
object._beginAwakening();
|
|
214
|
+
if (object._markAwake()) {
|
|
215
|
+
const dynamicObject: any = object;
|
|
216
|
+
dynamicObject.onAwake();
|
|
217
|
+
}
|
|
218
|
+
if (object.destroyed || object.scene !== this) {
|
|
219
|
+
object._endAwakening();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this._awakenComponents(object);
|
|
224
|
+
if (object.destroyed || object.scene !== this) {
|
|
225
|
+
object._endAwakening();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const children = object._childrenSnapshot();
|
|
230
|
+
for (let index = 0; index < children.length; index++) {
|
|
231
|
+
this._awakenSubtree(children[index]);
|
|
232
|
+
}
|
|
233
|
+
if (!object.destroyed && object.scene === this) {
|
|
234
|
+
this._awakenComponents(object);
|
|
235
|
+
}
|
|
236
|
+
object._endAwakening();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private _awakenComponents(object: GameObject): void {
|
|
240
|
+
while (!object.destroyed && object.scene === this) {
|
|
241
|
+
const components = object._componentsSnapshot();
|
|
242
|
+
let next: GameComponent | null = null;
|
|
243
|
+
for (let index = 0; index < components.length; index++) {
|
|
244
|
+
const component = components[index];
|
|
245
|
+
if (component.gameObject !== object || component.destroyed) continue;
|
|
246
|
+
if (component._markAwake()) {
|
|
247
|
+
next = component;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (next === null) return;
|
|
252
|
+
const dynamicComponent: any = next;
|
|
253
|
+
dynamicComponent.onAwake();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private _isEligibleObject(object: GameObject, generation: number): boolean {
|
|
258
|
+
return object.scene === this && !object.destroyed &&
|
|
259
|
+
object._getAttachmentGeneration() === generation && object.activeInHierarchy;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private _isEligibleComponent(
|
|
263
|
+
object: GameObject,
|
|
264
|
+
component: GameComponent,
|
|
265
|
+
generation: number,
|
|
266
|
+
): boolean {
|
|
267
|
+
return this._isEligibleObject(object, generation) &&
|
|
268
|
+
component.gameObject === object && !component.destroyed && component.enabled &&
|
|
269
|
+
object.activeInHierarchy;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private _syncRuntimeAdapters(): void {
|
|
273
|
+
for (let index = 0; index < this.sceneObjects.length; index++) {
|
|
274
|
+
const object = this.sceneObjects[index];
|
|
275
|
+
if (!object.destroyed && object.scene === this) {
|
|
276
|
+
this._syncObjectAdapters(object);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private _syncObjectAdapters(object: GameObject): void {
|
|
282
|
+
const components = object._componentsSnapshot();
|
|
283
|
+
for (let index = 0; index < components.length; index++) {
|
|
284
|
+
const component = components[index];
|
|
285
|
+
if (component.gameObject !== object || component.destroyed) continue;
|
|
286
|
+
const dynamicComponent: any = component;
|
|
287
|
+
dynamicComponent._syncRuntimeAfterPhase();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private _collectSubtree(root: GameObject): GameObject[] {
|
|
292
|
+
const result: GameObject[] = [];
|
|
293
|
+
const visit = (object: GameObject): void => {
|
|
294
|
+
result.push(object);
|
|
295
|
+
const children = object._childrenSnapshot();
|
|
296
|
+
for (let index = 0; index < children.length; index++) {
|
|
297
|
+
visit(children[index]);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
visit(root);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './game-component';
|
|
2
|
+
export * from './game-object';
|
|
3
|
+
export * from './game-scene';
|
|
4
|
+
export * from './transform';
|
|
5
|
+
export * from './adapters/scene-node-component';
|
|
6
|
+
export * from './adapters/rigid-body-component';
|
|
7
|
+
export * from './adapters/audio-source-component';
|