lecodes-cli 0.19.0 → 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.
Files changed (254) hide show
  1. package/dist/index.js +730 -235
  2. package/package.json +14 -13
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  86. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  87. package/runtime/sdk/compile/assetMacro.ts +0 -146
  88. package/runtime/sdk/compile/assetName.ts +0 -50
  89. package/runtime/sdk/compile/bundler.ts +0 -287
  90. package/runtime/sdk/compile/compileProject.ts +0 -129
  91. package/runtime/sdk/compile/detectEntry.ts +0 -128
  92. package/runtime/sdk/compile/fontMacro.ts +0 -459
  93. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  94. package/runtime/sdk/compile/header.ts +0 -67
  95. package/runtime/sdk/compile/index.ts +0 -100
  96. package/runtime/sdk/compile/libraryImports.ts +0 -52
  97. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  98. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  99. package/runtime/sdk/compile/serverSplit.ts +0 -233
  100. package/runtime/sdk/compile/serverTypes.ts +0 -227
  101. package/runtime/sdk/compile/sfnt.ts +0 -98
  102. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  103. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  104. package/runtime/sdk/compile/sourcemap.ts +0 -25
  105. package/runtime/sdk/core/Aspect.ts +0 -568
  106. package/runtime/sdk/core/InspectorUI.ts +0 -212
  107. package/runtime/sdk/core/StateMachine.ts +0 -308
  108. package/runtime/sdk/core/color.ts +0 -66
  109. package/runtime/sdk/core/compWrite.ts +0 -42
  110. package/runtime/sdk/core/events.ts +0 -38
  111. package/runtime/sdk/core/fields.ts +0 -120
  112. package/runtime/sdk/core/registry.ts +0 -23
  113. package/runtime/sdk/core/signals.ts +0 -277
  114. package/runtime/sdk/core/time.ts +0 -84
  115. package/runtime/sdk/g2/Camera2D.ts +0 -40
  116. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  117. package/runtime/sdk/g2/Node2D.ts +0 -267
  118. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  119. package/runtime/sdk/g2/Physics2D.ts +0 -346
  120. package/runtime/sdk/g2/Scene2D.ts +0 -209
  121. package/runtime/sdk/g2/Shape2D.ts +0 -259
  122. package/runtime/sdk/g2/Sprite.ts +0 -89
  123. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  124. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  125. package/runtime/sdk/g2/Texture2D.ts +0 -47
  126. package/runtime/sdk/g2/Tilemap.ts +0 -41
  127. package/runtime/sdk/g2/Tileset.ts +0 -71
  128. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  129. package/runtime/sdk/g2/autotile.ts +0 -433
  130. package/runtime/sdk/g2/cells.ts +0 -91
  131. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  132. package/runtime/sdk/g2/groups2d.ts +0 -106
  133. package/runtime/sdk/g2/loop.ts +0 -50
  134. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  135. package/runtime/sdk/g2/touch.ts +0 -83
  136. package/runtime/sdk/gl/Camera.ts +0 -160
  137. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  138. package/runtime/sdk/gl/CharacterController.ts +0 -238
  139. package/runtime/sdk/gl/Gearbox.ts +0 -212
  140. package/runtime/sdk/gl/Geometry.ts +0 -279
  141. package/runtime/sdk/gl/IK.ts +0 -193
  142. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  143. package/runtime/sdk/gl/Light.ts +0 -107
  144. package/runtime/sdk/gl/Lightmap.ts +0 -256
  145. package/runtime/sdk/gl/Material.ts +0 -260
  146. package/runtime/sdk/gl/Mesh.ts +0 -83
  147. package/runtime/sdk/gl/Model.ts +0 -124
  148. package/runtime/sdk/gl/NavAgent.ts +0 -337
  149. package/runtime/sdk/gl/NavMesh.ts +0 -397
  150. package/runtime/sdk/gl/Node.ts +0 -350
  151. package/runtime/sdk/gl/Noise.ts +0 -30
  152. package/runtime/sdk/gl/Particles.ts +0 -676
  153. package/runtime/sdk/gl/Physics.ts +0 -222
  154. package/runtime/sdk/gl/Plane.ts +0 -53
  155. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  156. package/runtime/sdk/gl/Ray.ts +0 -16
  157. package/runtime/sdk/gl/Scene.ts +0 -486
  158. package/runtime/sdk/gl/Shape.ts +0 -409
  159. package/runtime/sdk/gl/Terrain.ts +0 -1092
  160. package/runtime/sdk/gl/Texture.ts +0 -63
  161. package/runtime/sdk/gl/Trigger.ts +0 -45
  162. package/runtime/sdk/gl/Vehicle.ts +0 -473
  163. package/runtime/sdk/gl/Wheel.ts +0 -240
  164. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  165. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  166. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  167. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  168. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  169. package/runtime/sdk/gl/animation/core.ts +0 -336
  170. package/runtime/sdk/gl/controls.ts +0 -95
  171. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  172. package/runtime/sdk/gl/scenarios.ts +0 -291
  173. package/runtime/sdk/gl/state.ts +0 -6
  174. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  175. package/runtime/sdk/gl/touch.ts +0 -68
  176. package/runtime/sdk/inject.ts +0 -197
  177. package/runtime/sdk/math/Mathf.ts +0 -118
  178. package/runtime/sdk/math/mat4.ts +0 -278
  179. package/runtime/sdk/math/quat.ts +0 -232
  180. package/runtime/sdk/math/vec.ts +0 -255
  181. package/runtime/sdk/net/codec.ts +0 -119
  182. package/runtime/sdk/net/core.ts +0 -384
  183. package/runtime/sdk/net/index.ts +0 -181
  184. package/runtime/sdk/net/replication.ts +0 -622
  185. package/runtime/sdk/plugins/camera.ts +0 -81
  186. package/runtime/sdk/plugins/geolocation.ts +0 -123
  187. package/runtime/sdk/plugins/oauth.ts +0 -61
  188. package/runtime/sdk/plugins/permission.ts +0 -7
  189. package/runtime/sdk/plugins/push.ts +0 -132
  190. package/runtime/sdk/plugins/qr.ts +0 -73
  191. package/runtime/sdk/plugins/service.ts +0 -47
  192. package/runtime/sdk/runtime/app.ts +0 -101
  193. package/runtime/sdk/runtime/appEvents.ts +0 -54
  194. package/runtime/sdk/runtime/channel.ts +0 -61
  195. package/runtime/sdk/runtime/clipboard.ts +0 -20
  196. package/runtime/sdk/runtime/datetime.ts +0 -329
  197. package/runtime/sdk/runtime/device.ts +0 -293
  198. package/runtime/sdk/runtime/fetch.ts +0 -77
  199. package/runtime/sdk/runtime/files.ts +0 -23
  200. package/runtime/sdk/runtime/input.ts +0 -175
  201. package/runtime/sdk/runtime/media.ts +0 -111
  202. package/runtime/sdk/runtime/misc.ts +0 -16
  203. package/runtime/sdk/runtime/net.ts +0 -36
  204. package/runtime/sdk/runtime/rpc.ts +0 -218
  205. package/runtime/sdk/runtime/service.ts +0 -83
  206. package/runtime/sdk/runtime/share.ts +0 -9
  207. package/runtime/sdk/runtime/storage.ts +0 -13
  208. package/runtime/sdk/runtime/touch.ts +0 -76
  209. package/runtime/sdk/scene/defineScene.ts +0 -1353
  210. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  211. package/runtime/sdk/scene/gizmos.ts +0 -148
  212. package/runtime/sdk/scene/grammar.ts +0 -120
  213. package/runtime/sdk/scene/material.ts +0 -188
  214. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  215. package/runtime/sdk/server/auth/global.ts +0 -80
  216. package/runtime/sdk/server/auth/host.ts +0 -318
  217. package/runtime/sdk/server/auth/models.ts +0 -83
  218. package/runtime/sdk/server/auth/types.ts +0 -50
  219. package/runtime/sdk/server/channel.ts +0 -56
  220. package/runtime/sdk/server/context.ts +0 -36
  221. package/runtime/sdk/server/db/defineDb.ts +0 -237
  222. package/runtime/sdk/server/db/fields.ts +0 -132
  223. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  224. package/runtime/sdk/server/db/index.ts +0 -7
  225. package/runtime/sdk/server/db/marci/query.ts +0 -412
  226. package/runtime/sdk/server/db/types.ts +0 -202
  227. package/runtime/sdk/server/errors.ts +0 -12
  228. package/runtime/sdk/server/host.ts +0 -74
  229. package/runtime/sdk/server/inject.ts +0 -13
  230. package/runtime/sdk/server/runtime.ts +0 -133
  231. package/runtime/sdk/server/validate.ts +0 -87
  232. package/runtime/sdk/ui/NativeView.ts +0 -142
  233. package/runtime/sdk/ui/UI.ts +0 -39
  234. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  235. package/runtime/sdk/ui/UIButton.ts +0 -101
  236. package/runtime/sdk/ui/UIContainer.ts +0 -60
  237. package/runtime/sdk/ui/UIImage.ts +0 -83
  238. package/runtime/sdk/ui/UIInput.ts +0 -185
  239. package/runtime/sdk/ui/UIModal.ts +0 -139
  240. package/runtime/sdk/ui/UINode.ts +0 -830
  241. package/runtime/sdk/ui/UIPager.ts +0 -362
  242. package/runtime/sdk/ui/UIPopover.ts +0 -100
  243. package/runtime/sdk/ui/UIScreen.ts +0 -123
  244. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  245. package/runtime/sdk/ui/UISpacer.ts +0 -14
  246. package/runtime/sdk/ui/UITabs.ts +0 -236
  247. package/runtime/sdk/ui/UIText.ts +0 -51
  248. package/runtime/sdk/ui/UIVideo.ts +0 -88
  249. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  250. package/runtime/sdk/ui/UIWidget.ts +0 -127
  251. package/runtime/sdk/ui/fonts.ts +0 -13
  252. package/runtime/sdk/ui/presentable.ts +0 -117
  253. package/runtime/sdk/ui/router.ts +0 -132
  254. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,212 +0,0 @@
