lecodes-web-runtime 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/native/dist/creator-2d.js +2 -0
- package/native/dist/creator-2d.wasm +0 -0
- package/native/dist/creator-full.js +2 -0
- package/native/dist/creator-full.js.symbols +8124 -0
- package/native/dist/creator-full.wasm +0 -0
- package/native/dist/creator-headless.js +0 -0
- package/native/dist/creator-nav.js +14 -0
- package/native/dist/creator-ui.js +2 -0
- package/native/dist/creator-ui.wasm +0 -0
- package/native/exports/2d.json +119 -0
- package/native/exports/gl.json +166 -0
- package/native/exports/nav.json +3 -0
- package/native/exports/runtime.json +58 -0
- package/native/exports/ui.json +60 -0
- package/native/gen/2d.exports.json +109 -0
- package/native/gen/gl.exports.json +272 -0
- package/native/gen/nav.exports.json +24 -0
- package/package.json +30 -0
- package/src/assets/materials-world.bin +0 -0
- package/src/assets/materials.bin +0 -0
- package/src/assets/neutral_ibl128.ktx +0 -0
- package/src/assets/uberarchive.bin +0 -0
- package/src/gen/host.gen.ts +199 -0
- package/src/gen/materialGroups.gen.ts +12 -0
- package/src/gen/wasm.ts +156 -0
- package/src/jsb.ts +494 -0
- package/src/native.d.ts +23 -0
- package/src/profile.ts +50 -0
- package/src/validateGlb.ts +262 -0
- package/src/vite-env.d.ts +1 -0
- package/src/wasmCrashSymbolizer.ts +133 -0
package/src/gen/wasm.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// The wasm adapter of the generated web bridges — the runtime/native `js/args.h` of the web target
|
|
2
|
+
// (scripts/gen-bridges.ts, the TS emitter). A generated method turns its JS arguments into what the
|
|
3
|
+
// engine's exported C function takes (numbers as they are, strings and arrays copied into the heap,
|
|
4
|
+
// a fetch buffer id into a heap copy of the bytes) and turns the result back (a heap array into a
|
|
5
|
+
// Float32Array, a C string into a string, a struct into an object). The module is one of the
|
|
6
|
+
// runtime/web/native profiles: every engine shares its heap, so this adapter is the only place that
|
|
7
|
+
// knows about `HEAPF32`, `_malloc` and scratch memory.
|
|
8
|
+
//
|
|
9
|
+
// Scratch: every call runs inside `begin()` / `end(sp)` — a bump allocator over one heap block that
|
|
10
|
+
// is reset when the call returns (an argument copy never outlives the call). A copy that does not
|
|
11
|
+
// fit the block is `_malloc`ed and freed by `end`. Nothing here holds a heap view across a call that
|
|
12
|
+
// can grow the heap: the `HEAP*` getters read the module's CURRENT views every time.
|
|
13
|
+
|
|
14
|
+
/** The exports a creator-web profile provides (runtime/web/native/CMakeLists.txt). */
|
|
15
|
+
export type WasmModule = {
|
|
16
|
+
HEAPU8: Uint8Array; HEAPU16: Uint16Array; HEAP32: Int32Array; HEAPU32: Uint32Array; HEAPF32: Float32Array; HEAPF64: Float64Array
|
|
17
|
+
_malloc(bytes: number): number
|
|
18
|
+
_free(ptr: number): void
|
|
19
|
+
UTF8ToString(ptr: number, maxBytes?: number): string
|
|
20
|
+
stringToUTF8(s: string, ptr: number, maxBytes: number): void
|
|
21
|
+
lengthBytesUTF8(s: string): number
|
|
22
|
+
[fn: `_${string}`]: (...args: any[]) => any
|
|
23
|
+
} & Record<string, any>
|
|
24
|
+
|
|
25
|
+
/** What the host lends the bridges: the fetch store behind the `bytes` alias of the contract. */
|
|
26
|
+
export type WasmHost = {
|
|
27
|
+
getBuffer(systemId: number): ArrayBuffer | Uint8Array | null | undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SCRATCH_BYTES = 256 * 1024
|
|
31
|
+
|
|
32
|
+
export class Wasm {
|
|
33
|
+
readonly M: WasmModule
|
|
34
|
+
readonly host: WasmHost
|
|
35
|
+
private readonly base: number
|
|
36
|
+
private sp: number
|
|
37
|
+
private readonly heapAllocs: number[] = []
|
|
38
|
+
private readonly marks: number[] = []
|
|
39
|
+
|
|
40
|
+
constructor(module: WasmModule, host: WasmHost) {
|
|
41
|
+
this.M = module
|
|
42
|
+
this.host = host
|
|
43
|
+
this.base = module._malloc(SCRATCH_BYTES)
|
|
44
|
+
this.sp = this.base
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ---- scratch ----------------------------------------------------------------------------------
|
|
48
|
+
/** Open a call's scratch frame; `end(sp)` releases everything allocated after it. */
|
|
49
|
+
begin(): number { this.marks.push(this.heapAllocs.length); return this.sp }
|
|
50
|
+
end(sp: number): void {
|
|
51
|
+
this.sp = sp
|
|
52
|
+
const from = this.marks.pop() ?? 0
|
|
53
|
+
while (this.heapAllocs.length > from) this.M._free(this.heapAllocs.pop()!)
|
|
54
|
+
}
|
|
55
|
+
/** `bytes` of scratch, 8-aligned; falls back to the heap when the block is full. */
|
|
56
|
+
alloc(bytes: number): number {
|
|
57
|
+
const n = (Math.max(bytes, 1) + 7) & ~7
|
|
58
|
+
if (this.sp + n <= this.base + SCRATCH_BYTES) { const p = this.sp; this.sp += n; return p }
|
|
59
|
+
const p = this.M._malloc(n)
|
|
60
|
+
this.heapAllocs.push(p)
|
|
61
|
+
return p
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---- arguments in ---------------------------------------------------------------------------------
|
|
65
|
+
/** A NUL-terminated UTF-8 copy of `s`. */
|
|
66
|
+
str(s: string): number {
|
|
67
|
+
const n = this.M.lengthBytesUTF8(s) + 1
|
|
68
|
+
const p = this.alloc(n)
|
|
69
|
+
this.M.stringToUTF8(s, p, n)
|
|
70
|
+
return p
|
|
71
|
+
}
|
|
72
|
+
/** A heap copy of a typed array (or a plain number list as floats); `elem` = bytes per element. */
|
|
73
|
+
array(values: ArrayLike<number>, elem: 1 | 2 | 4 | 8, float = false): number {
|
|
74
|
+
const length = values.length
|
|
75
|
+
const p = this.alloc(length * elem)
|
|
76
|
+
if (length === 0) return p
|
|
77
|
+
if (ArrayBuffer.isView(values)) {
|
|
78
|
+
const v = values as unknown as ArrayBufferView
|
|
79
|
+
this.M.HEAPU8.set(new Uint8Array(v.buffer, v.byteOffset, v.byteLength), p)
|
|
80
|
+
} else if (float) this.M.HEAPF32.set(values as ArrayLike<number>, p >> 2)
|
|
81
|
+
else if (elem === 4) this.M.HEAPU32.set(values as ArrayLike<number>, p >> 2)
|
|
82
|
+
else if (elem === 2) this.M.HEAPU16.set(values as ArrayLike<number>, p >> 1)
|
|
83
|
+
else this.M.HEAPU8.set(values as ArrayLike<number>, p)
|
|
84
|
+
return p
|
|
85
|
+
}
|
|
86
|
+
f32(values: ArrayLike<number>): number { return this.array(values, 4, true) }
|
|
87
|
+
u8(values: ArrayLike<number>): number { return this.array(values, 1) }
|
|
88
|
+
u16(values: ArrayLike<number>): number { return this.array(values, 2) }
|
|
89
|
+
u32(values: ArrayLike<number>): number { return this.array(values, 4) }
|
|
90
|
+
i32(values: ArrayLike<number>): number { return this.array(values, 4) }
|
|
91
|
+
/** The bytes behind a fetch buffer id, copied into the heap; null when the store has nothing. */
|
|
92
|
+
bytes(systemId: number): { ptr: number, size: number } | null {
|
|
93
|
+
const buf = this.host.getBuffer(systemId)
|
|
94
|
+
if (!buf) return null
|
|
95
|
+
const view = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
|
96
|
+
if (view.byteLength === 0) return null
|
|
97
|
+
const p = this.alloc(view.byteLength)
|
|
98
|
+
this.M.HEAPU8.set(view, p)
|
|
99
|
+
return { ptr: p, size: view.byteLength }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---- results out ------------------------------------------------------------------------------------
|
|
103
|
+
/** Copy `n` heap elements at `ptr` into `into` (min(n, into.length) of them) — an engine-written array. */
|
|
104
|
+
readInto(ptr: number, n: number, into: ArrayBufferView, elem: 1 | 2 | 4): void {
|
|
105
|
+
const count = Math.min(n, into.byteLength / elem)
|
|
106
|
+
if (count <= 0) return
|
|
107
|
+
new Uint8Array(into.buffer, into.byteOffset, count * elem).set(this.M.HEAPU8.subarray(ptr, ptr + count * elem))
|
|
108
|
+
}
|
|
109
|
+
/** A NEW Float32Array of `n` floats read at `ptr`. */
|
|
110
|
+
newF32(ptr: number, n: number): Float32Array {
|
|
111
|
+
const out = new Float32Array(Math.max(n, 0))
|
|
112
|
+
if (n > 0) out.set(this.M.HEAPF32.subarray(ptr >> 2, (ptr >> 2) + n))
|
|
113
|
+
return out
|
|
114
|
+
}
|
|
115
|
+
/** A C string (borrowed pointer) as a JS string; 0 → "". */
|
|
116
|
+
cstr(ptr: number, maxBytes?: number): string { return ptr ? this.M.UTF8ToString(ptr, maxBytes) : "" }
|
|
117
|
+
/** A malloc'd C string the engine hands over: read, then freed. */
|
|
118
|
+
cstrFree(ptr: number): string {
|
|
119
|
+
if (!ptr) return ""
|
|
120
|
+
const s = this.M.UTF8ToString(ptr)
|
|
121
|
+
this.M._free(ptr)
|
|
122
|
+
return s
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Zero `bytes` at `p` (a struct the engine fills, a @pack struct before its fields are written). */
|
|
126
|
+
zero(p: number, bytes: number): void { this.M.HEAPU8.fill(0, p, p + bytes) }
|
|
127
|
+
// ---- struct fields (the struct rule's generated readers / the @pack writers) ---------------------------
|
|
128
|
+
wrU8(p: number, v: number): void { this.M.HEAPU8[p] = v }
|
|
129
|
+
wrU16(p: number, v: number): void { this.M.HEAPU16[p >> 1] = v }
|
|
130
|
+
wrI32(p: number, v: number): void { this.M.HEAP32[p >> 2] = v }
|
|
131
|
+
wrU32(p: number, v: number): void { this.M.HEAPU32[p >> 2] = v }
|
|
132
|
+
wrF32(p: number, v: number): void { this.M.HEAPF32[p >> 2] = v }
|
|
133
|
+
wrF64(p: number, v: number): void { this.M.HEAPF64[p >> 3] = v }
|
|
134
|
+
wrI64(p: number, v: number): void { this.M.HEAPU32[p >> 2] = v >>> 0; this.M.HEAP32[(p >> 2) + 1] = Math.floor(v / 4294967296) }
|
|
135
|
+
wrU64(p: number, v: number): void { this.M.HEAPU32[p >> 2] = v >>> 0; this.M.HEAPU32[(p >> 2) + 1] = Math.floor(v / 4294967296) }
|
|
136
|
+
/** `n` structs of `size` bytes starting at `p`, each read by `read`. */
|
|
137
|
+
rdList<T>(p: number, n: number, size: number, read: (q: number) => T): T[] {
|
|
138
|
+
const out: T[] = []
|
|
139
|
+
for (let i = 0; i < n; i++) out.push(read(p + i * size))
|
|
140
|
+
return out
|
|
141
|
+
}
|
|
142
|
+
rdU8(p: number): number { return this.M.HEAPU8[p]! }
|
|
143
|
+
rdU16(p: number): number { return this.M.HEAPU16[p >> 1]! }
|
|
144
|
+
rdI32(p: number): number { return this.M.HEAP32[p >> 2]! }
|
|
145
|
+
rdU32(p: number): number { return this.M.HEAPU32[p >> 2]! }
|
|
146
|
+
rdF32(p: number): number { return this.M.HEAPF32[p >> 2]! }
|
|
147
|
+
rdF64(p: number): number { return this.M.HEAPF64[p >> 3]! }
|
|
148
|
+
rdI64(p: number): number { return this.M.HEAPU32[p >> 2]! + this.M.HEAP32[(p >> 2) + 1]! * 4294967296 }
|
|
149
|
+
rdU64(p: number): number { return this.M.HEAPU32[p >> 2]! + this.M.HEAPU32[(p >> 2) + 1]! * 4294967296 }
|
|
150
|
+
/** `n` numbers of `elem` bytes at `p` (a `float pt[3]` field). */
|
|
151
|
+
rdNumbers(p: number, n: number, rd: (q: number) => number, elem: number): number[] {
|
|
152
|
+
const out: number[] = []
|
|
153
|
+
for (let i = 0; i < n; i++) out.push(rd.call(this, p + i * elem))
|
|
154
|
+
return out
|
|
155
|
+
}
|
|
156
|
+
}
|
package/src/jsb.ts
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// The JS side of runtime/native's wasm arm (runtime/native/src/js/wasm.cpp): the handle table its
|
|
2
|
+
// `jsb::Value`s index, the imports the arm calls (property access, calls, strings, typed arrays,
|
|
3
|
+
// throws), the wrappers that turn an installed handler table into JS functions, and the
|
|
4
|
+
// FinalizationRegistry behind a class finalizer (the owned `Handle`). One instance per module,
|
|
5
|
+
// installed as `Module.jsb` before `creatorWebInit`. Engine-agnostic: a browser or Bun.
|
|
6
|
+
//
|
|
7
|
+
// Values: a number is its IEEE bits (a NaN written canonical — the engine may canonicalize NaNs,
|
|
8
|
+
// so a tagged word never travels as a JS number); anything else is a NaN with the sign bit set, a
|
|
9
|
+
// TAG in bits 16–19 of the high word and the payload in the low word (wasm-support.h). Handles are
|
|
10
|
+
// refcounted: the arm's `dup` / `free` are `dup` / `release` here; objects and functions are deduped
|
|
11
|
+
// through a WeakMap so the arm's `same` holds; at zero references the slot is dropped and the value
|
|
12
|
+
// is the engine's to collect.
|
|
13
|
+
import type { WasmModule } from "./gen/wasm"
|
|
14
|
+
|
|
15
|
+
const TAG_UNDEFINED = 1, TAG_NULL = 2, TAG_BOOL = 3, TAG_OBJECT = 4, TAG_FUNCTION = 5, TAG_STRING = 6, TAG_ARRAY = 7, TAG_OTHER = 8, TAG_EXCEPTION = 15
|
|
16
|
+
const HI_BASE = 0xFFF00000
|
|
17
|
+
const hiOf = (tag: number) => (HI_BASE | (tag << 16)) >>> 0
|
|
18
|
+
const HI_UNDEFINED = hiOf(TAG_UNDEFINED), HI_NULL = hiOf(TAG_NULL), HI_BOOL = hiOf(TAG_BOOL), HI_EXCEPTION = hiOf(TAG_EXCEPTION)
|
|
19
|
+
const tagOf = (hi: number) => ((hi & 0xFFF00000) >>> 0) === HI_BASE ? (hi >>> 16) & 0xF : 0
|
|
20
|
+
|
|
21
|
+
/** A world the arm evaluates in: its global object (the bridges are installed on it) + evaluator. */
|
|
22
|
+
export type JsbWorld = {
|
|
23
|
+
global: object
|
|
24
|
+
evaluate(code: string, filename: string): unknown
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type JsbOptions = {
|
|
28
|
+
/** The boot world (context 1). */
|
|
29
|
+
world: JsbWorld
|
|
30
|
+
/** A fresh world for `_creatorApp.run` (a vm context headless); absent = the boot world is reused. */
|
|
31
|
+
newWorld?: () => JsbWorld
|
|
32
|
+
/** The host tables the generated forwarders call (`Module.jsb.host.<table>.<slot>`). */
|
|
33
|
+
host: Record<string, unknown>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ARG_STACK_BYTES = 64 * 1024
|
|
37
|
+
|
|
38
|
+
/** A settle group: the heap callbacks of one host slot call, released together after the first settle. */
|
|
39
|
+
type Group = { ptrs: number[], done: boolean }
|
|
40
|
+
|
|
41
|
+
export class Jsb {
|
|
42
|
+
readonly M: WasmModule
|
|
43
|
+
readonly host: Record<string, unknown>
|
|
44
|
+
private out = 0
|
|
45
|
+
// the table: index → value (0 unused); refs[i] = references the arm holds; free = released slots
|
|
46
|
+
private table: unknown[] = [undefined]
|
|
47
|
+
private refs: number[] = [0]
|
|
48
|
+
private freeSlots: number[] = []
|
|
49
|
+
private objIndex = new WeakMap<object, number>()
|
|
50
|
+
private pending: unknown = undefined
|
|
51
|
+
private hasPending = false
|
|
52
|
+
private worlds = new Map<number, JsbWorld>()
|
|
53
|
+
private nextWorld = 2
|
|
54
|
+
private readonly newWorld?: () => JsbWorld
|
|
55
|
+
// the argument stack the wrappers write call arguments into (nested calls bump it)
|
|
56
|
+
private argBase = 0
|
|
57
|
+
private argSp = 0
|
|
58
|
+
private readonly scratch = new DataView(new ArrayBuffer(8))
|
|
59
|
+
private readonly classRegistry: FinalizationRegistry<{ ref: WeakRef<object>, cls: number, opaque: number }>
|
|
60
|
+
// Every live class instance as a WeakRef: `gc()` sweeps them so a finalizer lands SYNCHRONOUSLY
|
|
61
|
+
// inside the arm's runGC (QuickJS semantics: `ownedStats` = collect + drain + count), instead of
|
|
62
|
+
// on the FinalizationRegistry's later task — which still covers the objects a natural collection takes.
|
|
63
|
+
private readonly tracked = new Map<object, { ref: WeakRef<object>, cls: number, opaque: number }>()
|
|
64
|
+
private readonly fnRegistry: FinalizationRegistry<number>
|
|
65
|
+
private readonly opaques = new WeakMap<object, { cls: number, opaque: number }>()
|
|
66
|
+
|
|
67
|
+
constructor(M: WasmModule, options: JsbOptions) {
|
|
68
|
+
this.M = M
|
|
69
|
+
this.host = options.host
|
|
70
|
+
this.worlds.set(1, options.world)
|
|
71
|
+
this.newWorld = options.newWorld
|
|
72
|
+
this.argBase = M._malloc(ARG_STACK_BYTES)
|
|
73
|
+
this.argSp = this.argBase
|
|
74
|
+
this.classRegistry = new FinalizationRegistry((token) => {
|
|
75
|
+
if (!this.tracked.delete(token)) return // swept already by gc()
|
|
76
|
+
this.M._jsbFinalize(token.cls, token.opaque)
|
|
77
|
+
})
|
|
78
|
+
this.fnRegistry = new FinalizationRegistry((k) => { this.M._jsbFunctionGone(k) })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- the arm's hooks --------------------------------------------------------------------------
|
|
82
|
+
_init(out: number) { this.out = out }
|
|
83
|
+
|
|
84
|
+
// ---- encoding ---------------------------------------------------------------------------------
|
|
85
|
+
/** Encode `v` at heap address `p` (8 bytes). A handle takes ONE reference (the caller owns it);
|
|
86
|
+
* the result is that handle's index, 0 for an immediate (a boolean's payload is NOT an index —
|
|
87
|
+
* returning it once released slot 1 under an unrelated function). */
|
|
88
|
+
private write(p: number, v: unknown): number {
|
|
89
|
+
const M = this.M
|
|
90
|
+
if (typeof v === "number") {
|
|
91
|
+
M.HEAPF64[p >> 3] = v !== v ? NaN : v // the engine writes a canonical NaN for NaN
|
|
92
|
+
return 0
|
|
93
|
+
}
|
|
94
|
+
let lo = 0, hi: number, idx = 0
|
|
95
|
+
if (v === undefined) hi = HI_UNDEFINED
|
|
96
|
+
else if (v === null) hi = HI_NULL
|
|
97
|
+
else if (typeof v === "boolean") { hi = HI_BOOL; lo = v ? 1 : 0 }
|
|
98
|
+
else {
|
|
99
|
+
lo = idx = this.alloc(v)
|
|
100
|
+
hi = hiOf(typeof v === "function" ? TAG_FUNCTION : typeof v === "string" ? TAG_STRING : Array.isArray(v) ? TAG_ARRAY : typeof v === "object" ? TAG_OBJECT : TAG_OTHER)
|
|
101
|
+
}
|
|
102
|
+
M.HEAPU32[p >> 2] = lo
|
|
103
|
+
M.HEAPU32[(p >> 2) + 1] = hi
|
|
104
|
+
return idx
|
|
105
|
+
}
|
|
106
|
+
private writeOut(v: unknown) { this.write(this.out, v) }
|
|
107
|
+
private writeOutException() { const M = this.M; M.HEAPU32[this.out >> 2] = 0; M.HEAPU32[(this.out >> 2) + 1] = HI_EXCEPTION }
|
|
108
|
+
/** Decode a (lo, hi) word pair; a handle's value is BORROWED (the reference stays). */
|
|
109
|
+
private decode(lo: number, hi: number): unknown {
|
|
110
|
+
const tag = tagOf(hi)
|
|
111
|
+
switch (tag) {
|
|
112
|
+
case 0: { this.scratch.setUint32(0, lo, true); this.scratch.setUint32(4, hi, true); return this.scratch.getFloat64(0, true) }
|
|
113
|
+
case TAG_UNDEFINED: case TAG_EXCEPTION: return undefined
|
|
114
|
+
case TAG_NULL: return null
|
|
115
|
+
case TAG_BOOL: return lo !== 0
|
|
116
|
+
default: return this.table[lo]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
private readAt(p: number): unknown {
|
|
120
|
+
const M = this.M
|
|
121
|
+
return this.decode(M.HEAPU32[p >> 2], M.HEAPU32[(p >> 2) + 1])
|
|
122
|
+
}
|
|
123
|
+
/** A `value` parameter of a host slot (the generated forwarders): the JS value whose word is at
|
|
124
|
+
* `p` — the app's own value, not a copy; undefined for a null address. */
|
|
125
|
+
valueAt(p: number): unknown { return p ? this.readAt(p) : undefined }
|
|
126
|
+
/** Read the result slot and TAKE the reference the arm handed over (an OWNED result). */
|
|
127
|
+
private takeOut(): { value: unknown, threw: boolean } {
|
|
128
|
+
const M = this.M
|
|
129
|
+
const lo = M.HEAPU32[this.out >> 2], hi = M.HEAPU32[(this.out >> 2) + 1]
|
|
130
|
+
const tag = tagOf(hi)
|
|
131
|
+
if (tag === TAG_EXCEPTION) return { value: undefined, threw: true }
|
|
132
|
+
const value = this.decode(lo, hi)
|
|
133
|
+
if (tag >= TAG_OBJECT && tag <= TAG_OTHER) this.release(lo)
|
|
134
|
+
return { value, threw: false }
|
|
135
|
+
}
|
|
136
|
+
private args(argc: number, argv: number): unknown[] {
|
|
137
|
+
const out: unknown[] = new Array(argc)
|
|
138
|
+
for (let i = 0; i < argc; i++) out[i] = this.readAt(argv + i * 8)
|
|
139
|
+
return out
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- the table ----------------------------------------------------------------------------------
|
|
143
|
+
private alloc(v: unknown): number {
|
|
144
|
+
const isObj = typeof v === "object" || typeof v === "function"
|
|
145
|
+
if (isObj) {
|
|
146
|
+
const hit = this.objIndex.get(v as object)
|
|
147
|
+
if (hit !== undefined) { this.refs[hit]++; return hit }
|
|
148
|
+
}
|
|
149
|
+
let i: number
|
|
150
|
+
if (this.freeSlots.length > 0) { i = this.freeSlots.pop()!; this.table[i] = v; this.refs[i] = 1 }
|
|
151
|
+
else { i = this.table.length; this.table.push(v); this.refs.push(1) }
|
|
152
|
+
if (isObj) this.objIndex.set(v as object, i)
|
|
153
|
+
return i
|
|
154
|
+
}
|
|
155
|
+
dup(idx: number) { if (idx > 0) this.refs[idx]++ }
|
|
156
|
+
release(idx: number) {
|
|
157
|
+
if (idx <= 0) return
|
|
158
|
+
if (this.refs[idx] <= 0) { console.warn(`[jsb] release of a dead handle ${idx} (${typeof this.table[idx]})`, new Error().stack?.split("\n").slice(2, 6).join("\n")); return }
|
|
159
|
+
if (--this.refs[idx] > 0) return
|
|
160
|
+
const v = this.table[idx]
|
|
161
|
+
if (typeof v === "object" || typeof v === "function") this.objIndex.delete(v as object)
|
|
162
|
+
this.table[idx] = undefined
|
|
163
|
+
this.freeSlots.push(idx)
|
|
164
|
+
}
|
|
165
|
+
/** Live handles (for tests). */
|
|
166
|
+
liveHandles(): number { return this.table.length - 1 - this.freeSlots.length }
|
|
167
|
+
|
|
168
|
+
// ---- worlds -------------------------------------------------------------------------------------
|
|
169
|
+
global(ctx: number) { this.writeOut(this.worlds.get(ctx)?.global ?? this.worlds.get(1)!.global) }
|
|
170
|
+
newContext(): number {
|
|
171
|
+
if (!this.newWorld) return 1
|
|
172
|
+
const id = this.nextWorld++
|
|
173
|
+
this.worlds.set(id, this.newWorld())
|
|
174
|
+
return id
|
|
175
|
+
}
|
|
176
|
+
freeContext(ctx: number) { if (ctx !== 1) this.worlds.delete(ctx) }
|
|
177
|
+
eval(ctx: number, code: string, filename: string) {
|
|
178
|
+
const w = this.worlds.get(ctx) ?? this.worlds.get(1)!
|
|
179
|
+
try { this.writeOut(w.evaluate(code, filename)) }
|
|
180
|
+
catch (e) { this.pending = e; this.hasPending = true; this.writeOutException() }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---- objects ----------------------------------------------------------------------------------
|
|
184
|
+
get(lo: number, hi: number, key: string) {
|
|
185
|
+
const o = this.decode(lo, hi) as any
|
|
186
|
+
if (o === undefined || o === null) { this.writeOut(undefined); return }
|
|
187
|
+
try { this.writeOut(o[key]) }
|
|
188
|
+
catch (e) { this.pending = e; this.hasPending = true; this.writeOutException() }
|
|
189
|
+
}
|
|
190
|
+
getIndex(lo: number, hi: number, i: number) {
|
|
191
|
+
const o = this.decode(lo, hi) as any
|
|
192
|
+
if (o === undefined || o === null) { this.writeOut(undefined); return }
|
|
193
|
+
try { this.writeOut(o[i]) }
|
|
194
|
+
catch (e) { this.pending = e; this.hasPending = true; this.writeOutException() }
|
|
195
|
+
}
|
|
196
|
+
has(lo: number, hi: number, key: string): boolean {
|
|
197
|
+
const o = this.decode(lo, hi)
|
|
198
|
+
return (typeof o === "object" || typeof o === "function") && o !== null && key in (o as object)
|
|
199
|
+
}
|
|
200
|
+
set(obj: number, key: string, lo: number, hi: number) {
|
|
201
|
+
const o = this.table[obj] as any
|
|
202
|
+
if (o === undefined || o === null) return
|
|
203
|
+
o[key] = this.decode(lo, hi)
|
|
204
|
+
}
|
|
205
|
+
setIndex(obj: number, i: number, lo: number, hi: number) {
|
|
206
|
+
const o = this.table[obj] as any
|
|
207
|
+
if (o === undefined || o === null) return
|
|
208
|
+
o[i] = this.decode(lo, hi)
|
|
209
|
+
}
|
|
210
|
+
keys(lo: number, hi: number, countOut: number): number {
|
|
211
|
+
const M = this.M
|
|
212
|
+
const o = this.decode(lo, hi)
|
|
213
|
+
const ks = (typeof o === "object" || typeof o === "function") && o !== null ? Object.keys(o as object) : []
|
|
214
|
+
let total = 1
|
|
215
|
+
for (const k of ks) total += M.lengthBytesUTF8(k) + 1
|
|
216
|
+
const p = M._malloc(total)
|
|
217
|
+
let off = p
|
|
218
|
+
for (const k of ks) { const n = M.lengthBytesUTF8(k) + 1; M.stringToUTF8(k, off, n); off += n }
|
|
219
|
+
M.HEAPU32[countOut >> 2] = ks.length
|
|
220
|
+
return p
|
|
221
|
+
}
|
|
222
|
+
newObject(classId: number, opaque: number) {
|
|
223
|
+
const o: Record<string, unknown> = {}
|
|
224
|
+
if (classId > 0) {
|
|
225
|
+
this.opaques.set(o, { cls: classId, opaque })
|
|
226
|
+
const token = { ref: new WeakRef(o), cls: classId, opaque }
|
|
227
|
+
this.tracked.set(token, token)
|
|
228
|
+
this.classRegistry.register(o, token, token)
|
|
229
|
+
}
|
|
230
|
+
this.writeOut(o)
|
|
231
|
+
}
|
|
232
|
+
newArray() { this.writeOut([]) }
|
|
233
|
+
opaqueOf(idx: number, classId: number): number {
|
|
234
|
+
const o = this.table[idx]
|
|
235
|
+
if (typeof o !== "object" || o === null) return 0
|
|
236
|
+
const e = this.opaques.get(o)
|
|
237
|
+
return e && e.cls === classId ? e.opaque : 0
|
|
238
|
+
}
|
|
239
|
+
json(lo: number, hi: number) {
|
|
240
|
+
let s: unknown
|
|
241
|
+
try { s = JSON.stringify(this.decode(lo, hi)) } catch { s = undefined }
|
|
242
|
+
this.writeOut(s)
|
|
243
|
+
}
|
|
244
|
+
parseJson(text: string) {
|
|
245
|
+
try { this.writeOut(JSON.parse(text)) }
|
|
246
|
+
catch (e) { this.pending = e; this.hasPending = true; this.writeOutException() }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---- strings + numbers ---------------------------------------------------------------------------
|
|
250
|
+
newString(s: string) { this.writeOut(s) }
|
|
251
|
+
str(lo: number, hi: number, lenOut: number): number {
|
|
252
|
+
const M = this.M
|
|
253
|
+
const v = this.decode(lo, hi)
|
|
254
|
+
let s: string
|
|
255
|
+
try { s = typeof v === "string" ? v : String(v) } catch { return 0 } // a Symbol
|
|
256
|
+
const n = M.lengthBytesUTF8(s)
|
|
257
|
+
const p = M._malloc(n + 1)
|
|
258
|
+
M.stringToUTF8(s, p, n + 1)
|
|
259
|
+
M.HEAPU32[lenOut >> 2] = n
|
|
260
|
+
return p
|
|
261
|
+
}
|
|
262
|
+
logString(lo: number, hi: number): number {
|
|
263
|
+
const M = this.M
|
|
264
|
+
const v = this.decode(lo, hi)
|
|
265
|
+
let s: string
|
|
266
|
+
try { s = String(v) } catch { s = "" }
|
|
267
|
+
if (v instanceof Error && typeof v.stack === "string" && v.stack.length > 0) {
|
|
268
|
+
const stack = v.stack.startsWith(s) ? v.stack.slice(s.length).replace(/^\n/, "") : v.stack
|
|
269
|
+
if (stack.trim().length > 0) s += "\n" + stack.replace(/[\r\n]+$/, "")
|
|
270
|
+
}
|
|
271
|
+
const n = M.lengthBytesUTF8(s)
|
|
272
|
+
const p = M._malloc(n + 1)
|
|
273
|
+
M.stringToUTF8(s, p, n + 1)
|
|
274
|
+
return p
|
|
275
|
+
}
|
|
276
|
+
toNumber(idx: number): number { try { return Number(this.table[idx]) } catch { return NaN } }
|
|
277
|
+
truthy(idx: number): boolean { return !!this.table[idx] }
|
|
278
|
+
|
|
279
|
+
// ---- errors --------------------------------------------------------------------------------------
|
|
280
|
+
newError(message: string) { this.writeOut(new Error(message)) }
|
|
281
|
+
throwNew(kind: number, message: string) {
|
|
282
|
+
this.pending = kind === 1 ? new TypeError(message) : new Error(message)
|
|
283
|
+
this.hasPending = true
|
|
284
|
+
}
|
|
285
|
+
takeException() {
|
|
286
|
+
const e = this.hasPending ? this.pending : undefined
|
|
287
|
+
this.pending = undefined
|
|
288
|
+
this.hasPending = false
|
|
289
|
+
this.writeOut(e)
|
|
290
|
+
}
|
|
291
|
+
private throwPending(): never {
|
|
292
|
+
const e = this.hasPending ? this.pending : new Error("native handler failed")
|
|
293
|
+
this.pending = undefined
|
|
294
|
+
this.hasPending = false
|
|
295
|
+
throw e
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---- calls ---------------------------------------------------------------------------------------
|
|
299
|
+
call(_ctx: number, fn: number, thisLo: number, thisHi: number, argc: number, argv: number) {
|
|
300
|
+
const f = this.table[fn] as (...a: unknown[]) => unknown
|
|
301
|
+
const thisv = this.decode(thisLo, thisHi)
|
|
302
|
+
const args = this.args(argc, argv)
|
|
303
|
+
try { this.writeOut(f.apply(thisv, args)) }
|
|
304
|
+
catch (e) { this.pending = e; this.hasPending = true; this.writeOutException() }
|
|
305
|
+
}
|
|
306
|
+
construct(ctor: number, argc: number, argv: number) {
|
|
307
|
+
const c = this.table[ctor] as new (...a: unknown[]) => unknown
|
|
308
|
+
const args = this.args(argc, argv)
|
|
309
|
+
try { this.writeOut(Reflect.construct(c, args)) }
|
|
310
|
+
catch (e) { this.pending = e; this.hasPending = true; this.writeOutException() }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---- typed arrays ---------------------------------------------------------------------------------
|
|
314
|
+
typedArray(kind: number, data: number, bytes: number) {
|
|
315
|
+
const src = this.M.HEAPU8.slice(data, data + bytes) // a copy: the array owns its buffer
|
|
316
|
+
const buf = src.buffer as ArrayBuffer
|
|
317
|
+
const ctors = [null, Uint8Array, Uint16Array, Uint32Array, Int32Array, Float32Array, Float64Array, Int16Array, Int8Array, Uint8ClampedArray] as const
|
|
318
|
+
if (kind === 0) { this.writeOut(buf); return }
|
|
319
|
+
const C = ctors[kind]
|
|
320
|
+
this.writeOut(C ? new C(buf) : undefined)
|
|
321
|
+
}
|
|
322
|
+
/** mode 0: a typed array of `elem`-byte elements (size = elements); 1: any typed array's bytes;
|
|
323
|
+
* 2: an ArrayBuffer. A malloc'd copy the arm frees (a 1-byte block for an empty array), 0 when
|
|
324
|
+
* the value is not such an array. */
|
|
325
|
+
arrayIn(idx: number, elem: number, mode: number, sizeOut: number): number {
|
|
326
|
+
const M = this.M
|
|
327
|
+
const v = this.table[idx]
|
|
328
|
+
let bytes: Uint8Array | null = null
|
|
329
|
+
let size = 0
|
|
330
|
+
if (mode === 2) {
|
|
331
|
+
if (!(v instanceof ArrayBuffer)) return 0
|
|
332
|
+
bytes = new Uint8Array(v); size = bytes.byteLength
|
|
333
|
+
} else {
|
|
334
|
+
if (!ArrayBuffer.isView(v) || v instanceof DataView) return 0
|
|
335
|
+
const ta = v as Uint8Array
|
|
336
|
+
const bpe = (ta as unknown as { BYTES_PER_ELEMENT: number }).BYTES_PER_ELEMENT
|
|
337
|
+
if (mode === 0 && bpe !== elem) return 0
|
|
338
|
+
bytes = new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength)
|
|
339
|
+
size = mode === 0 ? ta.length : ta.byteLength
|
|
340
|
+
}
|
|
341
|
+
const p = M._malloc(Math.max(bytes.byteLength, 1))
|
|
342
|
+
if (bytes.byteLength > 0) M.HEAPU8.set(bytes, p)
|
|
343
|
+
M.HEAPU32[sizeOut >> 2] = size
|
|
344
|
+
return p
|
|
345
|
+
}
|
|
346
|
+
arrayOut(idx: number, data: number, bytes: number) {
|
|
347
|
+
const v = this.table[idx]
|
|
348
|
+
let dst: Uint8Array
|
|
349
|
+
if (v instanceof ArrayBuffer) dst = new Uint8Array(v)
|
|
350
|
+
else if (ArrayBuffer.isView(v)) dst = new Uint8Array(v.buffer, v.byteOffset, v.byteLength)
|
|
351
|
+
else return
|
|
352
|
+
const n = Math.min(bytes, dst.byteLength)
|
|
353
|
+
if (n > 0) dst.set(this.M.HEAPU8.subarray(data, data + n))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ---- handler tables + native functions --------------------------------------------------------
|
|
357
|
+
/** Run an installed handler with `args`: the arguments go onto the arg stack, the result comes
|
|
358
|
+
* back through the slot (an exception re-thrown here). */
|
|
359
|
+
private invoke(call: (argc: number, argv: number) => void, args: unknown[]): unknown {
|
|
360
|
+
const argc = args.length
|
|
361
|
+
const sp = this.argSp
|
|
362
|
+
let argv = sp
|
|
363
|
+
let heapArgv = 0
|
|
364
|
+
if (sp + argc * 8 > this.argBase + ARG_STACK_BYTES) { heapArgv = this.M._malloc(Math.max(argc * 8, 8)); argv = heapArgv }
|
|
365
|
+
else this.argSp = sp + argc * 8
|
|
366
|
+
const owned: number[] = []
|
|
367
|
+
for (let i = 0; i < argc; i++) { const idx = this.write(argv + i * 8, args[i]); if (idx) owned.push(idx) }
|
|
368
|
+
let r: { value: unknown, threw: boolean }
|
|
369
|
+
try {
|
|
370
|
+
call(argc, argv)
|
|
371
|
+
r = this.takeOut()
|
|
372
|
+
} finally {
|
|
373
|
+
for (const idx of owned) this.release(idx)
|
|
374
|
+
this.argSp = sp
|
|
375
|
+
if (heapArgv) this.M._free(heapArgv)
|
|
376
|
+
}
|
|
377
|
+
if (r.threw) this.throwPending()
|
|
378
|
+
return r.value
|
|
379
|
+
}
|
|
380
|
+
defineMethod(ctx: number, obj: number, name: string, fnIndex: number, length: number, getter: number) {
|
|
381
|
+
const o = this.table[obj] as object
|
|
382
|
+
if (getter) {
|
|
383
|
+
Object.defineProperty(o, name, { configurable: true, enumerable: false, get: () => this.invoke(() => this.M._jsbInvokeGetter(fnIndex), []) })
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
const M = this.M
|
|
387
|
+
const f = (...args: unknown[]) => this.invoke((argc, argv) => M._jsbInvoke(ctx, fnIndex, argc, argv), args)
|
|
388
|
+
Object.defineProperty(f, "name", { value: name })
|
|
389
|
+
Object.defineProperty(f, "length", { value: length })
|
|
390
|
+
Object.defineProperty(o, name, { configurable: true, enumerable: false, writable: true, value: f })
|
|
391
|
+
}
|
|
392
|
+
makeFunction(ctx: number, k: number, name: string, length: number) {
|
|
393
|
+
const M = this.M
|
|
394
|
+
const f = (...args: unknown[]) => this.invoke((argc, argv) => M._jsbInvokeData(ctx, k, argc, argv), args)
|
|
395
|
+
if (name) Object.defineProperty(f, "name", { value: name })
|
|
396
|
+
Object.defineProperty(f, "length", { value: length })
|
|
397
|
+
this.fnRegistry.register(f, k)
|
|
398
|
+
this.writeOut(f)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ---- host callbacks (the generated forwarders) -----------------------------------------------------
|
|
402
|
+
/** A JS function for a JSCallback heap pointer a host slot received: calling it posts the call
|
|
403
|
+
* through the runtime's dispatch queue (the next tick runs it). `group` = the slot's settle
|
|
404
|
+
* group (released after the first settle; a second settle is ignored); null = a long-lived
|
|
405
|
+
* listener the runtime frees itself. A zero pointer (an optional callback the app left out) is
|
|
406
|
+
* a no-op. */
|
|
407
|
+
callback(ptr: number, group: Group | null): (...args: unknown[]) => void {
|
|
408
|
+
if (!ptr) return () => {}
|
|
409
|
+
return (...args: unknown[]) => {
|
|
410
|
+
if (group) {
|
|
411
|
+
if (group.done) { console.warn("[jsb] a settled host callback was called again"); return }
|
|
412
|
+
group.done = true
|
|
413
|
+
}
|
|
414
|
+
const M = this.M
|
|
415
|
+
const argc = args.length
|
|
416
|
+
const n = group ? group.ptrs.length : 0
|
|
417
|
+
const p = M._malloc(Math.max(argc * 8 + n * 4, 8))
|
|
418
|
+
const owned: number[] = []
|
|
419
|
+
for (let i = 0; i < argc; i++) { const idx = this.write(p + i * 8, args[i]); if (idx) owned.push(idx) }
|
|
420
|
+
const gp = p + argc * 8
|
|
421
|
+
for (let i = 0; i < n; i++) M.HEAPU32[(gp >> 2) + i] = group!.ptrs[i]
|
|
422
|
+
M._jsbHostCall(ptr, argc, p, gp, n)
|
|
423
|
+
for (const idx of owned) this.release(idx)
|
|
424
|
+
M._free(p)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** A native view's event, host → app (`channel.emit` of a view a TS host mounted): the payload
|
|
428
|
+
* crosses as the value it is — the runtime retains it until its dispatch queue delivers the
|
|
429
|
+
* event. No payload = the listener gets none. */
|
|
430
|
+
viewEvent(viewId: number, event: string, data?: unknown): void {
|
|
431
|
+
const M = this.M
|
|
432
|
+
const e = M.stringToNewUTF8(event)
|
|
433
|
+
if (data === undefined) { M._jsbViewEvent(viewId, e, 0); M._free(e); return }
|
|
434
|
+
const p = M._malloc(8)
|
|
435
|
+
const idx = this.write(p, data)
|
|
436
|
+
M._jsbViewEvent(viewId, e, p)
|
|
437
|
+
if (idx) this.release(idx)
|
|
438
|
+
M._free(p)
|
|
439
|
+
M._free(e)
|
|
440
|
+
}
|
|
441
|
+
/** A settle group for the callbacks of one slot call. */
|
|
442
|
+
group(...ptrs: number[]): Group { return { ptrs: ptrs.filter((x) => x !== 0), done: false } }
|
|
443
|
+
/** A `const char* const*` + count → strings (the generated forwarders' string[] parameters). */
|
|
444
|
+
strings(ptr: number, count: number): string[] {
|
|
445
|
+
const M = this.M
|
|
446
|
+
const out: string[] = new Array(count)
|
|
447
|
+
for (let i = 0; i < count; i++) { const p = M.HEAPU32[(ptr >> 2) + i]; out[i] = p ? M.UTF8ToString(p) : "" }
|
|
448
|
+
return out
|
|
449
|
+
}
|
|
450
|
+
/** A host's byte result → a malloc'd, NUL-terminated copy the runtime frees (0 for none); the
|
|
451
|
+
* byte count goes to `outSize` when the slot has one. */
|
|
452
|
+
bytesOut(r: unknown, outSize: number): number {
|
|
453
|
+
const M = this.M
|
|
454
|
+
let bytes: Uint8Array | null = null
|
|
455
|
+
if (r instanceof Uint8Array) bytes = r
|
|
456
|
+
else if (r instanceof ArrayBuffer) bytes = new Uint8Array(r)
|
|
457
|
+
else if (ArrayBuffer.isView(r)) bytes = new Uint8Array(r.buffer, r.byteOffset, r.byteLength)
|
|
458
|
+
if (!bytes || bytes.byteLength === 0) { if (outSize) M.HEAPU32[outSize >> 2] = 0; return 0 }
|
|
459
|
+
const p = M._malloc(bytes.byteLength + 1)
|
|
460
|
+
M.HEAPU8.set(bytes, p)
|
|
461
|
+
M.HEAPU8[p + bytes.byteLength] = 0
|
|
462
|
+
if (outSize) M.HEAPU32[outSize >> 2] = bytes.byteLength
|
|
463
|
+
return p
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
gc() {
|
|
467
|
+
const g = (globalThis as { gc?: () => void }).gc
|
|
468
|
+
const bun = (globalThis as { Bun?: { gc?: (force: boolean) => void } }).Bun
|
|
469
|
+
// twice: the first full collection leaves what only became unreachable during it (the weak
|
|
470
|
+
// processing of the sweep below is one such source); the second takes it
|
|
471
|
+
for (let i = 0; i < 2; i++) {
|
|
472
|
+
if (bun?.gc) bun.gc(true)
|
|
473
|
+
else if (typeof g === "function") g()
|
|
474
|
+
else return
|
|
475
|
+
this.sweep()
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/** Collected class instances finalize now, not on the registry's later task. */
|
|
479
|
+
private sweep() {
|
|
480
|
+
for (const token of [...this.tracked.keys()] as { ref: WeakRef<object>, cls: number, opaque: number }[]) {
|
|
481
|
+
if (token.ref.deref() !== undefined) continue
|
|
482
|
+
this.tracked.delete(token)
|
|
483
|
+
this.classRegistry.unregister(token)
|
|
484
|
+
this.M._jsbFinalize(token.cls, token.opaque)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Create the glue for a loaded runtime module and install it as `Module.jsb`. */
|
|
490
|
+
export const installJsb = (M: WasmModule, options: JsbOptions): Jsb => {
|
|
491
|
+
const jsb = new Jsb(M, options)
|
|
492
|
+
;(M as unknown as { jsb: Jsb }).jsb = jsb
|
|
493
|
+
return jsb
|
|
494
|
+
}
|
package/src/native.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The wasm profiles of runtime/web/native (build-wasm.sh → native/dist): Emscripten ES6 modules whose
|
|
2
|
+
// default export is the module factory. Typed loosely on purpose — the generated bridges type the
|
|
3
|
+
// exports they call through the Wasm adapter (src/gen/wasm.ts).
|
|
4
|
+
declare module "*creator-full.js" {
|
|
5
|
+
const factory: (args?: Record<string, unknown>) => Promise<any>
|
|
6
|
+
export default factory
|
|
7
|
+
}
|
|
8
|
+
declare module "*creator-2d.js" {
|
|
9
|
+
const factory: (args?: Record<string, unknown>) => Promise<any>
|
|
10
|
+
export default factory
|
|
11
|
+
}
|
|
12
|
+
declare module "*creator-ui.js" {
|
|
13
|
+
const factory: (args?: Record<string, unknown>) => Promise<any>
|
|
14
|
+
export default factory
|
|
15
|
+
}
|
|
16
|
+
declare module "*creator-nav.js" {
|
|
17
|
+
const factory: (args?: Record<string, unknown>) => Promise<any>
|
|
18
|
+
export default factory
|
|
19
|
+
}
|
|
20
|
+
declare module "*creator-headless.js" {
|
|
21
|
+
const factory: (args?: Record<string, unknown>) => Promise<any>
|
|
22
|
+
export default factory
|
|
23
|
+
}
|