lecodes-sdk 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +11 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Particles.d.ts +15 -0
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +59 -3
- package/dist/types/gl/terrain/Terrain.d.ts +8 -0
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +8 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +142 -142
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +77 -54
- package/prompts/dist/3d-app.md +75 -52
- package/prompts/dist/ar-app.md +74 -51
- package/prompts/dist/design.md +30 -8
- package/prompts/dist/ui-app.md +73 -46
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +262 -378
- package/src/animate/tween/animateValue.ts +103 -100
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +505 -479
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +162 -161
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +13 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Model.ts +172 -167
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +20 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/Locomotion.ts +77 -8
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/terrain/Terrain.ts +33 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +233 -236
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +21 -8
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +154 -148
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4730
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1760
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
|
@@ -45,7 +45,7 @@ export type CompileOptions = {
|
|
|
45
45
|
/** "esm" (default, shipped) or "iife" (self-running, for the local renderer). */
|
|
46
46
|
format?: "esm" | "iife"
|
|
47
47
|
/** Resolve `asset()` resources to a LOCAL host asset at runtime instead of a `publicUrl` URL: each
|
|
48
|
-
* resource compiles to `"id:" +
|
|
48
|
+
* resource compiles to `"id:" + _creatorFetch.local(<name>)` (the host reads the file by name
|
|
49
49
|
* and returns its systemId). For hosts with no asset server (the desktop native host), where a URL
|
|
50
50
|
* can't be fetched. Off by default — the shipped/web build keeps the URL form. */
|
|
51
51
|
localAssets?: boolean
|
|
@@ -62,6 +62,12 @@ export type CompileOptions = {
|
|
|
62
62
|
* `/__lib/<slug>/…` and reachable from project code via `import … from "<slug>"`. See
|
|
63
63
|
* docs/library-system-plan.md §4. */
|
|
64
64
|
libraries?: Record<string, MemoryFiles>
|
|
65
|
+
/** The plugins of the app, by id — the `// plugins:` line of the header. Default: the keys of
|
|
66
|
+
* `"plugins"` in the project's app.json, when it is among the entries. */
|
|
67
|
+
plugins?: string[]
|
|
68
|
+
/** What the compile has to say that is not an error: a plugin the bundle uses and app.json does
|
|
69
|
+
* not list. */
|
|
70
|
+
onWarning?: (message: string) => void
|
|
65
71
|
/** Base URL of the project's deployed server (draft or prod), baked into the RPC/channel stubs
|
|
66
72
|
* that replace every `*.server.ts` module in the client bundle (docs/backend-plan.md §4). Server
|
|
67
73
|
* files never reach the client bundle either way; with no URL, calling an endpoint fails with
|
|
@@ -109,13 +115,14 @@ export const localAssetNames = (entries: readonly { path: string, type: string }
|
|
|
109
115
|
export const entriesToFiles = (entries: CompileEntry[], opts: { publicUrl: string, localAssets?: boolean }): Record<string, string> => {
|
|
110
116
|
const files: Record<string, string> = {}
|
|
111
117
|
const localNames = localAssetNames(entries)
|
|
118
|
+
const localAssetId = (nameExpr: string) => `_creatorFetch.local(${nameExpr})`
|
|
112
119
|
for (const e of entries) {
|
|
113
120
|
if (e.type === "resource") {
|
|
114
121
|
if (opts.localAssets && !e.remote) {
|
|
115
122
|
// The host resolves the asset by name at runtime (reads the local file → systemId); the
|
|
116
123
|
// "id:" prefix is the host's local-resource URL scheme (parsed by the UI + 2D texture paths).
|
|
117
124
|
const name = localNames.get(e.path) ?? localAssetBase(e.path)
|
|
118
|
-
files[e.path] = `export default ("id:" +
|
|
125
|
+
files[e.path] = `export default ("id:" + ${localAssetId(JSON.stringify(name))})`
|
|
119
126
|
} else {
|
|
120
127
|
files[e.path] = `export default "${opts.publicUrl}${e.fileSrc ?? ""}"`
|
|
121
128
|
}
|
|
@@ -131,7 +138,7 @@ export const entriesToFiles = (entries: CompileEntry[], opts: { publicUrl: strin
|
|
|
131
138
|
// (URLs) and un-staged ones keep the URL forms below.
|
|
132
139
|
const name = shaders[0].src.split(/[\/]/).pop() ?? ""
|
|
133
140
|
const [prefix, suffix = ".filamat"] = name.split("_opengl")
|
|
134
|
-
files[e.path] = `export default ("id:" +
|
|
141
|
+
files[e.path] = `export default ("id:" + ${localAssetId(`${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}`)})`
|
|
135
142
|
} else if (shaders.length > 0) {
|
|
136
143
|
files[e.path] = `export default \`${opts.publicUrl}${shaders[0].src.replace("_opengl", "_${_creator.backend}")}\``
|
|
137
144
|
} else {
|
|
@@ -151,6 +158,31 @@ export const entriesToFiles = (entries: CompileEntry[], opts: { publicUrl: strin
|
|
|
151
158
|
return files
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
/** The ids app.json lists under `"plugins"` (id → version), in its order. */
|
|
162
|
+
export const pluginsOfAppJson = (entries: CompileEntry[]): string[] => {
|
|
163
|
+
const app = entries.find(e => e.path.replace(/^[./\\]+/, "") === "app.json")
|
|
164
|
+
if (!app?.text) return []
|
|
165
|
+
try {
|
|
166
|
+
const plugins = (JSON.parse(app.text) as { plugins?: unknown }).plugins
|
|
167
|
+
return plugins && typeof plugins === "object" && !Array.isArray(plugins) ? Object.keys(plugins) : []
|
|
168
|
+
} catch {
|
|
169
|
+
return [] // a broken app.json is reported by whoever reads it as a config
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The plugins whose WRAPPER has code in the bundle, by id — read off the source map's sources:
|
|
174
|
+
* what chisel shook away is not there, so an app that never names a plugin carries none. */
|
|
175
|
+
export const pluginsOfBundle = (mapJson: string): string[] => {
|
|
176
|
+
const ids = new Set<string>()
|
|
177
|
+
try {
|
|
178
|
+
for (const source of (JSON.parse(mapJson) as { sources?: string[] }).sources ?? []) {
|
|
179
|
+
const m = /\/plugins\/gen\/([^/]+)\/sdk\//.exec(source)
|
|
180
|
+
if (m) ids.add(m[1]!)
|
|
181
|
+
}
|
|
182
|
+
} catch { /* no map, no answer */ }
|
|
183
|
+
return [...ids].sort()
|
|
184
|
+
}
|
|
185
|
+
|
|
154
186
|
export const compileProjectWithMap = async (opts: CompileOptions): Promise<CompileResult> => {
|
|
155
187
|
// Client bundle: every *.server.ts becomes its RPC/channel stub (server code stays server-side).
|
|
156
188
|
const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "")
|
|
@@ -163,8 +195,15 @@ export const compileProjectWithMap = async (opts: CompileOptions): Promise<Compi
|
|
|
163
195
|
const { code, map } = await bundleProjectWithMap(
|
|
164
196
|
entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true,
|
|
165
197
|
)
|
|
198
|
+
const plugins = opts.plugins ?? pluginsOfAppJson(entries)
|
|
199
|
+
if (map && opts.onWarning) {
|
|
200
|
+
const missing = pluginsOfBundle(map).filter(id => !plugins.includes(id))
|
|
201
|
+
if (missing.length) {
|
|
202
|
+
opts.onWarning(`The app uses ${missing.map(id => `"${id}"`).join(", ")} and app.json does not list ${missing.length === 1 ? "it" : "them"}: a plugin that is not listed is not there — in a shell built from this project and in the browser alike. Add to app.json: "plugins": { ${missing.map(id => `"${id}": "*"`).join(", ")} }`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
166
205
|
if (opts.header === false) return { code, map, entry }
|
|
167
|
-
const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader })
|
|
206
|
+
const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader, plugins })
|
|
168
207
|
// Shift the map down past the header (prepended before the code).
|
|
169
208
|
const headerLines = (header.match(/\n/g) ?? []).length
|
|
170
209
|
return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry }
|
package/src/compile/fontMacro.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Registry ids resolve against the vendored snapshot (fontRegistry.ts). Two source modes:
|
|
11
11
|
// cdn — faces stay remote: the generated code bakes registry URLs, picking woff2/ttf via the
|
|
12
|
-
// host's `
|
|
12
|
+
// host's `_creatorTree.fontFormat` (absent → ttf). Platform/server-backed compiles.
|
|
13
13
|
// vendor — the caller downloads faces into the project (assets/fonts/ + fonts.lock.json) and
|
|
14
14
|
// hands back their entry paths; faces then ride the normal asset pipeline (dev URLs,
|
|
15
15
|
// `id:` + fetchLocal, lecodesfile://) — fully offline, and local faces register
|
|
@@ -417,7 +417,7 @@ export const applyFontMacro = async (entries: FontMacroEntry[], opts: FontMacroO
|
|
|
417
417
|
const ref = faces.get(key)!
|
|
418
418
|
let urlExpr: string
|
|
419
419
|
if (ref.source.kind === "cdn") {
|
|
420
|
-
urlExpr = `${JSON.stringify(ref.source.urlBase)} + (
|
|
420
|
+
urlExpr = `${JSON.stringify(ref.source.urlBase)} + (_creatorTree.fontFormat ?? "ttf")`
|
|
421
421
|
} else {
|
|
422
422
|
let ident = faceImports.get(ref.source.path)
|
|
423
423
|
if (!ident) {
|
|
@@ -427,8 +427,7 @@ export const applyFontMacro = async (entries: FontMacroEntry[], opts: FontMacroO
|
|
|
427
427
|
}
|
|
428
428
|
urlExpr = ident
|
|
429
429
|
}
|
|
430
|
-
|
|
431
|
-
body.push(`_creatorUI.registerFont(${JSON.stringify(ref.family)}, ${urlExpr}, ${optionsExpr}, () => {}, () => {})`)
|
|
430
|
+
body.push(`_creatorTree.registerFont(${JSON.stringify(ref.family)}, ${urlExpr}, ${ref.weight}, ${ref.italic ? 2 : 0}, () => {}, () => {})`)
|
|
432
431
|
}
|
|
433
432
|
for (const [family, ident] of idents) lines.push(`export const ${ident} = ${JSON.stringify(family)}`)
|
|
434
433
|
lines.push(...body)
|
package/src/compile/header.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
// The metadata header the launcher reads from the compiled bundle: name / icon / gl / 2d / ar /
|
|
2
|
-
// preload / fonts / icons. Single source of truth — the backend and CLI both call this (today they
|
|
1
|
+
// The metadata header the launcher reads from the compiled bundle: sdk / name / icon / gl / 2d / ar /
|
|
2
|
+
// plugins / preload / fonts / icons. Single source of truth — the backend and CLI both call this (today they
|
|
3
3
|
// mirror it by hand).
|
|
4
4
|
//
|
|
5
|
+
// `// sdk: <version>` is the FIRST line: the SDK this bundle was compiled with (sdk/src/version.ts).
|
|
6
|
+
// Its major is the bundle ↔ runtime contract — the launchers send it as `?sdk=` when they fetch a
|
|
7
|
+
// published bundle (the platform keeps one bundle per major), and every host refuses a bundle whose
|
|
8
|
+
// major is not its own before evaluating it. A bundle without the line predates 2.0.
|
|
9
|
+
//
|
|
5
10
|
// Detection keys on the *bundled* `_creator*` calls, which the SDK's thin wrappers still emit
|
|
6
11
|
// (new Scene() → _creator.createScene, new Scene2D() → _creator2d.createScene), so the re-skin keeps
|
|
7
12
|
// the exact same signals the old worker bundles produced.
|
|
8
13
|
|
|
14
|
+
import { SDK_VERSION } from "../version"
|
|
15
|
+
|
|
9
16
|
export type HeaderOptions = {
|
|
10
17
|
/** Generated-icon URL (backend supplies it; the CLI omits it). */
|
|
11
18
|
icon?: string
|
|
@@ -15,10 +22,15 @@ export type HeaderOptions = {
|
|
|
15
22
|
/** `// icons:` payload from the assetIcon() macro (buildIconsHeaderValue) — the icon packs whose
|
|
16
23
|
* glyphs are inlined in the bundle. Purely informational (attribution record); hosts ignore it. */
|
|
17
24
|
icons?: string
|
|
25
|
+
/** `// plugins:` — the ids app.json lists (`"plugins"`), ONE list for every platform: what a
|
|
26
|
+
* native shell installs is what a web host registers, so an app cannot work in the browser and
|
|
27
|
+
* fail in its shell. A bundle without the line has no plugin. */
|
|
28
|
+
plugins?: string[]
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}): string => {
|
|
21
|
-
let header = `//
|
|
32
|
+
let header = `// sdk: ${SDK_VERSION}\n`
|
|
33
|
+
header += `// name: ${name.slice(0, 20)}\n`
|
|
22
34
|
if (opts.icon) header += `// icon: ${opts.icon}\n`
|
|
23
35
|
header += `// last-updated: ${new Date().toISOString()}\n`
|
|
24
36
|
|
|
@@ -41,9 +53,14 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
|
|
|
41
53
|
|
|
42
54
|
if (code.includes("_creator.createARController")) header += `// ar: enable\n`
|
|
43
55
|
|
|
44
|
-
//
|
|
56
|
+
// Short, so with the engine lines: a host reads it before it registers anything.
|
|
57
|
+
if (opts.plugins?.length) header += `// plugins: ${opts.plugins.join(";")}\n`
|
|
58
|
+
|
|
59
|
+
// Local assets to preload: explicit `.local()` calls on the fetch bridge + implicit engine archives.
|
|
60
|
+
// Any receiver counts (the SDK and the asset stub of compileProject call `_creatorFetch.local`);
|
|
61
|
+
// the string-literal test below is what keeps a stray `.local(` out.
|
|
45
62
|
const localAssets = new Set<string>()
|
|
46
|
-
for (const m of code.matchAll(/
|
|
63
|
+
for (const m of code.matchAll(/[\w$]\.local\((.+?)\)/g)) {
|
|
47
64
|
const arg = m[1].trim()
|
|
48
65
|
// Only a plain string literal names a file. A computed argument (the local-shader lookup
|
|
49
66
|
// builds `"<hash>_" + _creator.backend + ".filamat"` at runtime, since one bundle serves
|
|
@@ -51,6 +68,10 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
|
|
|
51
68
|
if (!/^(['"])[^'"]*\1$/.test(arg)) continue
|
|
52
69
|
localAssets.add(arg.slice(1, -1).trim())
|
|
53
70
|
}
|
|
71
|
+
// The engine's own materials, asked for by name (`_creator.builtinMaterial("lit")`): the header
|
|
72
|
+
// says WHICH (`material:lit`), never where they are — how a host keeps them (one archive, several
|
|
73
|
+
// it fetches by need) is the host's, and may change under a bundle compiled long ago.
|
|
74
|
+
for (const m of code.matchAll(/\.builtinMaterial\((['"])([^'"]+)\1\)/g)) localAssets.add(`material:${m[2]}`)
|
|
54
75
|
if (/_creator\.createGlb/.test(code)) localAssets.add("uberarchive")
|
|
55
76
|
if (/_creator\.captureImage/.test(code)) localAssets.add("capture")
|
|
56
77
|
if (localAssets.size > 0) header += `// preload: ${[ ...localAssets ].join(";")}\n`
|
package/src/compile/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export {
|
|
|
28
28
|
type ScanModule,
|
|
29
29
|
} from "./detectEntry"
|
|
30
30
|
export { sanitizeAssetName, isCanonicalAssetName } from "./assetName"
|
|
31
|
+
export { SDK_VERSION, sdkMajor, bundleSdkVersion, bundleRunsOn } from "../version"
|
|
31
32
|
export {
|
|
32
33
|
PLATFORM_BUILD,
|
|
33
34
|
LOCAL_PLATFORM_BUILD,
|
|
@@ -64,6 +65,8 @@ export { buildHeader, detectProjectKind, type HeaderOptions } from "./header"
|
|
|
64
65
|
export {
|
|
65
66
|
compileProject,
|
|
66
67
|
compileProjectWithMap,
|
|
68
|
+
pluginsOfAppJson,
|
|
69
|
+
pluginsOfBundle,
|
|
67
70
|
entriesToFiles,
|
|
68
71
|
localAssetNames,
|
|
69
72
|
localAssetBase,
|
|
@@ -73,7 +76,6 @@ export {
|
|
|
73
76
|
type CompileResult,
|
|
74
77
|
} from "./compileProject"
|
|
75
78
|
export {
|
|
76
|
-
loadSceneHarness,
|
|
77
79
|
sceneEditorEntries,
|
|
78
80
|
sceneImportsOf,
|
|
79
81
|
SCENE_ENTRY_PATH,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// .mat source → Web Lite material artifact.
|
|
2
2
|
//
|
|
3
3
|
// matc validates and compiles the .mat for the native platforms; the lite web runtime
|
|
4
|
-
// (
|
|
4
|
+
// (engines/gl-lite) has no Filament, so it gets this JSON artifact instead: the parsed
|
|
5
5
|
// material definition (parameters, shading model, blending) plus the author's fragment body with
|
|
6
6
|
// `materialParams.x` rewritten to `materialParams_x`. viewer-lite wraps that body in its own GLSL
|
|
7
7
|
// template (src/gl/customMaterial.ts mirrors the artifact type) — GLSL knowledge stays there, this
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// The scene-editor compile composition, shared by every host that compiles an edit-mode bundle
|
|
2
|
-
// for a .scene.ts file: the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// for a .scene.ts file: the lecodes CLI (`lecodes scene`), the platform's build service, the scene
|
|
3
|
+
// editor's own playground. One bundle on purpose: chisel's method-granular DCE trims each bundle's
|
|
4
|
+
// SDK copy to its own calls, so a separately-compiled harness could not call scene methods on
|
|
5
|
+
// objects the scene bundle created.
|
|
6
|
+
//
|
|
7
|
+
// The HARNESS — the editor's half that runs in the scene's world — is not the SDK's: it ships with
|
|
8
|
+
// the editor (lecodes-3d-editor, `sceneHarness()` of its server entry) and the caller hands its
|
|
9
|
+
// sources in. What it may use of the SDK is the editor contract (src/scene/editor.ts).
|
|
6
10
|
|
|
7
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs"
|
|
8
|
-
import { join } from "node:path"
|
|
9
11
|
import type { CompileEntry } from "./compileProject"
|
|
10
12
|
|
|
11
13
|
/** Where the harness sources land in the compile's file map. The dotfolder can't collide with
|
|
@@ -15,24 +17,6 @@ export const SCENE_HARNESS_DIR = "./.scene-editor"
|
|
|
15
17
|
/** The virtual entrypoint path (`entryOverride`). */
|
|
16
18
|
export const SCENE_ENTRY_PATH = "./.scene-entry.ts"
|
|
17
19
|
|
|
18
|
-
/**
|
|
19
|
-
* Read the harness sources (packages/projects/src/scene-editor) from a directory:
|
|
20
|
-
* `LECODES_SCENE_HARNESS_DIR` env override first (deployments vendor the sources, like
|
|
21
|
-
* LECODES_SDK_INJECT does for the SDK), else the given fallback dir. Returns name → source.
|
|
22
|
-
*/
|
|
23
|
-
export const loadSceneHarness = (fallbackDir?: string): Record<string, string> => {
|
|
24
|
-
const override = process.env.LECODES_SCENE_HARNESS_DIR
|
|
25
|
-
const dir = override && existsSync(override) ? override
|
|
26
|
-
: fallbackDir && existsSync(fallbackDir) ? fallbackDir
|
|
27
|
-
: null
|
|
28
|
-
if (!dir) throw new Error("scene-editor harness sources not found (set LECODES_SCENE_HARNESS_DIR)")
|
|
29
|
-
const out: Record<string, string> = {}
|
|
30
|
-
for (const f of readdirSync(dir)) {
|
|
31
|
-
if (f.endsWith(".ts")) out[f] = readFileSync(join(dir, f), "utf8")
|
|
32
|
-
}
|
|
33
|
-
return out
|
|
34
|
-
}
|
|
35
|
-
|
|
36
20
|
/**
|
|
37
21
|
* The extra entries that turn a project compile into a scene-editor compile: the harness modules
|
|
38
22
|
* plus a virtual entry that (1) sets the edit-mode flag in its own module — imports hoist, so the
|
|
@@ -43,8 +27,9 @@ export const loadSceneHarness = (fallbackDir?: string): Record<string, string> =
|
|
|
43
27
|
* FULL extensions everywhere — exact-match specifiers need no resolver guesswork on dotted names,
|
|
44
28
|
* so even the unpatched published chisel resolves the `.scene.ts` import.
|
|
45
29
|
*
|
|
46
|
-
* `scenePath` is the project path of the scene file ("./scenes/city.scene.ts"); `
|
|
47
|
-
*
|
|
30
|
+
* `scenePath` is the project path of the scene file ("./scenes/city.scene.ts"); `harness` is the
|
|
31
|
+
* editor's harness, module name → source (its entry is `main.ts`); `projectPaths` is the project's
|
|
32
|
+
* file list — `.editor.ts` entries are picked out of it. Compile with
|
|
48
33
|
* `entryOverride: SCENE_ENTRY_PATH`, `header: false`, `format: "iife"`, `editor: true`.
|
|
49
34
|
*/
|
|
50
35
|
/** Resolve a relative import specifier against a project path ("./src/map.scene.ts" + "./map/x.scene"
|
package/src/core/color.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
// ('#10131a', '#abc', '
|
|
3
|
-
// ([r,g,b] / [r,g,b,a] in 0..1). The engines
|
|
4
|
-
//
|
|
1
|
+
// The one color conversion of the SDK. Users pass colors the natural ways — any CSS color string
|
|
2
|
+
// ('#10131a', '#abc', 'red', 'rgb(0 0 0 / 50%)', 'hsl(210 50% 40%)', 'transparent'), a packed number
|
|
3
|
+
// (0xff0000 = opaque 0xRRGGBB) or normalized components ([r,g,b] / [r,g,b,a] in 0..1). The engines
|
|
4
|
+
// want different forms (2D wants 0..1 floats, 3D a packed int, the UI core '#rrggbbaa'), so this
|
|
5
|
+
// module is the single place that converts. Strings go through the vendored AnyCanvas twin of
|
|
6
|
+
// anycanvas/css_color.h (the parser every native layer uses), so the SDK and the engines agree on
|
|
7
|
+
// every name and every byte. Anything that is not a color throws with the value.
|
|
8
|
+
|
|
9
|
+
import { cssToByte, parseCssColor } from "../canvas/gen/cssColor"
|
|
5
10
|
|
|
6
11
|
export type ColorInput =
|
|
7
12
|
| string
|
|
@@ -9,38 +14,75 @@ export type ColorInput =
|
|
|
9
14
|
| readonly [number, number, number]
|
|
10
15
|
| readonly [number, number, number, number]
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
type Rgba = [number, number, number, number]
|
|
18
|
+
|
|
19
|
+
const f = Math.fround
|
|
20
|
+
|
|
21
|
+
// A non-string input as four floats, or null when it is not a color. Arrays pass through losslessly
|
|
22
|
+
// (HDR components above 1 stay); a number is an integer 0..0xFFFFFF. Kept apart from the string
|
|
23
|
+
// path so the UI wire (numericColorToHex) reaches no CSS parser: a bundle that never hands a
|
|
24
|
+
// string to `Color` carries no name table.
|
|
25
|
+
const parseNumeric = (c: unknown): Rgba | null => {
|
|
26
|
+
if (typeof c === "number") {
|
|
27
|
+
if (!Number.isInteger(c) || c < 0 || c > 0xFFFFFF) return null
|
|
28
|
+
return [ f(((c >> 16) & 255) / 255), f(((c >> 8) & 255) / 255), f((c & 255) / 255), 1 ]
|
|
29
|
+
}
|
|
30
|
+
if (typeof c === "object" && c !== null && typeof (c as ArrayLike<number>).length === "number") {
|
|
31
|
+
const a = c as ArrayLike<unknown>
|
|
32
|
+
if (a.length !== 3 && a.length !== 4) return null
|
|
33
|
+
for (let i = 0; i < a.length; i++) if (typeof a[i] !== "number" || !Number.isFinite(a[i])) return null
|
|
34
|
+
const n = a as ArrayLike<number>
|
|
35
|
+
return [ n[0], n[1], n[2], a.length === 4 ? n[3] : 1 ]
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
22
38
|
}
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
if (typeof c === "number") return [ ((c >> 16) & 255) / 255, ((c >> 8) & 255) / 255, (c & 255) / 255, 1 ]
|
|
40
|
+
// The input as four floats, or null when it is not a color.
|
|
41
|
+
const parse = (c: unknown): Rgba | null => {
|
|
27
42
|
if (typeof c === "string") {
|
|
28
|
-
const
|
|
29
|
-
return
|
|
43
|
+
const p = parseCssColor(c)
|
|
44
|
+
return p ? [ p.r, p.g, p.b, p.a ] : null
|
|
45
|
+
}
|
|
46
|
+
return parseNumeric(c)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const show = (c: unknown): string =>
|
|
50
|
+
typeof c === "string" ? JSON.stringify(c)
|
|
51
|
+
: typeof c === "number" && Number.isInteger(c) && c >= 0 ? `0x${c.toString(16)} (${c})`
|
|
52
|
+
: Array.isArray(c) ? `[${c.join(", ")}]` : String(c)
|
|
53
|
+
|
|
54
|
+
// The throw for a value that is not a color, naming it.
|
|
55
|
+
const notAColor = (c: unknown): never => {
|
|
56
|
+
if (typeof c === "number") {
|
|
57
|
+
throw new RangeError(`Color: ${show(c)} is not a color — a number is an opaque 0xRRGGBB (an integer 0..0xFFFFFF); write alpha as "#rrggbbaa" or [r, g, b, a]`)
|
|
30
58
|
}
|
|
31
|
-
|
|
59
|
+
if (typeof c === "string") throw new TypeError(`Color: ${show(c)} is not a CSS color`)
|
|
60
|
+
throw new TypeError(`Color: ${show(c)} is not a color — a string, an 0xRRGGBB number or [r, g, b(, a)] in 0..1`)
|
|
32
61
|
}
|
|
33
62
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
63
|
+
/** Normalize any ColorInput to four 0..1 float components; throws with the value when it is not one. */
|
|
64
|
+
const toFloats = (c: ColorInput): Rgba => parse(c) ?? notAColor(c)
|
|
65
|
+
|
|
66
|
+
// Bytes by the one rounding rule of the engines (anycanvas css::toByte: float32, clamped, half up).
|
|
67
|
+
const toBytes = (c: ColorInput): Rgba => {
|
|
68
|
+
const v = toFloats(c)
|
|
69
|
+
return [ cssToByte(v[0]), cssToByte(v[1]), cssToByte(v[2]), cssToByte(v[3]) ]
|
|
37
70
|
}
|
|
38
71
|
|
|
72
|
+
const hex2 = (n: number): string => (n < 16 ? "0" : "") + n.toString(16)
|
|
73
|
+
const floatsToHex = (v: Rgba): string =>
|
|
74
|
+
"#" + hex2(cssToByte(v[0])) + hex2(cssToByte(v[1])) + hex2(cssToByte(v[2])) + hex2(cssToByte(v[3]))
|
|
75
|
+
|
|
76
|
+
/** @internal '#rrggbbaa' of a NON-STRING color — the UI wire's own conversion (ui/styleColor.ts).
|
|
77
|
+
* Strings never come here: the UI core parses CSS itself with the same parser, so the SDK hands
|
|
78
|
+
* them over as written. Throws like `Color` when the value is not a color. */
|
|
79
|
+
export const numericColorToHex = (c: unknown): string => floatsToHex(parseNumeric(c) ?? notAColor(c))
|
|
80
|
+
|
|
39
81
|
export const Color = {
|
|
40
82
|
/** [r,g,b] in 0..1 — for the 2D engine's float color setters. */
|
|
41
83
|
toRgb01(c: ColorInput): [number, number, number] {
|
|
42
|
-
const
|
|
43
|
-
return [
|
|
84
|
+
const v = toFloats(c)
|
|
85
|
+
return [ v[0], v[1], v[2] ]
|
|
44
86
|
},
|
|
45
87
|
/** [r,g,b,a] in 0..1. */
|
|
46
88
|
toRgba01(c: ColorInput): [number, number, number, number] {
|
|
@@ -56,11 +98,12 @@ export const Color = {
|
|
|
56
98
|
const b = toBytes(c)
|
|
57
99
|
return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) >>> 0
|
|
58
100
|
},
|
|
59
|
-
/** '#
|
|
101
|
+
/** '#rrggbbaa' (lower-case, always eight digits) — the UI wire form and the 3D material color. */
|
|
60
102
|
toHexString(c: ColorInput): string {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
return floatsToHex(toFloats(c))
|
|
104
|
+
},
|
|
105
|
+
/** [r,g,b,a] in 0..1, or `null` when `c` is not a color — the non-throwing probe. */
|
|
106
|
+
tryParse(c: unknown): [number, number, number, number] | null {
|
|
107
|
+
return parse(c)
|
|
65
108
|
},
|
|
66
109
|
}
|
package/src/core/pins.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Pins — the JS side of owned handles (docs/plans/hosts-unification-plan.md, phase 10). A wrapper over a
|
|
2
|
+
// native resource holds `_h`, the handle the runtime returned; the runtime frees the resource once
|
|
3
|
+
// the wrapper is collected AND nothing native owns it. Native can only send an event to a resource
|
|
4
|
+
// it owns (a node in a tree / scene, a playing player), so those wrappers — and only those — are
|
|
5
|
+
// pinned here: kept alive and findable by id while native holds them. A loose wrapper is not in
|
|
6
|
+
// the map and collects freely. The SDK writes the map in the call that creates a native owner
|
|
7
|
+
// (append, add, setParent, show) and clears it on its own detach calls or when the runtime reports
|
|
8
|
+
// a free (`_creatorApp.setFreedEmitter`: a subtree the app dropped or destroyed).
|
|
9
|
+
|
|
10
|
+
/** The handle of a wrapper whose resource is gone: `id` 0, nothing to finalize. Replacing `_h`
|
|
11
|
+
* with it drops the real handle, so the runtime's record can go. */
|
|
12
|
+
export const DEAD_HANDLE: Handle = Object.freeze({ id: 0 })
|
|
13
|
+
|
|
14
|
+
export interface Pinned {
|
|
15
|
+
readonly _h: Handle
|
|
16
|
+
/** The runtime freed the resource underneath (an ancestor's free): mark the wrapper dead. */
|
|
17
|
+
_freed?(): void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Pins<T extends Pinned> {
|
|
21
|
+
private readonly map = new Map<number, T>()
|
|
22
|
+
/** Keep `obj` alive and findable while native owns its resource. */
|
|
23
|
+
pin(obj: T | null | undefined): void {
|
|
24
|
+
if (obj && obj._h.id !== 0) this.map.set(obj._h.id, obj)
|
|
25
|
+
}
|
|
26
|
+
unpin(id: number): void { this.map.delete(id) }
|
|
27
|
+
get(id: number): T | undefined { return this.map.get(id) }
|
|
28
|
+
has(id: number): boolean { return this.map.has(id) }
|
|
29
|
+
/** The runtime freed these resources: drop the pins, tell the wrappers. */
|
|
30
|
+
freed(ids: number[]): void {
|
|
31
|
+
for (const id of ids) {
|
|
32
|
+
const obj = this.map.get(id)
|
|
33
|
+
this.map.delete(id)
|
|
34
|
+
obj?._freed?.()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---- the freed channel ---------------------------------------------------------------------------
|
|
40
|
+
const byKind = new Map<number, Pins<any>>()
|
|
41
|
+
let installed = false
|
|
42
|
+
|
|
43
|
+
/** @internal Route the runtime's freed reports of `kind` (OWNED_KIND_*) to `pins`. Called lazily
|
|
44
|
+
* from the wrappers' constructors (a top-level call would be dropped by the bundler's DCE). */
|
|
45
|
+
export const registerFreedKind = (kind: number, pins: Pins<any>): void => {
|
|
46
|
+
byKind.set(kind, pins)
|
|
47
|
+
if (installed) return
|
|
48
|
+
if (typeof _creatorApp === "undefined" || typeof _creatorApp.setFreedEmitter !== "function") return // a test stub
|
|
49
|
+
installed = true
|
|
50
|
+
_creatorApp.setFreedEmitter((k, ids) => { byKind.get(k)?.freed(ids) })
|
|
51
|
+
}
|
package/src/core/signals.ts
CHANGED
|
@@ -80,7 +80,14 @@ const flush = () => {
|
|
|
80
80
|
}
|
|
81
81
|
const first: EffectImpl = queue.values().next().value!
|
|
82
82
|
queue.delete(first)
|
|
83
|
-
|
|
83
|
+
// One effect's error must not stop the others — nor every flush after it: escaping here would
|
|
84
|
+
// leave flushScheduled set, and no signal write would ever re-run anything again (on
|
|
85
|
+
// JavaScriptCore the rejected flush promise is not even reported — the app just freezes).
|
|
86
|
+
try {
|
|
87
|
+
first._run()
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error("signals: an effect threw (the others still run):", e instanceof Error ? (e.stack || e.message) : e)
|
|
90
|
+
}
|
|
84
91
|
}
|
|
85
92
|
flushScheduled = false
|
|
86
93
|
}
|
|
@@ -41,7 +41,7 @@ import { Vec2, cx, cy, type Vec2Like } from "../math/vec"
|
|
|
41
41
|
import type { CompAxis, CompWriter } from "../core/compWrite"
|
|
42
42
|
import { PhysicsGroup2D, _watchGroupChanges, _unwatchGroupChanges, categoryOf, solidMask } from "./groups2d"
|
|
43
43
|
import { Shape2D } from "./Shape2D"
|
|
44
|
-
import {
|
|
44
|
+
import { nodes2d, type Node2D } from "./Node2D"
|
|
45
45
|
|
|
46
46
|
/** Where the character's feet are, as reported by the solver after the last step. */
|
|
47
47
|
export type GroundState2D = "ground" | "slope" | "air"
|
|
@@ -192,7 +192,7 @@ export class CharacterController2D extends Aspect<"controller", Node2D> implemen
|
|
|
192
192
|
* the game can read. The engine already carries the character along a kinematic platform. */
|
|
193
193
|
get groundNode(): Node2D | null {
|
|
194
194
|
const id = this._state()[5]
|
|
195
|
-
return id ?
|
|
195
|
+
return id ? nodes2d.get(id) ?? null : null
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
@@ -210,7 +210,7 @@ export class CharacterController2D extends Aspect<"controller", Node2D> implemen
|
|
|
210
210
|
const o = _creator2d.characterGetCollisions(this._id, MAX_COLLISIONS)
|
|
211
211
|
const out: Collision2D[] = []
|
|
212
212
|
for (let i = 0; i + 2 < o.length; i += 3) {
|
|
213
|
-
out.push({ node: o[i] ?
|
|
213
|
+
out.push({ node: o[i] ? nodes2d.get(o[i]) ?? null : null, normal: new Vec2(o[i + 1], o[i + 2]) })
|
|
214
214
|
}
|
|
215
215
|
return out
|
|
216
216
|
}
|