1
- // The scene editor's immediate-mode inspector protocol (docs/scene-editor-plan.md, phase 9).
2
- //
3
- // Custom editor UI never renders into the editor panel directly — the live data (aspect instances,
4
- // nodes, `model.anim.clips`, generated meshes) lives in the SCENE WORLD (the compiled bundle), on
5
- // the other side of the controller. So a custom view is an imgui-style function running in the
6
- // world: it re-runs on every change/event and emits a WIDGET LIST (plain data); the editor panel is
7
- // a dumb renderer of that list; user events `{ id, value }` come back and trigger the next run —
8
- // `ui.button()` returns `true` on the run that consumes the click.
9
- //
10
- // class Road extends Aspect<'road'> {
11
- // from: Node | null = null
12
- // to: Node | null = null
13
- // width = 2
14
- // static editor = { rebuild: true }
15
- // static inspector(ui: InspectorUI, road: Road) {
16
- // ui.auto() // the inferred fields, as usual
17
- // if (road.from && road.to) ui.info(`${road.generated.children.length} pieces`)
18
- // else ui.warn('Assign both endpoints')
19
- // if (ui.button('Shuffle')) road.rebuild()
20
- // }
21
- // }
22
- //
23
- // BINDING RULE: a field whose key is a declared aspect field (a public class field) is DOC-BOUND —
24
- // the editor persists edits to the scene file through its normal commit/undo path. Any other key is
25
- // EDITOR STATE — transient, kept on this InspectorUI instance across runs, never written to a file
26
- // (the animation card's clip choice, a tool's brush size). One vocabulary, two lifetimes.
27
-
28
- import type { FieldDescriptor, FieldEditor } from "./fields"
29
-
30
- /** One user interaction coming back from the editor panel: the widget's id (+ value for fields). */
31
- export type InspectorEvent = { id: string, value?: unknown }
32
-
33
- export type InspectorFieldWidget = {
34
- kind: "field"
35
- id: string
36
- /** The bound key — an aspect field (doc-bound) or an editor-state key. */
37
- key: string
38
- label: string
39
- editor: FieldEditor | undefined
40
- value: unknown
41
- /** True = persisted to the scene file by the editor; false = transient editor state. */
42
- doc: boolean
43
- /** True = emitted by `ui.auto()` — the editor may overlay its syntactic hints (options/node). */
44
- auto?: boolean
45
- min?: number
46
- max?: number
47
- step?: number
48
- options?: readonly (string | number)[]
49
- }
50
-
51
- export type InspectorWidget =
52
- | InspectorFieldWidget
53
- | { kind: "button", id: string, label: string }
54
- | { kind: "toolButton", id: string, label: string, tool: string }
55
- | { kind: "header", id: string, label: string }
56
- | { kind: "info" | "warn", id: string, text: string }
57
-
58
- type FieldOpts = {
59
- label?: string
60
- min?: number
61
- max?: number
62
- step?: number
63
- /** Default for EDITOR-STATE keys (doc keys default from the class field). */
64
- value?: unknown
65
- }
66
-
67
- /**
68
- * The widget builder handed to `static inspector(ui, aspect)` (and to editor windows/tools later).
69
- * One instance lives per card and persists across runs — it carries the card's editor state.
70
- */
71
- export class InspectorUI {
72
- /** @internal This run's emitted widgets. */
73
- _widgets: InspectorWidget[] = []
74
- /** @internal Transient editor state (non-doc field keys), kept across runs. */
75
- _state: Record<string, unknown> = {}
76
- /** @internal The aspect entry's doc props (live reference — the runner keeps it current). */
77
- _props: Record<string, unknown> = {}
78
- /** @internal Keys that are declared aspect fields → doc-bound. Empty for node/window cards. */
79
- _docKeys = new Set<string>()
80
- /** @internal Field descriptors for auto() + doc-key defaults (describeFields output). */
81
- _fields: FieldDescriptor[] = []
82
- /** @internal The unconsumed button event of the current run. */
83
- _event: InspectorEvent | null = null
84
-
85
- // ---- fields (each emits one widget AND returns the current value) ---------------------------
86
-
87
- /** Numeric field (min/max/step render as a slider-style scrub range). */
88
- number(key: string, opts?: FieldOpts): number {
89
- const v = this._field("number", key, opts)
90
- return typeof v === "number" ? v : 0
91
- }
92
- /** Alias of `number` — give it min/max/step and the editor renders a scrubable range. */
93
- slider(key: string, opts?: FieldOpts): number { return this.number(key, opts) }
94
-
95
- text(key: string, opts?: FieldOpts): string {
96
- const v = this._field("text", key, opts)
97
- return typeof v === "string" ? v : ""
98
- }
99
-
100
- color(key: string, opts?: FieldOpts): string {
101
- const v = this._field("color", key, opts)
102
- return typeof v === "string" ? v : "#ffffff"
103
- }
104
-
105
- switch(key: string, opts?: FieldOpts): boolean {
106
- return this._field("switch", key, opts) === true
107
- }
108
-
109
- /** Dropdown — `options` may be computed fresh every run (dynamic lists come free). */
110
- select(key: string, options: readonly (string | number)[], opts?: FieldOpts): string | number {
111
- const v = this._field("select", key, { ...opts, value: opts?.value ?? options[0] }, options)
112
- return (typeof v === "string" || typeof v === "number") && options.includes(v) ? v : options[0]
113
- }
114
-
115
- vec2(key: string, opts?: FieldOpts): [number, number] { return this._vec("vec2", key, 2, opts) as [number, number] }
116
- vec3(key: string, opts?: FieldOpts): [number, number, number] { return this._vec("vec3", key, 3, opts) as [number, number, number] }
117
- vec4(key: string, opts?: FieldOpts): [number, number, number, number] { return this._vec("vec4", key, 4, opts) as [number, number, number, number] }
118
-
119
- /** Scene-node reference (`ref()` fields): name dropdown + pick-in-viewport. Returns the RAW doc
120
- * value (`{ $ref }` marker or null) — read the resolved live node off the aspect instance. */
121
- node(key: string, opts?: FieldOpts): unknown {
122
- return this._field("node", key, opts)
123
- }
124
-
125
- /** Project-asset path (the editor renders a dropdown of the project's GLBs). "" = none picked. */
126
- asset(key: string, opts?: FieldOpts): string {
127
- const v = this._field("asset", key, { ...opts, value: opts?.value ?? "" })
128
- return typeof v === "string" ? v : ""
129
- }
130
-
131
- // ---- everything else --------------------------------------------------------------------------
132
-
133
- /** True on the run that consumes this button's click — do the action right there. */
134
- button(label: string, opts?: { id?: string }): boolean {
135
- const id = opts?.id ?? `b:${label}`
136
- this._widgets.push({ kind: "button", id, label })
137
- if (this._event?.id === id) {
138
- this._event = null
139
- return true
140
- }
141
- return false
142
- }
143
-
144
- /** A toggle that activates/deactivates the named viewport tool (`registerEditorTool`). The
145
- * editor owns the active-tool state — the click never round-trips into the scene world. */
146
- toolButton(label: string, tool: string): void {
147
- this._widgets.push({ kind: "toolButton", id: `t:${tool}`, label, tool })
148
- }
149
-
150
- header(label: string): void { this._widgets.push({ kind: "header", id: `h:${label}`, label }) }
151
- info(text: string): void { this._widgets.push({ kind: "info", id: `i:${this._widgets.length}`, text }) }
152
- warn(text: string): void { this._widgets.push({ kind: "warn", id: `w:${this._widgets.length}`, text }) }
153
-
154
- /** Emit the inferred field widgets (all declared aspect fields, or just the named ones) — the
155
- * zero-ceremony baseline; append custom widgets around it. */
156
- auto(...keys: string[]): void {
157
- for (const f of this._fields) {
158
- if (keys.length > 0 && !keys.includes(f.key)) continue
159
- const value = this._props[f.key] !== undefined ? this._props[f.key] : f.value
160
- this._widgets.push({
161
- kind: "field", id: `f:${f.key}`, key: f.key, label: f.label, editor: f.editor,
162
- value, doc: true, auto: true, min: f.min, max: f.max, step: f.step, options: f.options,
163
- })
164
- }
165
- }
166
-
167
- // ---- internals ----------------------------------------------------------------------------
168
-
169
- private _vec(editor: FieldEditor, key: string, size: number, opts?: FieldOpts): number[] {
170
- const v = this._field(editor, key, opts)
171
- const arr = Array.isArray(v) ? v : []
172
- return Array.from({ length: size }, (_, i) => (typeof arr[i] === "number" ? arr[i] as number : 0))
173
- }
174
-
175
- private _field(
176
- editor: FieldEditor, key: string, opts?: FieldOpts, options?: readonly (string | number)[],
177
- ): unknown {
178
- const doc = this._docKeys.has(key)
179
- const fallback = doc ? this._fields.find((f) => f.key === key)?.value : opts?.value
180
- const stored = doc ? this._props[key] : this._state[key]
181
- const value = stored !== undefined ? stored : fallback
182
- this._widgets.push({
183
- kind: "field", id: `f:${key}`, key, label: opts?.label ?? key, editor, value, doc,
184
- min: opts?.min, max: opts?.max, step: opts?.step, options,
185
- })
186
- return value
187
- }
188
-
189
- /**
190
- * @internal One immediate-mode pass: deliver `event`, run `fn`, return the widget list.
191
- * Editor-state field events are applied here; DOC-key field events must be applied by the
192
- * caller beforehand (they go through the scene loader's prop machinery — rebuilds, ref
193
- * resolution); button events are consumed by the matching `button()` call during the run.
194
- * A throwing inspector logs and shows the error as a warn line — it can't take the editor down.
195
- */
196
- _run(fn: (ui: this) => void, event?: InspectorEvent): InspectorWidget[] {
197
- if (event && event.id.startsWith("f:") && !this._docKeys.has(event.id.slice(2))) {
198
- this._state[event.id.slice(2)] = event.value
199
- event = undefined
200
- }
201
- this._event = event ?? null
202
- this._widgets = []
203
- try {
204
- fn(this)
205
- } catch (e) {
206
- console.error("[scene] inspector render failed:", e)
207
- this.warn(String((e as Error)?.message ?? e))
208
- }
209
- this._event = null
210
- return this._widgets
211
- }
212
- }
@@ -1,308 +0,0 @@
1
- // A hierarchical state machine over a plain context object — the one place a game keeps "what am I
2
- // doing right now": an enemy brain, a weapon (ready → fire → cooldown → reload), a game mode
3
- // (warmup → round → over), a door. Declarative: the whole machine is ONE table you can read top
4
- // to bottom, log, and later show in an inspector; the machine knows nothing about aspects, nodes
5
- // or the network, so it runs wherever its owner ticks it (an `updateFixed` for anything that must
6
- // match another machine's, `update` for presentation).
7
- //
8
- // const Guard = defineStates<Bot>({
9
- // initial: 'patrol',
10
- // on: { hit: 'flinch' }, // from ANY state
11
- // to: { dead: (b) => b.hp <= 0 }, // checked in every state, first
12
- // states: {
13
- // patrol: { update: (b, dt) => b.walk(dt), to: { combat: (b) => b.target !== null } },
14
- // combat: {
15
- // initial: 'engage',
16
- // to: { search: (b) => b.lostFor > 4 }, // leaves whichever child is active
17
- // states: {
18
- // engage: { update: (b) => b.approach(), to: { shoot: (b) => b.visible && b.dist < 12 } },
19
- // shoot: { enter: (b) => b.fire = true, exit: (b) => b.fire = false, to: { engage: (b) => !b.visible } },
20
- // },
21
- // },
22
- // search: { after: [6, 'patrol'] }, // a timed transition
23
- // flinch: { after: [0.2, '$back'] }, // '$back' = the state we came from
24
- // dead: { enter: (b) => b.die() },
25
- // },
26
- // })
27
- // const fsm = new StateMachine(Guard, bot)
28
- // fsm.step(dt) // once per tick: guards (outer states first), then updates
29
- // fsm.send('hit') // an event: the innermost state that handles it wins
30
- // fsm.state // 'combat.shoot' fsm.is('combat') → true
31
- // fsm.on('change', (to, from) => console.log(`[fsm] ${from} → ${to}`))
32
- //
33
- // Semantics, in order, per `step(dt)`:
34
- // 1. every active state's clock advances (`t` = seconds in that state);
35
- // 2. transitions are tested OUTERMOST first — the root's `to` (any-state guards), then each
36
- // active state's `to` guards (first true wins) and its `after` timer; a taken transition is
37
- // re-tested from the new state (a pass-through state chains), at most 8 hops per step;
38
- // 3. `update` runs outermost → innermost; an update that RETURNS a state name transitions
39
- // there and ends the pass (the states it entered update next step).
40
- // A transition exits states innermost-first up to the common ancestor and enters the new branch
41
- // outermost-first, descending into `initial` children; the target state's own name (or an
42
- // ancestor's) re-enters it. Names resolve relative to the declaring state (siblings, then each
43
- // ancestor's siblings), or as a dotted path from the root; unknown names throw when the machine is
44
- // built, so a typo is a startup error, not a silent no-op. `send` looks innermost → outermost
45
- // (the root's `on` = any state) and returns whether someone handled it.
46
-
47
- export type StateGuard<C> = (ctx: C, t: number) => boolean
48
- export type StateHandler<C> = (ctx: C, ...args: any[]) => string | void
49
-
50
- export interface StateDef<C> {
51
- /** The child entered by default when this (compound) state is entered. */
52
- initial?: string
53
- /** Child states — this becomes a compound state; only a leaf is ever "the" state. */
54
- states?: Record<string, StateDef<C>>
55
- /** Entered (from = the previous leaf's path, '' at start). */
56
- enter?: (ctx: C, from: string) => void
57
- /** Left (to = the next leaf's path). */
58
- exit?: (ctx: C, to: string) => void
59
- /** Every step while active, outer states first; `t` = seconds in this state. Return a state name
60
- * to transition. */
61
- update?: (ctx: C, dt: number, t: number) => string | void
62
- /** Guarded transitions, tested every step before `update` — key = target, first true wins. */
63
- to?: Record<string, StateGuard<C>>
64
- /** Leave for `target` after `seconds` in this state. */
65
- after?: [seconds: number, target: string]
66
- /** Event handlers for `send(name, …args)`: a target name, or a function returning one (or nothing). */
67
- on?: Record<string, string | StateHandler<C>>
68
- }
69
-
70
- export interface StatesDef<C> {
71
- initial: string
72
- states: Record<string, StateDef<C>>
73
- /** Any-state guards — tested first, in every state. */
74
- to?: Record<string, StateGuard<C>>
75
- /** Any-state event handlers — the fallback when no active state handles the event. */
76
- on?: Record<string, string | StateHandler<C>>
77
- /** Any-state update, runs before the active states' own. */
78
- update?: (ctx: C, dt: number, t: number) => string | void
79
- }
80
-
81
- export type StateMachineEvents = {
82
- /** After a transition completed (both are leaf paths; `from` = '' for the initial entry). */
83
- change: (to: string, from: string) => void
84
- }
85
-
86
- /** Identity helper — names the context type once so every callback is typed. */
87
- export const defineStates = <C>(def: StatesDef<C>): StatesDef<C> => def
88
-
89
- const BACK = "$back"
90
- const MAX_HOPS = 8
91
-
92
- interface SNode<C> {
93
- name: string
94
- path: string
95
- def: StateDef<C>
96
- parent: SNode<C> | null
97
- children: Map<string, SNode<C>>
98
- initial: SNode<C> | null
99
- depth: number
100
- /** seconds in this state (valid while active) */
101
- t: number
102
- /** resolved `to` targets in declaration order */
103
- guards: Array<[SNode<C>, StateGuard<C>]>
104
- after: [number, SNode<C> | typeof BACK] | null
105
- /** resolution cache for names returned at runtime */
106
- resolved: Map<string, SNode<C> | typeof BACK>
107
- }
108
-
109
- export class StateMachine<C> {
110
- readonly ctx: C
111
- private readonly root: SNode<C>
112
- private readonly byPath = new Map<string, SNode<C>>()
113
- /** the active branch, root's child (index 0) → leaf */
114
- private chain: SNode<C>[] = []
115
- private prev: SNode<C> | null = null
116
- private listeners: Array<(to: string, from: string) => void> = []
117
- private changed = 0
118
-
119
- constructor(def: StatesDef<C>, ctx: C) {
120
- this.ctx = ctx
121
- this.root = this.build("", "", def as StateDef<C>, null, 0)
122
- // second pass: resolve names (every state exists now)
123
- for (const n of this.byPath.values()) this.link(n)
124
- this.link(this.root)
125
- if (!this.root.initial) throw new Error("StateMachine: `initial` is required")
126
- this.enterBranch([], this.leafOf(this.root.initial), "")
127
- }
128
-
129
- /** The active leaf's path ('combat.shoot'). */
130
- get state(): string { return this.chain[this.chain.length - 1]?.path ?? "" }
131
- /** The leaf we came from ('' before the first transition). */
132
- get previous(): string { return this.prev?.path ?? "" }
133
- /** Seconds in the active leaf. */
134
- get time(): number { return this.chain[this.chain.length - 1]?.t ?? 0 }
135
- /** Every active state's path, outermost first. */
136
- get branch(): string[] { return this.chain.map((n) => n.path) }
137
-
138
- /** Is `name` (a state name or dotted path) active — as the leaf OR as one of its ancestors? */
139
- is(name: string): boolean {
140
- for (const n of this.chain) if (n.name === name || n.path === name) return true
141
- return false
142
- }
143
- /** Seconds in the named active state (0 when it is not active). */
144
- timeIn(name: string): number {
145
- for (const n of this.chain) if (n.name === name || n.path === name) return n.t
146
- return 0
147
- }
148
-
149
- /** Force a transition (a spawn, a reset from outside the table). */
150
- go(name: string): void {
151
- const leaf = this.chain[this.chain.length - 1] ?? this.root
152
- this.transition(this.resolve(leaf, name))
153
- }
154
-
155
- /** Deliver an event. Innermost active state first, the root's `on` last. True if handled. */
156
- send(event: string, ...args: unknown[]): boolean {
157
- for (let i = this.chain.length - 1; i >= 0; i--) {
158
- const n = this.chain[i]!
159
- const h = n.def.on?.[event]
160
- if (h === undefined) continue
161
- const target = typeof h === "string" ? h : h(this.ctx, ...args)
162
- if (typeof target === "string") this.transition(this.resolve(n, target))
163
- return true
164
- }
165
- const h = this.root.def.on?.[event]
166
- if (h === undefined) return false
167
- const target = typeof h === "string" ? h : h(this.ctx, ...args)
168
- if (typeof target === "string") this.transition(this.resolve(this.chain[this.chain.length - 1] ?? this.root, target))
169
- return true
170
- }
171
-
172
- /** One tick: clocks, guards (outer first, chained), then updates (outer first). */
173
- step(dt: number): void {
174
- for (const n of this.chain) n.t += dt
175
- this.root.t += dt
176
- for (let hop = 0; hop < MAX_HOPS; hop++) {
177
- const target = this.findTransition()
178
- if (target === null) break
179
- this.transition(target)
180
- }
181
- const mark = this.changed
182
- const r0 = this.root.def.update?.(this.ctx, dt, this.root.t)
183
- if (typeof r0 === "string") { this.transition(this.resolve(this.chain[this.chain.length - 1]!, r0)); return }
184
- if (this.changed !== mark) return
185
- const chain = this.chain.slice()
186
- for (const n of chain) {
187
- const r = n.def.update?.(this.ctx, dt, n.t)
188
- if (typeof r === "string") { this.transition(this.resolve(n, r)); return }
189
- if (this.changed !== mark) return // an update transitioned through send()/go()
190
- }
191
- }
192
-
193
- /** Listen for transitions. */
194
- on(event: "change", fn: StateMachineEvents["change"]): this { this.listeners.push(fn); return this }
195
- off(event: "change", fn: StateMachineEvents["change"]): this {
196
- const i = this.listeners.indexOf(fn)
197
- if (i >= 0) this.listeners.splice(i, 1)
198
- return this
199
- }
200
-
201
- // ---- building ----
202
- private build(name: string, path: string, def: StateDef<C>, parent: SNode<C> | null, depth: number): SNode<C> {
203
- const node: SNode<C> = { name, path, def, parent, children: new Map(), initial: null, depth, t: 0, guards: [], after: null, resolved: new Map() }
204
- if (path) this.byPath.set(path, node)
205
- if (def.states) {
206
- for (const [childName, childDef] of Object.entries(def.states)) {
207
- if (childName.includes(".") || childName.startsWith("$")) throw new Error(`StateMachine: bad state name '${childName}'`)
208
- node.children.set(childName, this.build(childName, path ? `${path}.${childName}` : childName, childDef, node, depth + 1))
209
- }
210
- const initial = def.initial ?? Object.keys(def.states)[0]!
211
- const child = node.children.get(initial)
212
- if (!child) throw new Error(`StateMachine: '${path || "root"}' has no child '${initial}' for initial`)
213
- node.initial = child
214
- } else if (def.initial) {
215
- throw new Error(`StateMachine: '${path}' names an initial child but has no states`)
216
- }
217
- return node
218
- }
219
-
220
- private link(n: SNode<C>): void {
221
- if (n.def.to) for (const [target, guard] of Object.entries(n.def.to)) n.guards.push([this.resolveStrict(n, target), guard])
222
- if (n.def.after) n.after = [n.def.after[0], this.resolve(n, n.def.after[1])]
223
- if (n.def.on) for (const h of Object.values(n.def.on)) if (typeof h === "string") this.resolve(n, h)
224
- }
225
-
226
- private resolveStrict(from: SNode<C>, name: string): SNode<C> {
227
- const r = this.resolve(from, name)
228
- if (r === BACK) throw new Error(`StateMachine: '${BACK}' is not allowed as a guard target of '${from.path}'`)
229
- return r
230
- }
231
-
232
- /** Relative first (siblings of `from`, then of each ancestor), then a dotted path from the root. */
233
- private resolve(from: SNode<C>, name: string): SNode<C> | typeof BACK {
234
- const cached = from.resolved.get(name)
235
- if (cached) return cached
236
- let found: SNode<C> | typeof BACK | undefined
237
- if (name === BACK) found = BACK
238
- else {
239
- for (let n: SNode<C> | null = from; n && !found; n = n.parent) {
240
- const scope = n.parent ?? this.root
241
- found = scope.children.get(name)
242
- if (!found && n === from) found = n.children.get(name) // a compound state naming its own child
243
- }
244
- if (!found) found = this.byPath.get(name)
245
- }
246
- if (!found) throw new Error(`StateMachine: unknown state '${name}' (from '${from.path || "root"}')`)
247
- from.resolved.set(name, found)
248
- return found
249
- }
250
-
251
- // ---- running ----
252
- /** Guards never re-enter a state that is already active (a `dead` guard staying true does not
253
- * re-enter `dead` every step) — only an explicit `go()` / event does that. */
254
- private findTransition(): SNode<C> | typeof BACK | null {
255
- const ctx = this.ctx
256
- for (const [target, guard] of this.root.guards) if (!this.active(target) && guard(ctx, this.root.t)) return target
257
- for (const n of this.chain) {
258
- for (const [target, guard] of n.guards) if (!this.active(target) && guard(ctx, n.t)) return target
259
- if (n.after && n.t >= n.after[0] && (n.after[1] === BACK || !this.active(n.after[1]))) return n.after[1]
260
- }
261
- return null
262
- }
263
-
264
- private active(n: SNode<C>): boolean { return this.chain[n.depth - 1] === n }
265
-
266
- private leafOf(n: SNode<C>): SNode<C> {
267
- while (n.initial) n = n.initial
268
- return n
269
- }
270
-
271
- private transition(target: SNode<C> | typeof BACK): void {
272
- const current = this.chain[this.chain.length - 1] ?? null
273
- let node: SNode<C>
274
- if (target === BACK) {
275
- if (!this.prev) return
276
- node = this.prev
277
- } else node = target
278
- const leaf = this.leafOf(node)
279
- // the new branch (root excluded)
280
- const next: SNode<C>[] = []
281
- for (let n: SNode<C> | null = leaf; n && n !== this.root; n = n.parent) next.unshift(n)
282
- // common prefix; a target that is the current leaf or one of its ancestors re-enters itself
283
- let common = 0
284
- while (common < this.chain.length && common < next.length && this.chain[common] === next[common]) common++
285
- if (node !== this.root && common >= node.depth && this.chain[node.depth - 1] === node) common = node.depth - 1
286
- const from = current?.path ?? ""
287
- const to = leaf.path
288
- for (let i = this.chain.length - 1; i >= common; i--) this.chain[i]!.def.exit?.(this.ctx, to)
289
- this.prev = current
290
- this.enterBranch(this.chain.slice(0, common), leaf, from, next)
291
- }
292
-
293
- private enterBranch(kept: SNode<C>[], leaf: SNode<C>, from: string, next?: SNode<C>[]): void {
294
- if (!next) {
295
- next = []
296
- for (let n: SNode<C> | null = leaf; n && n !== this.root; n = n.parent) next.unshift(n)
297
- }
298
- this.chain = kept.slice()
299
- this.changed++
300
- for (let i = kept.length; i < next.length; i++) {
301
- const n = next[i]!
302
- n.t = 0
303
- this.chain.push(n)
304
- n.def.enter?.(this.ctx, from)
305
- }
306
- for (const fn of this.listeners.slice()) fn(leaf.path, from)
307
- }
308
- }
@@ -1,66 +0,0 @@
1
- // Shared color parsing for both engines. Users pass colors the natural ways — a CSS-ish hex string
2
- // ('#10131a', '#abc', '#rrggbbaa'), a packed number (0xff0000), or normalized components
3
- // ([r,g,b] / [r,g,b,a] in 0..1). The engines want different forms (2D wants 0..1 floats, 3D wants a
4
- // packed int), so this module is the single place that converts.
5
-
6
- export type ColorInput =
7
- | string
8
- | number
9
- | readonly [number, number, number]
10
- | readonly [number, number, number, number]
11
-
12
- const parseHexString = (input: string): [number, number, number, number] => {
13
- let s = input.trim()
14
- if (s[0] === "#") s = s.slice(1)
15
- // #rgb / #rgba shorthand → double each nibble
16
- if (s.length === 3 || s.length === 4) s = s.split("").map((c) => c + c).join("")
17
- const int = parseInt(s, 16)
18
- if (s.length === 8) return [ (int >>> 24) & 255, (int >> 16) & 255, (int >> 8) & 255, int & 255 ]
19
- // default 6-digit (rrggbb); anything malformed falls back to opaque black
20
- if (s.length === 6) return [ (int >> 16) & 255, (int >> 8) & 255, int & 255, 255 ]
21
- return [ 0, 0, 0, 255 ]
22
- }
23
-
24
- /** Normalize any ColorInput to four 0..1 float components (array inputs pass through losslessly). */
25
- const toFloats = (c: ColorInput): [number, number, number, number] => {
26
- if (typeof c === "number") return [ ((c >> 16) & 255) / 255, ((c >> 8) & 255) / 255, (c & 255) / 255, 1 ]
27
- if (typeof c === "string") {
28
- const b = parseHexString(c)
29
- return [ b[0] / 255, b[1] / 255, b[2] / 255, b[3] / 255 ]
30
- }
31
- return [ c[0], c[1], c[2], (c as readonly number[])[3] ?? 1 ]
32
- }
33
-
34
- const toBytes = (c: ColorInput): [number, number, number, number] => {
35
- const f = toFloats(c)
36
- return [ Math.round(f[0] * 255), Math.round(f[1] * 255), Math.round(f[2] * 255), Math.round(f[3] * 255) ]
37
- }
38
-
39
- export const Color = {
40
- /** [r,g,b] in 0..1 — for the 2D engine's float color setters. */
41
- toRgb01(c: ColorInput): [number, number, number] {
42
- const f = toFloats(c)
43
- return [ f[0], f[1], f[2] ]
44
- },
45
- /** [r,g,b,a] in 0..1. */
46
- toRgba01(c: ColorInput): [number, number, number, number] {
47
- return toFloats(c)
48
- },
49
- /** 0xRRGGBB packed int — for the 3D engine's color uniforms / skybox. */
50
- toPackedRgb(c: ColorInput): number {
51
- const b = toBytes(c)
52
- return (b[0] << 16) | (b[1] << 8) | b[2]
53
- },
54
- /** 0xRRGGBBAA packed int (unsigned). */
55
- toPackedRgba(c: ColorInput): number {
56
- const b = toBytes(c)
57
- return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) >>> 0
58
- },
59
- /** '#rrggbb' (or '#rrggbbaa' when alpha < 1) — for the 3D material color uniforms. */
60
- toHexString(c: ColorInput): string {
61
- const b = toBytes(c)
62
- const hex = (n: number) => n.toString(16).padStart(2, "0")
63
- const base = `#${hex(b[0])}${hex(b[1])}${hex(b[2])}`
64
- return b[3] >= 255 ? base : base + hex(b[3])
65
- },
66
- }
@@ -1,42 +0,0 @@
1
- // Component writes on SDK-owned vectors — the runtime half of chisel's `comp_write` pass.
2
- //
3
- // hero.controller.velocity.y = 7 → __compWrite(hero.controller, "velocity", "y", 7)
4
- // hero.controller.velocity.y += 3 → __compOp(hero.controller, "velocity", "y", 0, 3)
5
- //
6
- // A getter like `velocity` hands out a fresh Vec3, so the literal spelling would write to a copy and
7
- // silently do nothing. chisel rewrites the exact `<owner>.<prop>.<x|y|z|w> = v` shape into these
8
- // helpers, which ask the OWNER to apply the component write (no getter call, no allocation) and
9
- // fall back to the plain write on anything else, so a user record `{ velocity: { x, y } }` behaves
10
- // exactly as written. Only the direct spelling is covered: `const v = c.velocity; v.y = 7` is still
11
- // a write to a copy.
12
- //
13
- // Owners today: Node / Node2D / Camera2D (`position` → the scalar setters, physics routing included),
14
- // Physics (`velocity`, `angularVelocity`), CharacterController / CharacterController2D (`velocity`).
15
- //
16
- // An owner declares WHICH getters qualify as compile-time data and answers them at runtime:
17
- //
18
- // static _comps = ["velocity"] // chisel reads the list, then strips it (0 bytes shipped)
19
- // _writeComp(prop, axis, value) { … } // called for listed props only; a hook without the
20
- // // list is a build diagnostic and routes nothing
21
-
22
- export type CompAxis = "x" | "y" | "z" | "w"
23
-
24
- /** Implemented by a class whose vector getters accept component writes (see above). The class also
25
- * carries `static _comps: string[]` naming those getters — compile-time data, absent at runtime. */
26
- export interface CompWriter {
27
- _writeComp(prop: string, axis: CompAxis, value: number): void
28
- }
29
-
30
- /** `<o>.<prop>.<axis> = value`, routed to the owner when it has the hook. Returns the value. */
31
- export const __compWrite = (o: any, prop: string, axis: CompAxis, value: number): number => {
32
- if (o._writeComp !== undefined) o._writeComp(prop, axis, value) // a direct call, not .call — cheaper on QuickJS
33
- else o[prop][axis] = value
34
- return value
35
- }
36
-
37
- /** `<o>.<prop>.<axis> op= v` — `op`: 0 `+=`, 1 `-=`, 2 `*=`, 3 `/=`. The current component is read
38
- * through the normal getter (one fresh vector), the result written like `__compWrite`. */
39
- export const __compOp = (o: any, prop: string, axis: CompAxis, op: number, v: number): number => {
40
- const cur = o[prop][axis] as number
41
- return __compWrite(o, prop, axis, op === 0 ? cur + v : op === 1 ? cur - v : op === 2 ? cur * v : cur / v)
42
- }
@@ -1,38 +0,0 @@
1
- // Tiny typed event dispatcher shared by media players, sockets, and (later) nodes. Keeps the
2
- // addEventListener / removeEventListener / dispatch boilerplate in one place.
3
-
4
- export type EventMap = Record<string, (...args: any[]) => void>
5
-
6
- export class Emitter<E extends EventMap> {
7
- private _listeners: { [K in keyof E]?: E[K][] } = {}
8
-
9
- addEventListener<K extends keyof E>(channel: K, callback: E[K]): void {
10
- const list = this._listeners[channel]
11
- if (list) list.push(callback)
12
- else this._listeners[channel] = [ callback ]
13
- }
14
-
15
- removeEventListener<K extends keyof E>(channel: K, callback: E[K]): void {
16
- const list = this._listeners[channel]
17
- if (!list) return
18
- const i = list.indexOf(callback)
19
- if (i >= 0) list.splice(i, 1)
20
- }
21
-
22
- /** Fire all listeners on a channel. Protected: only the owning class emits. */
23
- protected dispatch<K extends keyof E>(channel: K, ...args: Parameters<E[K]>): void {
24
- const list = this._listeners[channel]
25
- if (!list) return
26
- for (const fn of list.slice()) fn(...args)
27
- }
28
-
29
- protected hasListeners<K extends keyof E>(channel: K): boolean {
30
- const list = this._listeners[channel]
31
- return !!list && list.length > 0
32
- }
33
-
34
- /** Drop every listener on every channel (used when the owning object is disposed). */
35
- protected clearListeners(): void {
36
- this._listeners = {}
37
- }
38
- }