lecodes-cli 0.18.2 → 0.19.2
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/index.js +1866 -301
- package/package.json +14 -12
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +891 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +326 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +448 -0
- package/src/commands/appTemplates.ts +883 -0
- package/src/commands/appTemplatesAndroid.ts +599 -0
- package/src/commands/appTemplatesGradlew.ts +9 -0
- package/src/commands/assets.ts +28 -0
- package/src/commands/clone.ts +60 -0
- package/src/commands/compile.ts +253 -0
- package/src/commands/create.ts +59 -0
- package/src/commands/design.ts +863 -0
- package/src/commands/designTemplates.ts +11 -0
- package/src/commands/desktop.ts +122 -0
- package/src/commands/dev.ts +214 -0
- package/src/commands/diff.ts +59 -0
- package/src/commands/init.ts +191 -0
- package/src/commands/install.ts +198 -0
- package/src/commands/lightmap.ts +290 -0
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/navmesh.ts +225 -0
- package/src/commands/pn.ts +305 -0
- package/src/commands/projectTemplates.ts +129 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +414 -0
- package/src/commands/scene.ts +148 -0
- package/src/commands/shaders.ts +157 -0
- package/src/commands/status.ts +29 -0
- package/src/commands/test.ts +370 -0
- package/src/commands/thumbs.ts +178 -0
- package/src/commands/types.ts +87 -0
- package/src/commands/update.ts +190 -0
- package/src/compile/assetIcons.ts +214 -0
- package/src/compile/collect.ts +46 -0
- package/src/compile/collectLocal.ts +37 -0
- package/src/compile/designCompile.ts +104 -0
- package/src/compile/fonts.ts +156 -0
- package/src/compile/headlessBundle.ts +129 -0
- package/src/compile/nativeStack.ts +21 -0
- package/src/compile/projectCompile.ts +109 -0
- package/src/compile/sceneCompile.ts +113 -0
- package/src/compile/screenEntry.ts +127 -0
- package/src/compile/shaders.ts +245 -0
- package/src/config.ts +42 -0
- package/src/designMeta.ts +35 -0
- package/src/desktopRenderer.ts +532 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +292 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +491 -0
- package/src/lecodes-3d-editor.d.ts +41 -0
- package/src/lecodes-assets.d.ts +7 -0
- package/src/lecodes-design.d.ts +191 -0
- package/src/lecodes-renderer.d.ts +131 -0
- package/src/localFiles.ts +144 -0
- package/src/manifest.ts +46 -0
- package/src/matcTool.ts +137 -0
- package/src/peers.ts +40 -0
- package/src/project.ts +61 -0
- package/src/projectEnv.ts +94 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/releases.ts +125 -0
- package/src/serverDiff.ts +78 -0
- package/src/textDiff.ts +103 -0
- package/src/types.ts +0 -0
- package/src/util.ts +146 -0
- package/runtime/sdk/animate/animate.ts +0 -238
- package/runtime/sdk/animate/bezier.ts +0 -138
- package/runtime/sdk/animate/easings.ts +0 -126
- package/runtime/sdk/canvas/Canvas.ts +0 -305
- package/runtime/sdk/core/Aspect.ts +0 -512
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- package/runtime/sdk/core/color.ts +0 -66
- package/runtime/sdk/core/compWrite.ts +0 -42
- package/runtime/sdk/core/events.ts +0 -38
- package/runtime/sdk/core/fields.ts +0 -120
- package/runtime/sdk/core/registry.ts +0 -23
- package/runtime/sdk/core/signals.ts +0 -277
- package/runtime/sdk/core/time.ts +0 -81
- package/runtime/sdk/g2/Camera2D.ts +0 -40
- package/runtime/sdk/g2/CharacterController2D.ts +0 -276
- package/runtime/sdk/g2/Node2D.ts +0 -267
- package/runtime/sdk/g2/OneWay2D.ts +0 -66
- package/runtime/sdk/g2/Physics2D.ts +0 -346
- package/runtime/sdk/g2/Scene2D.ts +0 -209
- package/runtime/sdk/g2/Shape2D.ts +0 -259
- package/runtime/sdk/g2/Sprite.ts +0 -89
- package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
- package/runtime/sdk/g2/SpriteSheet.ts +0 -166
- package/runtime/sdk/g2/Texture2D.ts +0 -47
- package/runtime/sdk/g2/Tilemap.ts +0 -41
- package/runtime/sdk/g2/Tileset.ts +0 -71
- package/runtime/sdk/g2/Trigger2D.ts +0 -77
- package/runtime/sdk/g2/autotile.ts +0 -433
- package/runtime/sdk/g2/cells.ts +0 -91
- package/runtime/sdk/g2/defineScene2d.ts +0 -381
- package/runtime/sdk/g2/groups2d.ts +0 -106
- package/runtime/sdk/g2/loop.ts +0 -50
- package/runtime/sdk/g2/scenarios2d.ts +0 -69
- package/runtime/sdk/g2/touch.ts +0 -83
- package/runtime/sdk/gl/Camera.ts +0 -160
- package/runtime/sdk/gl/CameraPlace.ts +0 -52
- package/runtime/sdk/gl/CharacterController.ts +0 -238
- package/runtime/sdk/gl/Gearbox.ts +0 -212
- package/runtime/sdk/gl/Geometry.ts +0 -279
- package/runtime/sdk/gl/IK.ts +0 -193
- package/runtime/sdk/gl/InstancedMesh.ts +0 -132
- package/runtime/sdk/gl/Light.ts +0 -99
- package/runtime/sdk/gl/Lightmap.ts +0 -179
- package/runtime/sdk/gl/Material.ts +0 -245
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -64
- package/runtime/sdk/gl/Node.ts +0 -350
- package/runtime/sdk/gl/Noise.ts +0 -30
- package/runtime/sdk/gl/Particles.ts +0 -676
- package/runtime/sdk/gl/Physics.ts +0 -222
- package/runtime/sdk/gl/Plane.ts +0 -53
- package/runtime/sdk/gl/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -479
- package/runtime/sdk/gl/Shape.ts +0 -377
- package/runtime/sdk/gl/Texture.ts +0 -46
- package/runtime/sdk/gl/Trigger.ts +0 -45
- package/runtime/sdk/gl/Vehicle.ts +0 -473
- package/runtime/sdk/gl/Wheel.ts +0 -240
- package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
- package/runtime/sdk/gl/animation/Animator.ts +0 -87
- package/runtime/sdk/gl/animation/Layer.ts +0 -29
- package/runtime/sdk/gl/animation/Loop.ts +0 -25
- package/runtime/sdk/gl/animation/Playback.ts +0 -43
- package/runtime/sdk/gl/animation/core.ts +0 -294
- package/runtime/sdk/gl/controls.ts +0 -95
- package/runtime/sdk/gl/physicsEvents.ts +0 -20
- package/runtime/sdk/gl/scenarios.ts +0 -291
- package/runtime/sdk/gl/state.ts +0 -6
- package/runtime/sdk/gl/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -186
- package/runtime/sdk/math/Mathf.ts +0 -118
- package/runtime/sdk/math/mat4.ts +0 -278
- package/runtime/sdk/math/quat.ts +0 -232
- package/runtime/sdk/math/vec.ts +0 -255
- package/runtime/sdk/plugins/camera.ts +0 -81
- package/runtime/sdk/plugins/geolocation.ts +0 -123
- package/runtime/sdk/plugins/oauth.ts +0 -61
- package/runtime/sdk/plugins/permission.ts +0 -7
- package/runtime/sdk/plugins/push.ts +0 -132
- package/runtime/sdk/plugins/qr.ts +0 -73
- package/runtime/sdk/plugins/service.ts +0 -47
- package/runtime/sdk/runtime/app.ts +0 -101
- package/runtime/sdk/runtime/appEvents.ts +0 -54
- package/runtime/sdk/runtime/channel.ts +0 -61
- package/runtime/sdk/runtime/clipboard.ts +0 -20
- package/runtime/sdk/runtime/datetime.ts +0 -329
- package/runtime/sdk/runtime/device.ts +0 -293
- package/runtime/sdk/runtime/fetch.ts +0 -77
- package/runtime/sdk/runtime/files.ts +0 -23
- package/runtime/sdk/runtime/input.ts +0 -175
- package/runtime/sdk/runtime/media.ts +0 -111
- package/runtime/sdk/runtime/misc.ts +0 -16
- package/runtime/sdk/runtime/net.ts +0 -36
- package/runtime/sdk/runtime/rpc.ts +0 -218
- package/runtime/sdk/runtime/service.ts +0 -83
- package/runtime/sdk/runtime/share.ts +0 -9
- package/runtime/sdk/runtime/storage.ts +0 -13
- package/runtime/sdk/runtime/touch.ts +0 -76
- package/runtime/sdk/scene/defineScene.ts +0 -1227
- package/runtime/sdk/scene/editorPlugins.ts +0 -92
- package/runtime/sdk/scene/gizmos.ts +0 -148
- package/runtime/sdk/scene/grammar.ts +0 -120
- package/runtime/sdk/scene/material.ts +0 -188
- package/runtime/sdk/server/auth/appConfig.ts +0 -12
- package/runtime/sdk/server/auth/global.ts +0 -80
- package/runtime/sdk/server/auth/host.ts +0 -318
- package/runtime/sdk/server/auth/models.ts +0 -83
- package/runtime/sdk/server/auth/types.ts +0 -50
- package/runtime/sdk/server/channel.ts +0 -56
- package/runtime/sdk/server/context.ts +0 -36
- package/runtime/sdk/server/db/defineDb.ts +0 -237
- package/runtime/sdk/server/db/fields.ts +0 -132
- package/runtime/sdk/server/db/httpTransport.ts +0 -93
- package/runtime/sdk/server/db/index.ts +0 -7
- package/runtime/sdk/server/db/marci/query.ts +0 -412
- package/runtime/sdk/server/db/types.ts +0 -202
- package/runtime/sdk/server/errors.ts +0 -12
- package/runtime/sdk/server/host.ts +0 -74
- package/runtime/sdk/server/inject.ts +0 -13
- package/runtime/sdk/server/runtime.ts +0 -133
- package/runtime/sdk/server/validate.ts +0 -87
- package/runtime/sdk/ui/NativeView.ts +0 -142
- package/runtime/sdk/ui/UI.ts +0 -39
- package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
- package/runtime/sdk/ui/UIButton.ts +0 -101
- package/runtime/sdk/ui/UIContainer.ts +0 -60
- package/runtime/sdk/ui/UIImage.ts +0 -83
- package/runtime/sdk/ui/UIInput.ts +0 -185
- package/runtime/sdk/ui/UIModal.ts +0 -139
- package/runtime/sdk/ui/UINode.ts +0 -826
- package/runtime/sdk/ui/UIPager.ts +0 -362
- package/runtime/sdk/ui/UIPopover.ts +0 -100
- package/runtime/sdk/ui/UIScreen.ts +0 -123
- package/runtime/sdk/ui/UIScrollable.ts +0 -87
- package/runtime/sdk/ui/UISpacer.ts +0 -14
- package/runtime/sdk/ui/UITabs.ts +0 -236
- package/runtime/sdk/ui/UIText.ts +0 -51
- package/runtime/sdk/ui/UIVideo.ts +0 -88
- package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
- package/runtime/sdk/ui/UIWidget.ts +0 -127
- package/runtime/sdk/ui/fonts.ts +0 -13
- package/runtime/sdk/ui/presentable.ts +0 -117
- package/runtime/sdk/ui/router.ts +0 -132
- package/runtime/sdk/ui/theme.ts +0 -84
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
// The Aspect system: attachable capabilities (animation, collider, physics, and user behaviors)
|
|
2
|
-
// that hang off a node WITHOUT bloating the node class. Composition with the terseness of methods.
|
|
3
|
-
//
|
|
4
|
-
// const hero = new Sprite({ texture })
|
|
5
|
-
// .aspect(SpriteAnimation, { fps: 8, clips: { walk: [1, 2, 3] } }) // attach + configure
|
|
6
|
-
// .aspect(Shape2D, { box: [16, 8] }) // chains: returns the node
|
|
7
|
-
// hero.anim.play('walk') // access by name (the chain result is typed as having it)
|
|
8
|
-
// if (hero.has(Physics)) hero.physics.velocity = [0, 0] // has() is a type guard
|
|
9
|
-
//
|
|
10
|
-
// A user aspect declares its name + target node in the generic and reads `this.node`:
|
|
11
|
-
// class Health extends Aspect<'health', Sprite> { hp = 100; hurt(n: number) { this.hp -= n } }
|
|
12
|
-
// sprite.aspect(Health, { hp: 80 }).health.hurt(10)
|
|
13
|
-
//
|
|
14
|
-
// SYSTEMS are the same thing attached to a SCENE instead of a node — game logic that has no single
|
|
15
|
-
// node to live on (input mapping, FX pools, the HUD, the game mode): `scene.system(Fx)` → `scene.fx`.
|
|
16
|
-
// They share the lifecycle, the update phases, the ordering and the events of aspects (see System).
|
|
17
|
-
//
|
|
18
|
-
// The accessor name lives in the generic (Aspect<'name', Node>). The `chisel` bundler extracts it at
|
|
19
|
-
// compile time and injects the runtime `static aspect` on user aspects + the virtual type accessor;
|
|
20
|
-
// the SDK's own built-in aspects declare `static readonly aspect` directly (see SpriteAnimation etc.).
|
|
21
|
-
|
|
22
|
-
import type { Scene } from "../gl/Scene"
|
|
23
|
-
import { Emitter, type EventMap } from "./events"
|
|
24
|
-
import type { InspectorUI } from "./InspectorUI"
|
|
25
|
-
import type { Node } from "../gl/Node"
|
|
26
|
-
import { Time, _registerTimeScaleSink } from "./time"
|
|
27
|
-
|
|
28
|
-
// ---- type helpers -----------------------------------------------------------
|
|
29
|
-
/** The class object of an aspect — accepted even when its constructor is protected/abstract. */
|
|
30
|
-
export type AspectCtor<A extends Aspect<any, any, any>> = abstract new () => A
|
|
31
|
-
/** The accessor name carried in the aspect's generic. */
|
|
32
|
-
export type KeyOf<A> = A extends Aspect<infer K, any, any> ? K : never
|
|
33
|
-
/** The node kind an aspect targets (the scene kind for a System). */
|
|
34
|
-
export type TargetOf<A> = A extends Aspect<any, infer P, any> ? P : never
|
|
35
|
-
/** `{ name: Aspect }` — the field an aspect contributes to its node. */
|
|
36
|
-
export type FieldOf<A extends Aspect<any, any, any>> = { [P in KeyOf<A>]: A }
|
|
37
|
-
|
|
38
|
-
type UnionToIntersection<U> =
|
|
39
|
-
(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A variable typed as a node KNOWN to carry the given aspect(s). Union, not a tuple — reads as
|
|
43
|
-
* English and mirrors the runtime guard `node.has(Health)`:
|
|
44
|
-
* let boss: With<Sprite, Health | Physics>
|
|
45
|
-
*/
|
|
46
|
-
export type With<N, A extends Aspect<any, any, any>> =
|
|
47
|
-
N & UnionToIntersection<A extends any ? FieldOf<A> : never>
|
|
48
|
-
|
|
49
|
-
// ---- per-frame update dispatch (two phases) ---------------------------------
|
|
50
|
-
// Aspects tick every frame in one of two render-synced phases, chosen by WHICH METHOD they define:
|
|
51
|
-
// • `update(dt)` — LATE: after the physics step + transform sync + animators, right before the frame
|
|
52
|
-
// draws. Reads of node.worldPosition are the FINAL drawn position — cameras/followers land exactly,
|
|
53
|
-
// what you write to a plain node is what this frame shows. The default home for game logic.
|
|
54
|
-
// • `updateBefore(dt)` — EARLY: before the physics step, so writes of velocity / move commands /
|
|
55
|
-
// kinematic transforms are consumed by the SAME frame's step (zero input latency).
|
|
56
|
-
// An aspect rarely needs both; when a behaviour does (feed the sim, then pose something after the
|
|
57
|
-
// animators), it is two aspects on the same node — not one class in two phases.
|
|
58
|
-
//
|
|
59
|
-
// The FRAME SOURCE is pluggable so core never names a specific engine bridge (a 3D-only bundle must not
|
|
60
|
-
// pull in the 2D one): each engine layer installs a render-synced source via _installAspectFrames (the
|
|
61
|
-
// native early/late hooks), so dispatch is in lockstep with the draw. With none installed (a runtime
|
|
62
|
-
// without the hooks) it falls back to a single host setLoop, early-then-late — no true post-physics slot.
|
|
63
|
-
//
|
|
64
|
-
// ORDER within a phase: declared constraints first (`static after = [Controls]` / `static before`),
|
|
65
|
-
// then the numeric `updateOrder` (ascending, default 0), then attach order. Constraints are edges between
|
|
66
|
-
// classes: an aspect ranks after everything it names in `after` and after everything that names it in
|
|
67
|
-
// `before`; a class with no constraints has rank 0. `updateOrder` only breaks ties inside a rank — so a
|
|
68
|
-
// declared dependency always wins over a number.
|
|
69
|
-
//
|
|
70
|
-
// `dt` is GAME time (core/time.ts): scaled by Time.scale, 0 while Time.paused — and a paused frame skips
|
|
71
|
-
// every updater except those with `updateWhilePaused = true`. Lazy: the first update-bearing aspect installs
|
|
72
|
-
// the dispatch; a project with none starts nothing. No visibility culling yet.
|
|
73
|
-
// The dispatcher's view of an instance: the lifecycle members are `protected` on Aspect (the engine calls
|
|
74
|
-
// them, game code never does), so core reaches them through this structural type.
|
|
75
|
-
interface Updatable {
|
|
76
|
-
_phaseSeq: number
|
|
77
|
-
_phases: number
|
|
78
|
-
updateOrder: number
|
|
79
|
-
updateWhilePaused: boolean
|
|
80
|
-
update?(dt: number): void
|
|
81
|
-
updateBefore?(dt: number): void
|
|
82
|
-
onAttach?(): void
|
|
83
|
-
onDetach?(): void
|
|
84
|
-
onReconfigure?(): void
|
|
85
|
-
}
|
|
86
|
-
const internals = (a: Aspect<any, any, any>): Updatable => a as unknown as Updatable
|
|
87
|
-
type PhaseFn = (dt: number) => void
|
|
88
|
-
type Ctor = Function & { after?: Function[], before?: Function[], aspect?: string }
|
|
89
|
-
|
|
90
|
-
// A render-synced frame source: given the two phase runners, wire them to the engine and return true;
|
|
91
|
-
// return false if unavailable (→ setLoop fallback). Installed by the engine layers (Scene / Scene2D).
|
|
92
|
-
type FrameInstaller = (early: PhaseFn, late: PhaseFn) => boolean
|
|
93
|
-
let frameInstaller: FrameInstaller | undefined
|
|
94
|
-
/** @internal Install a render-synced frame source for aspect update(dt). Called from an engine layer. */
|
|
95
|
-
export const _installAspectFrames = (fn: FrameInstaller): void => { frameInstaller = fn }
|
|
96
|
-
|
|
97
|
-
let attachSeq = 0
|
|
98
|
-
const warned = new Set<string>()
|
|
99
|
-
const warnOnce = (msg: string): void => { if (!warned.has(msg)) { warned.add(msg); console.warn(msg) } }
|
|
100
|
-
|
|
101
|
-
/** One phase's updater list. Iterated LIVE (no per-frame copy): removals during a pass null the slot
|
|
102
|
-
* and compact afterwards; additions land past the pass's end and tick from the next frame. Sorting
|
|
103
|
-
* is lazy — re-ranked before the next pass after any add. */
|
|
104
|
-
class PhaseList {
|
|
105
|
-
readonly items: (Updatable | null)[] = []
|
|
106
|
-
private iterating = false
|
|
107
|
-
private dirty = false
|
|
108
|
-
private holes = false
|
|
109
|
-
|
|
110
|
-
add(a: Updatable, phase: "update" | "updateBefore"): void {
|
|
111
|
-
a._phaseSeq = ++attachSeq
|
|
112
|
-
this.items.push(a)
|
|
113
|
-
this.dirty = true
|
|
114
|
-
a._phases |= phase === "update" ? 2 : 1
|
|
115
|
-
}
|
|
116
|
-
remove(a: Updatable): boolean {
|
|
117
|
-
const i = this.items.indexOf(a)
|
|
118
|
-
if (i < 0) return false
|
|
119
|
-
if (this.iterating) { this.items[i] = null; this.holes = true }
|
|
120
|
-
else this.items.splice(i, 1)
|
|
121
|
-
return true
|
|
122
|
-
}
|
|
123
|
-
run(method: "update" | "updateBefore", dt: number): void {
|
|
124
|
-
if (this.dirty) this.sort()
|
|
125
|
-
const items = this.items
|
|
126
|
-
const n = items.length // appended during the pass → next frame (attach-order snapshot semantics)
|
|
127
|
-
const paused = Time.paused
|
|
128
|
-
this.iterating = true
|
|
129
|
-
for (let i = 0; i < n; i++) {
|
|
130
|
-
const a = items[i]
|
|
131
|
-
if (a === null || (paused && !a.updateWhilePaused)) continue
|
|
132
|
-
a[method]!(dt)
|
|
133
|
-
}
|
|
134
|
-
this.iterating = false
|
|
135
|
-
if (this.holes) {
|
|
136
|
-
this.holes = false
|
|
137
|
-
let w = 0
|
|
138
|
-
for (let r = 0; r < items.length; r++) { const a = items[r]; if (a !== null) items[w++] = a }
|
|
139
|
-
items.length = w
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/** Stable sort by (constraint rank, updateOrder, attach sequence). */
|
|
143
|
-
private sort(): void {
|
|
144
|
-
this.dirty = false
|
|
145
|
-
const ranks = rankConstraints(this.items)
|
|
146
|
-
this.items.sort((x, y) => {
|
|
147
|
-
if (x === null || y === null) return x === null ? (y === null ? 0 : 1) : -1
|
|
148
|
-
const rx = ranks.get(x.constructor) ?? 0, ry = ranks.get(y.constructor) ?? 0
|
|
149
|
-
if (rx !== ry) return rx - ry
|
|
150
|
-
if (x.updateOrder !== y.updateOrder) return x.updateOrder - y.updateOrder
|
|
151
|
-
return x._phaseSeq - y._phaseSeq
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Constraint rank per class among the classes present in one phase list: rank = 0 with no incoming
|
|
157
|
-
// edge, else 1 + max(rank of predecessors). Edges: C after D (from C.after or D.before). A cycle is
|
|
158
|
-
// warned once and its closing edge ignored; a constraint naming a class not present in this phase is
|
|
159
|
-
// simply unconstrained here (early always precedes late anyway).
|
|
160
|
-
const rankConstraints = (items: (Updatable | null)[]): Map<Function, number> => {
|
|
161
|
-
const present = new Set<Function>()
|
|
162
|
-
for (const a of items) if (a) present.add(a.constructor)
|
|
163
|
-
const preds = new Map<Function, Set<Function>>()
|
|
164
|
-
const edge = (before: Function, after: Function): void => {
|
|
165
|
-
if (!present.has(before) || !present.has(after) || before === after) return
|
|
166
|
-
let s = preds.get(after)
|
|
167
|
-
if (!s) preds.set(after, (s = new Set()))
|
|
168
|
-
s.add(before)
|
|
169
|
-
}
|
|
170
|
-
for (const c of present) {
|
|
171
|
-
for (const d of (c as Ctor).after ?? []) edge(d, c)
|
|
172
|
-
for (const d of (c as Ctor).before ?? []) edge(c, d)
|
|
173
|
-
}
|
|
174
|
-
if (preds.size === 0) return new Map()
|
|
175
|
-
const rank = new Map<Function, number>()
|
|
176
|
-
const visiting = new Set<Function>()
|
|
177
|
-
const rankOf = (c: Function): number => {
|
|
178
|
-
const known = rank.get(c)
|
|
179
|
-
if (known !== undefined) return known
|
|
180
|
-
if (visiting.has(c)) {
|
|
181
|
-
warnOnce(`[aspects] ordering cycle through ${c.name || "?"} — its constraint is ignored`)
|
|
182
|
-
return 0
|
|
183
|
-
}
|
|
184
|
-
visiting.add(c)
|
|
185
|
-
let r = 0
|
|
186
|
-
for (const p of preds.get(c) ?? []) r = Math.max(r, rankOf(p) + 1)
|
|
187
|
-
visiting.delete(c)
|
|
188
|
-
rank.set(c, r)
|
|
189
|
-
return r
|
|
190
|
-
}
|
|
191
|
-
for (const c of present) rankOf(c)
|
|
192
|
-
return rank
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const earlyUpdaters = new PhaseList()
|
|
196
|
-
const lateUpdaters = new PhaseList()
|
|
197
|
-
let dispatchInstalled = false
|
|
198
|
-
|
|
199
|
-
const ensureDispatch = (): void => {
|
|
200
|
-
if (dispatchInstalled) return
|
|
201
|
-
dispatchInstalled = true
|
|
202
|
-
// Both phases scale their OWN raw dt (the engine hands the same value to both; a host or test that
|
|
203
|
-
// drives them apart still gets each phase's dt right). The frame counter / clocks advance once, early.
|
|
204
|
-
const early: PhaseFn = (dt) => { Time._beginFrame(dt); earlyUpdaters.run("updateBefore", Time.dt) }
|
|
205
|
-
const late: PhaseFn = (dt) => lateUpdaters.run("update", Time._phaseDt(dt))
|
|
206
|
-
// render-synced source if an engine layer installed one; else one host loop (early then late).
|
|
207
|
-
if (!(frameInstaller && frameInstaller(early, late))) {
|
|
208
|
-
setLoop((dt) => { early(dt); late(dt) })
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const registerUpdater = (inst: Aspect<any, any, any>): void => {
|
|
213
|
-
const a = internals(inst)
|
|
214
|
-
const hasBefore = typeof a.updateBefore === "function"
|
|
215
|
-
const hasUpdate = typeof a.update === "function"
|
|
216
|
-
if (!hasBefore && !hasUpdate) return
|
|
217
|
-
if (hasBefore) earlyUpdaters.add(a, "updateBefore")
|
|
218
|
-
if (hasUpdate) lateUpdaters.add(a, "update")
|
|
219
|
-
ensureDispatch()
|
|
220
|
-
}
|
|
221
|
-
const unregisterUpdater = (inst: Aspect<any, any, any>): void => {
|
|
222
|
-
const a = internals(inst)
|
|
223
|
-
if (a._phases === 0) return
|
|
224
|
-
earlyUpdaters.remove(a)
|
|
225
|
-
lateUpdaters.remove(a)
|
|
226
|
-
a._phases = 0
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ---- per-class instance registry (`Aspect.all(Ctor)`) -----------------------
|
|
230
|
-
const instances = new Map<Function, Aspect<any, any, any>[]>()
|
|
231
|
-
const trackInstance = (a: Aspect<any, any, any>): void => {
|
|
232
|
-
let list = instances.get(a.constructor)
|
|
233
|
-
if (!list) instances.set(a.constructor, (list = []))
|
|
234
|
-
list.push(a)
|
|
235
|
-
}
|
|
236
|
-
const untrackInstance = (a: Aspect<any, any, any>): void => {
|
|
237
|
-
const list = instances.get(a.constructor)
|
|
238
|
-
if (!list) return
|
|
239
|
-
const i = list.indexOf(a)
|
|
240
|
-
if (i >= 0) list.splice(i, 1)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// ---- shared attach / detach (nodes AND scenes) ------------------------------
|
|
244
|
-
// `host` receives the named accessor and becomes `inst.node`; `store` is the class → instance map
|
|
245
|
-
// (a node's `_aspects`, a scene's `_systems`).
|
|
246
|
-
type Store = Map<Function, Aspect<any, any, any>>
|
|
247
|
-
|
|
248
|
-
const attachTo = <Self, A extends Aspect<any, any, any>>(
|
|
249
|
-
host: Self, store: Store, ctor: AspectCtor<A>, opts: Partial<A> | undefined, scene: unknown,
|
|
250
|
-
): Self & FieldOf<A> => {
|
|
251
|
-
let inst = store.get(ctor) as A | undefined
|
|
252
|
-
const fresh = inst === undefined
|
|
253
|
-
if (!inst) {
|
|
254
|
-
inst = new (ctor as unknown as new () => A)()
|
|
255
|
-
;(inst as { node: unknown }).node = host
|
|
256
|
-
if (scene !== undefined) (inst as { scene: unknown }).scene = scene
|
|
257
|
-
store.set(ctor, inst)
|
|
258
|
-
const name = (ctor as Ctor).aspect
|
|
259
|
-
if (name) (host as unknown as Record<string, unknown>)[name] = inst
|
|
260
|
-
}
|
|
261
|
-
if (opts) Object.assign(inst, opts)
|
|
262
|
-
if (fresh) {
|
|
263
|
-
trackInstance(inst)
|
|
264
|
-
internals(inst).onAttach?.()
|
|
265
|
-
registerUpdater(inst)
|
|
266
|
-
} else if (opts) internals(inst).onReconfigure?.()
|
|
267
|
-
return host as Self & FieldOf<A>
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const detachFrom = (host: unknown, store: Store, ctor: Function): void => {
|
|
271
|
-
const inst = store.get(ctor)
|
|
272
|
-
if (!inst) return
|
|
273
|
-
internals(inst).onDetach?.()
|
|
274
|
-
unregisterUpdater(inst)
|
|
275
|
-
untrackInstance(inst)
|
|
276
|
-
inst._clearEvents()
|
|
277
|
-
store.delete(ctor)
|
|
278
|
-
const name = (ctor as Ctor).aspect
|
|
279
|
-
if (name) delete (host as Record<string, unknown>)[name]
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** Detach every aspect of a store, last-attached first (a Physics attached after its Shape releases
|
|
283
|
-
* its body before the Shape goes). */
|
|
284
|
-
const detachAllFrom = (host: unknown, store: Store): void => {
|
|
285
|
-
const ctors = [...store.keys()]
|
|
286
|
-
for (let i = ctors.length - 1; i >= 0; i--) detachFrom(host, store, ctors[i])
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ---- the host: mixed into every node kind (Node2D, Node) --------------------
|
|
290
|
-
/**
|
|
291
|
-
* Base for anything that can carry aspects. Extends Emitter, so node kinds get events too. Provides
|
|
292
|
-
* the four verbs; the named accessor (node.physics, node.health, …) is set as an own property at
|
|
293
|
-
* attach time, keyed by the aspect class's runtime `aspect` name.
|
|
294
|
-
*/
|
|
295
|
-
export abstract class AspectHost<E extends EventMap = EventMap> extends Emitter<E> {
|
|
296
|
-
/** @internal class → instance, the authoritative store (named accessors mirror this). */
|
|
297
|
-
readonly _aspects = new Map<Function, Aspect<any, any, any>>()
|
|
298
|
-
|
|
299
|
-
/** Attach (and configure) an aspect, or reconfigure it if already present. Returns the node typed
|
|
300
|
-
* as now-having that aspect (so no guard/`?.` is needed afterwards). Rejects a wrong target node. */
|
|
301
|
-
aspect<Self extends TargetOf<A>, A extends Aspect<any, any, any>>(
|
|
302
|
-
this: Self,
|
|
303
|
-
ctor: AspectCtor<A>,
|
|
304
|
-
opts?: Partial<A>,
|
|
305
|
-
): Self & FieldOf<A> {
|
|
306
|
-
return attachTo(this, (this as unknown as AspectHost)._aspects, ctor, opts, undefined)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** Safe access — undefined if the aspect isn't attached. */
|
|
310
|
-
get<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): A | undefined {
|
|
311
|
-
return this._aspects.get(ctor) as A | undefined
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** Existence check AND type guard: inside `if (node.has(Physics))`, `node.physics` is present. */
|
|
315
|
-
has<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this is this & FieldOf<A> {
|
|
316
|
-
return this._aspects.has(ctor)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** Detach an aspect (runs its onDetach). Named `removeAspect` so it never collides with the
|
|
320
|
-
* child-management `add`/`remove` semantics some node kinds expose. */
|
|
321
|
-
removeAspect<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this {
|
|
322
|
-
detachFrom(this, this._aspects, ctor)
|
|
323
|
-
return this
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/** @internal Detach every aspect (node teardown): onDetach, updaters, events, registries. */
|
|
327
|
-
_detachAll(): void {
|
|
328
|
-
detachAllFrom(this, this._aspects)
|
|
329
|
-
this.clearListeners()
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// ---- systems: aspects of a scene --------------------------------------------
|
|
334
|
-
/** @internal What a scene needs to carry systems. Scene / Scene2D implement it over these helpers. */
|
|
335
|
-
export type SystemHost = { readonly _systems: Map<Function, Aspect<any, any, any>> }
|
|
336
|
-
|
|
337
|
-
/** @internal `scene.system(Ctor, opts)`. */
|
|
338
|
-
export const _attachSystem = <Self extends SystemHost, A extends Aspect<any, any, any>>(
|
|
339
|
-
scene: Self, ctor: AspectCtor<A>, opts?: Partial<A>,
|
|
340
|
-
): Self & FieldOf<A> => attachTo(scene, scene._systems, ctor, opts, scene)
|
|
341
|
-
/** @internal `scene.removeSystem(Ctor)`. */
|
|
342
|
-
export const _detachSystem = (scene: SystemHost, ctor: Function): void => detachFrom(scene, scene._systems, ctor)
|
|
343
|
-
/** @internal Scene teardown: every system, last-attached first. */
|
|
344
|
-
export const _detachAllSystems = (scene: SystemHost): void => detachAllFrom(scene, scene._systems)
|
|
345
|
-
|
|
346
|
-
/** @internal Engine layers call this once with their bridge's clock hook (feature-detected). */
|
|
347
|
-
export const _installTimeScale = (sink: (scale: number) => void): void => _registerTimeScaleSink(sink)
|
|
348
|
-
|
|
349
|
-
// ---- the aspect base --------------------------------------------------------
|
|
350
|
-
/**
|
|
351
|
-
* Base for everything you attach. `K` = accessor name (extracted by chisel for the runtime). `P` =
|
|
352
|
-
* the target node kind: `this.node` is typed to it, AND attaching to a wrong node is a compile error.
|
|
353
|
-
* `E` = the events this aspect emits (`{ explode: (b: Barrel) => void }`): `this.emit('explode', …)`
|
|
354
|
-
* inside, `node.barrel.on('explode', fn)` outside.
|
|
355
|
-
* Initialize in `onAttach` (the node is set by then) — not a constructor; aspects are created by the
|
|
356
|
-
* engine via `node.aspect()`, never `new`.
|
|
357
|
-
*/
|
|
358
|
-
export abstract class Aspect<K extends string, P = Node, E extends EventMap = {}> {
|
|
359
|
-
/**
|
|
360
|
-
* Opt this aspect class into EDITOR-RUN mode (generators): while a scene is edited, the scene
|
|
361
|
-
* loader constructs the instance (refs resolved, `node`/`generated` set — never `onAttach`) and
|
|
362
|
-
* calls `rebuild()` — again on inspector prop edits and when a `ref()`-referenced node moves.
|
|
363
|
-
* `static editor = { rebuild: true }`.
|
|
364
|
-
*/
|
|
365
|
-
static editor?: { rebuild?: boolean }
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Custom inspector card for this aspect in the scene editor (immediate-mode — see
|
|
369
|
-
* core/InspectorUI.ts): re-runs on every edit/event and emits a widget list. Without it, the
|
|
370
|
-
* editor shows the inferred fields; `ui.auto()` emits those same fields, so a custom inspector
|
|
371
|
-
* usually starts with `ui.auto()` and appends status lines / buttons / dynamic dropdowns:
|
|
372
|
-
*
|
|
373
|
-
* static inspector(ui: InspectorUI, self: MyAspect) {
|
|
374
|
-
* ui.auto()
|
|
375
|
-
* if (ui.button('Randomize')) self.rebuild()
|
|
376
|
-
* }
|
|
377
|
-
*/
|
|
378
|
-
static inspector?: (ui: InspectorUI, aspect: any) => void
|
|
379
|
-
|
|
380
|
-
/** Tick AFTER these classes within the same phase (`static after = [Controls]`). A constraint
|
|
381
|
-
* always beats the numeric `updateOrder`. Classes absent from the phase are ignored. */
|
|
382
|
-
static after?: Function[]
|
|
383
|
-
/** Tick BEFORE these classes within the same phase (`static before = [Camera]`). */
|
|
384
|
-
static before?: Function[]
|
|
385
|
-
|
|
386
|
-
/** Every live instance of an aspect/system class, in attach order — the registry a game used to
|
|
387
|
-
* hand-roll as `static all[]`. Live, read-only: copy it (`[...Aspect.all(Barrel)]`) before a loop
|
|
388
|
-
* that detaches. Empty in the scene editor (aspects are inert data there). */
|
|
389
|
-
static all<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): readonly A[] {
|
|
390
|
-
return (instances.get(ctor) ?? []) as unknown as readonly A[]
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// NOTE: P has no `extends AspectHost` constraint on purpose — Emitter<E>'s generic
|
|
394
|
-
// addEventListener makes a specific-event node (AspectHost<NodeEvents>) not assignable to
|
|
395
|
-
// AspectHost<any>, which would reject every real node. P only types `this.node`; target
|
|
396
|
-
// enforcement happens in aspect() via `this: Self extends TargetOf<A>`. Defaults to Node
|
|
397
|
-
// (3D is the default engine) — a 2D aspect declares its own P, e.g. Aspect<'x', Sprite>.
|
|
398
|
-
/** The node this aspect is attached to (set at attach time). For a System: the scene. */
|
|
399
|
-
readonly node!: P
|
|
400
|
-
/**
|
|
401
|
-
* GENERATOR aspects (`static editor = { rebuild: true }`, attached through a scene file): a
|
|
402
|
-
* scene-added container child for the aspect's generated output — `rebuild()` clears and refills
|
|
403
|
-
* it. Provided by the scene loader before `onAttach`/`rebuild` run; nodes `add()`ed to it join
|
|
404
|
-
* the scene's draw set automatically (membership is separate from parenting). Undefined for
|
|
405
|
-
* aspects attached by hand outside scene files. See docs/3d/scene-files.md.
|
|
406
|
-
*/
|
|
407
|
-
readonly generated!: Node & { clear(): void, readonly version: number }
|
|
408
|
-
/**
|
|
409
|
-
* GENERATOR aspects attached through a scene file: the `Scene` the host node was built into —
|
|
410
|
-
* what `handle.instantiate({ scene })` / `scene.add()` need inside `rebuild()`. Set by the scene
|
|
411
|
-
* loader (edit AND play mode); undefined for aspects attached by hand. 3D only (2D aspects own
|
|
412
|
-
* their `scene` field). For a System: always set — the scene it is attached to.
|
|
413
|
-
*/
|
|
414
|
-
readonly scene!: P extends Node ? Scene : P extends SystemHost ? P : unknown
|
|
415
|
-
// Phantom fields — they let the type system recover `K` (the accessor name) and `E` from an
|
|
416
|
-
// instance type (`KeyOf<A>`), never read at runtime. Deliberately NOT marked as internal-only:
|
|
417
|
-
// build-types emits with `stripInternal` (which matches that tag in ANY leading comment, `//`
|
|
418
|
-
// included), and without these in the public .d.ts `infer K` collapses to `string`, which turned
|
|
419
|
-
// every `node.<accessor>` / `scene.<accessor>` into an untyped index signature in projects.
|
|
420
|
-
declare protected readonly __key__?: K
|
|
421
|
-
declare protected readonly __events__?: E
|
|
422
|
-
/** @internal attach sequence within the phase lists (tie-breaker). */
|
|
423
|
-
_phaseSeq = 0
|
|
424
|
-
/** @internal bit 1 = registered early, bit 2 = registered late. */
|
|
425
|
-
_phases = 0
|
|
426
|
-
private _listeners?: Map<string, Function[]>
|
|
427
|
-
|
|
428
|
-
// ---- lifecycle: the engine calls these, game code never does (hence `protected`; a subclass may
|
|
429
|
-
// still widen one to public when a method doubles as an explicit action). ----
|
|
430
|
-
/** The node (or scene) is set — initialize here, not in a constructor. */
|
|
431
|
-
protected onAttach?(): void
|
|
432
|
-
/** Release what onAttach acquired. */
|
|
433
|
-
protected onDetach?(): void
|
|
434
|
-
/** Called after `node.aspect(Ctor, opts)` re-assigns options on an ALREADY attached aspect (a
|
|
435
|
-
* pre-attached one like `model.anim`): rebuild whatever was derived from the options at attach. */
|
|
436
|
-
protected onReconfigure?(): void
|
|
437
|
-
/**
|
|
438
|
-
* GENERATOR aspects: (re)build the derived output under `this.generated` — must be idempotent
|
|
439
|
-
* (clear, then create). Call it from `onAttach()` for play mode; a class opting in with
|
|
440
|
-
* `static editor = { rebuild: true }` ALSO runs it while a scene is being edited: once at load,
|
|
441
|
-
* and again whenever an inspector prop changes or a `ref()`-referenced node moves.
|
|
442
|
-
*/
|
|
443
|
-
rebuild?(): void
|
|
444
|
-
/**
|
|
445
|
-
* LATE phase — every frame while attached, after the physics step + transform sync + animators,
|
|
446
|
-
* right before the frame draws: reads of `node.worldPosition` are the final drawn position
|
|
447
|
-
* (cameras/followers have no 1-frame lag) and what you write to a plain node is what this frame
|
|
448
|
-
* shows. The default home for game logic. `dt` = GAME seconds since the last frame (`Time.scale`
|
|
449
|
-
* applied; 0 while paused — see `updateWhilePaused`).
|
|
450
|
-
*/
|
|
451
|
-
protected update?(dt: number): void
|
|
452
|
-
/**
|
|
453
|
-
* EARLY phase — every frame BEFORE the physics step, so what you feed the simulation (velocity,
|
|
454
|
-
* `controller.move()`, forces, kinematic transforms) is consumed by this same frame's step: zero
|
|
455
|
-
* input latency. Use it only when you FEED the simulation. Reads here see last frame's settled
|
|
456
|
-
* state. A class rarely needs both phases — that is two aspects on one node.
|
|
457
|
-
*/
|
|
458
|
-
protected updateBefore?(dt: number): void
|
|
459
|
-
|
|
460
|
-
/** Tick order within a phase and rank — ascending; default 0, ties keep attach order. A declared
|
|
461
|
-
* `static after` / `static before` constraint always beats this number. Read once, at attach —
|
|
462
|
-
* set it as a class field. */
|
|
463
|
-
protected updateOrder = 0
|
|
464
|
-
/** Keep ticking while `Time.paused` (a HUD fade, the pause menu). `dt` is still 0 then — read
|
|
465
|
-
* `Time.unscaledDt` for wall-clock motion. Checked every frame; may be toggled at any time. */
|
|
466
|
-
protected updateWhilePaused = false
|
|
467
|
-
|
|
468
|
-
/** Opt-in: only run update(dt) while the node is on-screen. NOOP for now — visibility culling isn't
|
|
469
|
-
* wired yet, so every updater ticks regardless; declared so aspects can opt in ahead of it. */
|
|
470
|
-
protected updateWhenVisible = false
|
|
471
|
-
|
|
472
|
-
// ---- events: what this aspect tells the world (`E` in the generic) ----
|
|
473
|
-
/** Listen to one of this aspect's events. Chainable. Cleared on detach. */
|
|
474
|
-
on<C extends keyof E & string>(channel: C, callback: E[C]): this {
|
|
475
|
-
const map = (this._listeners ??= new Map())
|
|
476
|
-
const list = map.get(channel)
|
|
477
|
-
if (list) list.push(callback)
|
|
478
|
-
else map.set(channel, [callback])
|
|
479
|
-
return this
|
|
480
|
-
}
|
|
481
|
-
/** Remove a listener added with `on` (same function reference). */
|
|
482
|
-
off<C extends keyof E & string>(channel: C, callback: E[C]): this {
|
|
483
|
-
const list = this._listeners?.get(channel)
|
|
484
|
-
if (list) { const i = list.indexOf(callback); if (i >= 0) list.splice(i, 1) }
|
|
485
|
-
return this
|
|
486
|
-
}
|
|
487
|
-
/** Fire one of this aspect's events. Protected: only the aspect itself emits. */
|
|
488
|
-
protected emit<C extends keyof E & string>(channel: C, ...args: Parameters<E[C]>): void {
|
|
489
|
-
const list = this._listeners?.get(channel)
|
|
490
|
-
if (!list || list.length === 0) return
|
|
491
|
-
for (const fn of list.slice()) fn(...args)
|
|
492
|
-
}
|
|
493
|
-
/** @internal */
|
|
494
|
-
_clearEvents(): void { this._listeners = undefined }
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* An aspect of a SCENE: game logic with no single node to live on — input mapping, the FX pools, the
|
|
499
|
-
* HUD, the game mode. Same lifecycle (`onAttach`/`onDetach`), same two phases (`updateBefore` /
|
|
500
|
-
* `update`), same ordering (`updateOrder`, `static after`) and events as a node aspect; `this.scene` is the
|
|
501
|
-
* scene it was attached to. `S` = the scene kind (`Scene` by default, `Scene2D` for 2D games).
|
|
502
|
-
*
|
|
503
|
-
* class Hud extends System<'hud'> {
|
|
504
|
-
* static after = [Player]
|
|
505
|
-
* update(dt: number) { … }
|
|
506
|
-
* }
|
|
507
|
-
* scene.system(Hud) // attach
|
|
508
|
-
* scene.hud // the typed accessor
|
|
509
|
-
*/
|
|
510
|
-
export abstract class System<K extends string, S extends SystemHost = Scene, E extends EventMap = {}> extends Aspect<K, S, E> {
|
|
511
|
-
// `this.scene` (and `this.node`) are typed S by the base class's conditional — nothing to add.
|
|
512
|
-
}
|