@twip/web 0.0.0-canary-f16fa12

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 (82) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +35 -0
  3. package/dist/THIRD-PARTY-NOTICES.md +5457 -0
  4. package/dist/commands.d.ts +637 -0
  5. package/dist/commands.js +188 -0
  6. package/dist/editor.d.ts +218 -0
  7. package/dist/editor.js +460 -0
  8. package/dist/errors.d.ts +21 -0
  9. package/dist/errors.js +26 -0
  10. package/dist/events.d.ts +126 -0
  11. package/dist/events.js +1 -0
  12. package/dist/fonts/google.d.ts +3 -0
  13. package/dist/fonts/google.js +39 -0
  14. package/dist/fonts/library.d.ts +65 -0
  15. package/dist/fonts/library.js +130 -0
  16. package/dist/host/a11y.d.ts +3 -0
  17. package/dist/host/a11y.js +62 -0
  18. package/dist/host/chrome.d.ts +28 -0
  19. package/dist/host/chrome.js +144 -0
  20. package/dist/host/clipboard.d.ts +16 -0
  21. package/dist/host/clipboard.js +128 -0
  22. package/dist/host/cursors.d.ts +15 -0
  23. package/dist/host/cursors.js +5 -0
  24. package/dist/host/dom.d.ts +45 -0
  25. package/dist/host/dom.js +122 -0
  26. package/dist/host/frames.d.ts +14 -0
  27. package/dist/host/frames.js +87 -0
  28. package/dist/host/gpu.d.ts +58 -0
  29. package/dist/host/gpu.js +79 -0
  30. package/dist/host/ime.d.ts +3 -0
  31. package/dist/host/ime.js +11 -0
  32. package/dist/host/input.d.ts +12 -0
  33. package/dist/host/input.js +86 -0
  34. package/dist/host/keys.d.ts +16 -0
  35. package/dist/host/keys.js +27 -0
  36. package/dist/host/mount.d.ts +11 -0
  37. package/dist/host/mount.js +72 -0
  38. package/dist/host/pointer.d.ts +4 -0
  39. package/dist/host/pointer.js +196 -0
  40. package/dist/host/scroll.d.ts +14 -0
  41. package/dist/host/scroll.js +95 -0
  42. package/dist/index.d.ts +27 -0
  43. package/dist/index.js +10 -0
  44. package/dist/review.d.ts +156 -0
  45. package/dist/review.js +38 -0
  46. package/dist/services/accessibility.d.ts +52 -0
  47. package/dist/services/accessibility.js +71 -0
  48. package/dist/services/dialogs.d.ts +181 -0
  49. package/dist/services/dialogs.js +18 -0
  50. package/dist/services/elements.d.ts +146 -0
  51. package/dist/services/elements.js +18 -0
  52. package/dist/services/find.d.ts +50 -0
  53. package/dist/services/find.js +12 -0
  54. package/dist/services/menus.d.ts +82 -0
  55. package/dist/services/menus.js +8 -0
  56. package/dist/services/navigation.d.ts +58 -0
  57. package/dist/services/navigation.js +12 -0
  58. package/dist/services/ruler.d.ts +95 -0
  59. package/dist/services/ruler.js +16 -0
  60. package/dist/services/spelling.d.ts +34 -0
  61. package/dist/services/spelling.js +14 -0
  62. package/dist/services/view.d.ts +70 -0
  63. package/dist/services/view.js +19 -0
  64. package/dist/state.d.ts +483 -0
  65. package/dist/state.js +1 -0
  66. package/dist/transport/direct.d.ts +31 -0
  67. package/dist/transport/direct.js +405 -0
  68. package/dist/transport/module.d.ts +51 -0
  69. package/dist/transport/module.js +17 -0
  70. package/dist/transport/protocol.d.ts +265 -0
  71. package/dist/transport/protocol.js +132 -0
  72. package/dist/transport/worker.d.ts +54 -0
  73. package/dist/transport/worker.js +203 -0
  74. package/dist/twip-wasm.js +2 -0
  75. package/dist/twip_wasm.wasm +0 -0
  76. package/dist/types.d.ts +134 -0
  77. package/dist/types.js +16 -0
  78. package/dist/worker/entry.d.ts +1 -0
  79. package/dist/worker/entry.js +53 -0
  80. package/dist/worker.d.ts +1 -0
  81. package/dist/worker.js +2 -0
  82. package/package.json +30 -0
@@ -0,0 +1,405 @@
1
+ // The engine on the calling thread. One module instance and one engine per page, shared by every editor, so fonts,
2
+ // dictionaries and the internal clipboard are shared. Every call goes through one ordered queue that drains in a
3
+ // microtask: a command called from a listener runs after the current dispatch.
4
+ import { EditorError } from '../errors.js';
5
+ import { attachWebGpu, frameWebGl, frameWebGpu, gpuDevice, restoreWebGl, watchWebGl, webGlTarget, webGpuTarget, writeWebGpu, } from '../host/gpu.js';
6
+ import { loadModule } from './module.js';
7
+ import { Kind, Method, unwrap } from './protocol.js';
8
+ const encoder = new TextEncoder();
9
+ const decoder = new TextDecoder();
10
+ /** Status codes of the C ABI, from 1, for responses that carry no error JSON. */
11
+ const statusCodes = [
12
+ 'destroyed',
13
+ 'invalid-argument',
14
+ 'unavailable',
15
+ 'read-only',
16
+ 'import-failed',
17
+ 'export-failed',
18
+ 'aborted',
19
+ 'not-allowed',
20
+ 'unsupported',
21
+ 'internal',
22
+ ];
23
+ /** A `TwipBuf` is `{ ptr, len }`: two 32-bit words in wasm32. `TwipFrameInfo` is five f64 then two u32. */
24
+ const BUF_SIZE = 8;
25
+ const FRAME_INFO_SIZE = 48;
26
+ /** Lost WebGPU devices replaced by new ones the engine adopts. Each leaks the engine's GPU context for the old one
27
+ * (Skia can't free a lost device's context), so after these the canvases show CPU pixels copied in. */
28
+ const MAX_DEVICE_LOSSES = 3;
29
+ let shared;
30
+ /** The page's one direct transport, loading the engine module on first use. */
31
+ export function directTransport() {
32
+ shared ??= DirectTransport.load().catch((cause) => {
33
+ shared = undefined;
34
+ throw new EditorError('internal', `the engine module failed to load: ${String(cause)}`, { cause });
35
+ });
36
+ return shared;
37
+ }
38
+ /** The {@link Transport} that calls the engine module on this thread. */
39
+ export class DirectTransport {
40
+ #m;
41
+ #engine;
42
+ #queue = [];
43
+ #draining = false;
44
+ /** Messages from the engine during the running operation, delivered after it returns. */
45
+ #pending = [];
46
+ #listeners = new Map();
47
+ #timers = new Map();
48
+ #canvases = new Map();
49
+ /** The page's WebGPU device once the engine adopted it; null without one. */
50
+ #gpu;
51
+ #losses = 0;
52
+ /** Set when the module trapped: it can't be trusted again. */
53
+ #fault;
54
+ constructor(module, engine, pending) {
55
+ this.#m = module;
56
+ this.#engine = engine;
57
+ this.#pending = pending;
58
+ }
59
+ /** Loads the module and creates the page's engine. */
60
+ static async load() {
61
+ const pending = [];
62
+ let module;
63
+ let transport;
64
+ module = await loadModule((kind, sessionId, ptr, len) => {
65
+ // The payload is valid only during the callback: decode (copy) it now, act on it after the operation.
66
+ const payload = kind === Kind.RequestFrame || len === 0 ? undefined : JSON.parse(decoder.decode(module.HEAPU8.subarray(ptr, ptr + len)));
67
+ (transport ? transport.#pending : pending).push({ kind, sessionId, payload });
68
+ });
69
+ const options = encoder.encode('{}');
70
+ const at = module._twip_alloc(options.length);
71
+ module.HEAPU8.set(options, at);
72
+ const engine = module._twip_engine_new(at, options.length, 0, 0);
73
+ module._twip_free(at, options.length);
74
+ if (!engine)
75
+ throw new Error('twip_engine_new failed');
76
+ transport = new DirectTransport(module, engine, pending);
77
+ return transport;
78
+ }
79
+ createSession(options) {
80
+ return this.#enqueue(() => this.#withBytes(encoder.encode(JSON.stringify(options)), (p, n) => this.#m._twip_session_new(this.#engine, p, n)));
81
+ }
82
+ closeSession(session) {
83
+ return this.#enqueue(() => {
84
+ for (const t of this.#timers.get(session) ?? [])
85
+ clearTimeout(t);
86
+ this.#timers.delete(session);
87
+ const c = this.#canvases.get(session);
88
+ if (c?.backend === 'webgl')
89
+ c.unwatch();
90
+ this.#canvases.delete(session);
91
+ this.#m._twip_session_free(this.#engine, session);
92
+ }).finally(() => this.#listeners.delete(session));
93
+ }
94
+ call(session, method, request) {
95
+ const json = encoder.encode(JSON.stringify(request ?? null));
96
+ return this.#enqueue(() => {
97
+ let status = 0;
98
+ const out = this.#withBytes(json, (p, n) => this.#readBuf((buf) => (status = this.#m._twip_call(this.#engine, session, method, p, n, buf))));
99
+ return envelope(status, out);
100
+ });
101
+ }
102
+ open(session, bytes, signal) {
103
+ return this.#enqueue(() => {
104
+ signal?.throwIfAborted();
105
+ let status = 0;
106
+ const out = this.#withBytes(bytes, (p, n) => this.#withBytes(encoder.encode('{}'), (o, on) => this.#readBuf((buf) => (status = this.#m._twip_open(this.#engine, session, p, n, o, on, buf)))));
107
+ return envelope(status, out) ?? [];
108
+ }, signal);
109
+ }
110
+ openPrepare(session, bytes) {
111
+ return this.#enqueue(() => {
112
+ let status = 0;
113
+ const out = this.#withBytes(bytes, (p, n) => this.#withBytes(encoder.encode('{}'), (o, on) => this.#readBuf((buf) => (status = this.#m._twip_open_prepare(this.#engine, session, p, n, o, on, buf)))));
114
+ return envelope(status, out);
115
+ });
116
+ }
117
+ async openCommit(session, token) {
118
+ return (await this.call(session, Method.OpenCommit, token)) ?? [];
119
+ }
120
+ async openDiscard(session, token) {
121
+ await this.call(session, Method.OpenDiscard, token);
122
+ }
123
+ export(session, options, signal) {
124
+ return this.#enqueue(() => {
125
+ signal?.throwIfAborted();
126
+ let status = 0;
127
+ let meta = new Uint8Array();
128
+ const bytes = this.#withBytes(encoder.encode(JSON.stringify(options)), (o, on) => this.#readBuf((bytesBuf) => {
129
+ meta = this.#readBuf((metaBuf) => (status = this.#m._twip_export(this.#engine, session, o, on, bytesBuf, metaBuf)));
130
+ }));
131
+ const { revision, warnings } = envelope(status, meta);
132
+ return { bytes, revision, warnings };
133
+ }, signal);
134
+ }
135
+ addFont(bytes) {
136
+ return this.#enqueue(() => {
137
+ let status = 0;
138
+ const out = this.#withBytes(bytes, (p, n) => this.#readBuf((buf) => (status = this.#m._twip_font_add(this.#engine, p, n, buf))));
139
+ return envelope(status, out);
140
+ });
141
+ }
142
+ input(session, input) {
143
+ return this.#enqueue(() => {
144
+ const [m, e] = [this.#m, this.#engine];
145
+ switch (input.type) {
146
+ case 'key':
147
+ return m._twip_key(e, session, input.key, input.mods);
148
+ case 'text':
149
+ this.#withBytes(encoder.encode(input.text), (p, n) => m._twip_text(e, session, p, n));
150
+ break;
151
+ case 'composition':
152
+ this.#withBytes(encoder.encode(input.text), (p, n) => m._twip_composition(e, session, input.phase, p, n));
153
+ break;
154
+ case 'focus':
155
+ m._twip_focus(e, session, input.focused ? 1 : 0);
156
+ break;
157
+ case 'surface':
158
+ m._twip_surface(e, session, input.width, input.height, input.dpr);
159
+ break;
160
+ case 'scroll':
161
+ m._twip_scroll(e, session, input.x, input.y);
162
+ break;
163
+ case 'wheel':
164
+ m._twip_wheel(e, session, input.dx, input.dy, input.mods);
165
+ break;
166
+ case 'pointer':
167
+ m._twip_pointer(e, session, input.kind, input.x, input.y, input.button, input.clicks, input.mods);
168
+ break;
169
+ }
170
+ return 0;
171
+ });
172
+ }
173
+ async attachCanvas(session, canvas) {
174
+ const device = await this.#device();
175
+ return this.#enqueue(() => {
176
+ const m = this.#m;
177
+ let attached;
178
+ if (device)
179
+ attached = { backend: 'webgpu', canvas, target: webGpuTarget(canvas, device) };
180
+ const gl = attached ? null : webGlTarget(m, this.#engine, session, canvas);
181
+ if (gl) {
182
+ const c = { backend: 'webgl', canvas, target: gl, unwatch: () => { } };
183
+ c.unwatch = watchWebGl(canvas, () => (c.lost = true), () => this.#restoreGl(session, c));
184
+ attached = c;
185
+ }
186
+ const context = attached ? null : canvas.getContext('2d');
187
+ if (context)
188
+ attached = { backend: 'cpu', canvas, context };
189
+ if (!attached)
190
+ throw new EditorError('unsupported', 'the canvas has no WebGPU, WebGL 2 or 2D context');
191
+ this.#canvases.set(session, attached);
192
+ return attached.backend;
193
+ });
194
+ }
195
+ frame(session, width, height) {
196
+ return this.#enqueue(() => {
197
+ const c = this.#canvases.get(session);
198
+ if (!c || c.lost || width === 0 || height === 0)
199
+ return null;
200
+ if (c.canvas.width !== width)
201
+ c.canvas.width = width;
202
+ if (c.canvas.height !== height)
203
+ c.canvas.height = height;
204
+ const m = this.#m;
205
+ const info = m._twip_alloc(FRAME_INFO_SIZE);
206
+ try {
207
+ m.HEAPU8.fill(0, info, info + FRAME_INFO_SIZE);
208
+ let drawn;
209
+ if (c.backend === 'webgpu') {
210
+ const target = c.target;
211
+ if (target.cpu)
212
+ drawn = this.#frameRgba(session, width, height, info, (p) => writeWebGpu(target, p, width, height));
213
+ else
214
+ drawn = frameWebGpu(m, this.#engine, session, target, info);
215
+ }
216
+ else if (c.backend === 'webgl')
217
+ drawn = frameWebGl(m, this.#engine, session, c.target, info);
218
+ else {
219
+ const context = c.context;
220
+ drawn = this.#frameRgba(session, width, height, info, (p) => context.putImageData(new ImageData(new Uint8ClampedArray(p.buffer), width, height), 0, 0));
221
+ }
222
+ if (!drawn)
223
+ return null;
224
+ // TwipFrameInfo: content_width and content_height are its first two f64s, pages its last u32.
225
+ const view = new DataView(m.HEAPU8.buffer, info, FRAME_INFO_SIZE);
226
+ return { contentWidth: view.getFloat64(0, true), contentHeight: view.getFloat64(8, true), pages: view.getUint32(44, true) };
227
+ }
228
+ finally {
229
+ m._twip_free(info, FRAME_INFO_SIZE);
230
+ }
231
+ });
232
+ }
233
+ /** The page's WebGPU device the engine adopted, or null without one. A lost device is replaced by a new one, which
234
+ * the engine doesn't adopt past `MAX_DEVICE_LOSSES`. */
235
+ #device() {
236
+ return (this.#gpu ??= gpuDevice().then((d) => {
237
+ if (!d || (this.#losses <= MAX_DEVICE_LOSSES && !attachWebGpu(this.#m, d)))
238
+ return null;
239
+ d.lost.then(() => this.#deviceLost(), () => { });
240
+ return d;
241
+ }));
242
+ }
243
+ /** The WebGPU canvases wait for a new device, are configured for it, then draw again. */
244
+ #deviceLost() {
245
+ this.#gpu = undefined;
246
+ this.#losses++;
247
+ for (const c of this.#canvases.values())
248
+ if (c.backend === 'webgpu')
249
+ c.lost = true;
250
+ // shortcut: without a new device the canvases stay blank; moving the editor to a new canvas on WebGL or CPU pixels
251
+ // would recover them.
252
+ void this.#device().then((device) => this.#enqueue(() => {
253
+ if (!device)
254
+ return;
255
+ for (const [session, c] of this.#canvases) {
256
+ if (c.backend !== 'webgpu' || !c.lost)
257
+ continue;
258
+ c.target = webGpuTarget(c.canvas, device, this.#losses > MAX_DEVICE_LOSSES);
259
+ c.lost = false;
260
+ this.#pending.push({ kind: Kind.RequestFrame, sessionId: session, payload: undefined });
261
+ }
262
+ }));
263
+ }
264
+ /** A session's WebGL context is back: a new context handle for the engine, then a frame. */
265
+ #restoreGl(session, c) {
266
+ this.#enqueue(() => {
267
+ if (this.#canvases.get(session) !== c)
268
+ return;
269
+ const target = restoreWebGl(this.#m, this.#engine, session, c.target);
270
+ if (!target)
271
+ return;
272
+ c.target = target;
273
+ c.lost = false;
274
+ this.#pending.push({ kind: Kind.RequestFrame, sessionId: session, payload: undefined });
275
+ }).catch(() => { });
276
+ }
277
+ /** Draws CPU pixels and hands `put` a copy of them. */
278
+ #frameRgba(session, width, height, info, put) {
279
+ const size = width * height * 4;
280
+ const pixels = this.#m._twip_alloc(size);
281
+ try {
282
+ if (this.#m._twip_frame_rgba(this.#engine, session, pixels, width, height, info) !== 0)
283
+ return false;
284
+ put(this.#m.HEAPU8.slice(pixels, pixels + size));
285
+ return true;
286
+ }
287
+ finally {
288
+ this.#m._twip_free(pixels, size);
289
+ }
290
+ }
291
+ listen(session, listener) {
292
+ let set = this.#listeners.get(session);
293
+ if (!set)
294
+ this.#listeners.set(session, (set = new Set()));
295
+ set.add(listener);
296
+ return () => set.delete(listener);
297
+ }
298
+ /** Runs `op` after every earlier operation, then delivers the messages it caused, then settles. */
299
+ #enqueue(op, signal) {
300
+ if (signal?.aborted)
301
+ return Promise.reject(aborted());
302
+ return new Promise((resolve, reject) => {
303
+ this.#queue.push(() => {
304
+ if (this.#fault)
305
+ return reject(this.#fault);
306
+ let result;
307
+ try {
308
+ result = op();
309
+ }
310
+ catch (e) {
311
+ this.#deliver();
312
+ return reject(this.#failure(e));
313
+ }
314
+ this.#deliver();
315
+ resolve(result);
316
+ });
317
+ if (!this.#draining) {
318
+ this.#draining = true;
319
+ queueMicrotask(() => {
320
+ // Listeners may enqueue more: they run in this same drain, after the current dispatch.
321
+ for (let next = this.#queue.shift(); next; next = this.#queue.shift())
322
+ next();
323
+ this.#draining = false;
324
+ });
325
+ }
326
+ });
327
+ }
328
+ #deliver() {
329
+ const messages = this.#pending;
330
+ this.#pending = [];
331
+ for (const message of messages) {
332
+ if (message.kind === Kind.SetTimer) {
333
+ this.#arm(message.sessionId, message.payload);
334
+ continue;
335
+ }
336
+ for (const listener of [...(this.#listeners.get(message.sessionId) ?? [])]) {
337
+ try {
338
+ listener(message);
339
+ }
340
+ catch (e) {
341
+ globalThis.reportError?.(e);
342
+ }
343
+ }
344
+ }
345
+ }
346
+ #arm(session, { timer, ms }) {
347
+ let set = this.#timers.get(session);
348
+ if (!set)
349
+ this.#timers.set(session, (set = new Set()));
350
+ const handle = setTimeout(() => {
351
+ set.delete(handle);
352
+ this.#enqueue(() => this.#m._twip_timer_fired(this.#engine, session, timer)).catch(() => { });
353
+ }, ms);
354
+ set.add(handle);
355
+ }
356
+ #failure(e) {
357
+ if (e instanceof EditorError)
358
+ return e;
359
+ if (e instanceof DOMException && e.name === 'AbortError')
360
+ return aborted();
361
+ // A trap: the module's state is unknown from here on.
362
+ this.#fault = new EditorError('internal', `the engine stopped: ${String(e)}`, { fatal: true, cause: e });
363
+ return this.#fault;
364
+ }
365
+ /** Copies `bytes` into the module for the duration of `use`. */
366
+ #withBytes(bytes, use) {
367
+ const ptr = this.#m._twip_alloc(Math.max(bytes.length, 1));
368
+ this.#m.HEAPU8.set(bytes, ptr);
369
+ try {
370
+ return use(ptr, bytes.length);
371
+ }
372
+ finally {
373
+ this.#m._twip_free(ptr, Math.max(bytes.length, 1));
374
+ }
375
+ }
376
+ /** Passes a fresh `TwipBuf` to `fill`, then copies its bytes out and frees it. */
377
+ #readBuf(fill) {
378
+ const buf = this.#m._twip_alloc(BUF_SIZE);
379
+ this.#m.HEAPU32.fill(0, buf >> 2, (buf >> 2) + 2);
380
+ try {
381
+ fill(buf);
382
+ const ptr = this.#m.HEAPU32[buf >> 2] ?? 0;
383
+ const len = this.#m.HEAPU32[(buf >> 2) + 1] ?? 0;
384
+ const bytes = this.#m.HEAPU8.slice(ptr, ptr + len);
385
+ if (ptr)
386
+ this.#m._twip_buf_free(buf);
387
+ return bytes;
388
+ }
389
+ finally {
390
+ this.#m._twip_free(buf, BUF_SIZE);
391
+ }
392
+ }
393
+ }
394
+ function aborted() {
395
+ return new EditorError('aborted', 'the operation was aborted');
396
+ }
397
+ /** A response: its JSON envelope when there is one, else the status code alone. */
398
+ function envelope(status, bytes) {
399
+ if (bytes.length)
400
+ return unwrap(JSON.parse(decoder.decode(bytes)));
401
+ if (status === 0)
402
+ return undefined;
403
+ const code = statusCodes[Math.abs(status) - 1] ?? 'internal';
404
+ throw new EditorError(code, `the engine failed with status ${status}`);
405
+ }
@@ -0,0 +1,51 @@
1
+ /** Pointers and sizes are 32-bit numbers in wasm32. */
2
+ type Ptr = number;
3
+ /** The emscripten module of the engine: the C ABI as `_name` exports, plus the heap and the GPU helpers. */
4
+ export interface TwipModule {
5
+ HEAPU8: Uint8Array;
6
+ HEAPU32: Uint32Array;
7
+ /** Set by the SDK: `twip-host.js` calls it for every engine callback. The payload lives in HEAPU8 during the call. */
8
+ twipDispatch?: (kind: number, session: number, ptr: Ptr, len: number) => void;
9
+ _twip_alloc(len: number): Ptr;
10
+ _twip_free(ptr: Ptr, len: number): void;
11
+ _twip_buf_free(buf: Ptr): void;
12
+ _twip_engine_new(options: Ptr, len: number, callback: Ptr, context: Ptr): Ptr;
13
+ _twip_engine_free(engine: Ptr): void;
14
+ _twip_session_new(engine: Ptr, options: Ptr, len: number): number;
15
+ _twip_session_free(engine: Ptr, session: number): void;
16
+ _twip_call(engine: Ptr, session: number, method: number, request: Ptr, len: number, out: Ptr): number;
17
+ _twip_open(engine: Ptr, session: number, bytes: Ptr, len: number, options: Ptr, optionsLen: number, out: Ptr): number;
18
+ _twip_open_prepare(engine: Ptr, session: number, bytes: Ptr, len: number, options: Ptr, optionsLen: number, out: Ptr): number;
19
+ _twip_export(engine: Ptr, session: number, options: Ptr, optionsLen: number, bytes: Ptr, meta: Ptr): number;
20
+ _twip_font_add(engine: Ptr, bytes: Ptr, len: number, out: Ptr): number;
21
+ _twip_key(engine: Ptr, session: number, key: number, mods: number): number;
22
+ _twip_text(engine: Ptr, session: number, utf8: Ptr, len: number): void;
23
+ _twip_composition(engine: Ptr, session: number, phase: number, utf8: Ptr, len: number): void;
24
+ _twip_pointer(engine: Ptr, session: number, kind: number, x: number, y: number, button: number, clicks: number, mods: number): void;
25
+ _twip_wheel(engine: Ptr, session: number, dx: number, dy: number, mods: number): void;
26
+ _twip_focus(engine: Ptr, session: number, focused: number): void;
27
+ _twip_surface(engine: Ptr, session: number, width: number, height: number, dpr: number): void;
28
+ _twip_scroll(engine: Ptr, session: number, x: number, y: number): void;
29
+ _twip_frame_rgba(engine: Ptr, session: number, pixels: Ptr, width: number, height: number, info: Ptr): number;
30
+ _twip_timer_fired(engine: Ptr, session: number, timer: number): void;
31
+ _twip_gpu_instance(): Ptr;
32
+ _twip_attach_webgpu(instance: Ptr, device: Ptr, queue: Ptr): number;
33
+ _twip_frame_webgpu(engine: Ptr, session: number, texture: Ptr, bgra: number, height: number, info: Ptr): number;
34
+ _twip_attach_webgl(engine: Ptr, session: number): number;
35
+ _twip_frame_webgl(engine: Ptr, session: number, width: number, height: number, info: Ptr): number;
36
+ /** emscripten's WebGPU import helpers (emdawnwebgpu). */
37
+ WebGPU?: {
38
+ importJsDevice(device: unknown, instance: Ptr): Ptr;
39
+ importJsQueue(queue: unknown, instance: Ptr): Ptr;
40
+ importJsTexture(texture: unknown): Ptr;
41
+ };
42
+ /** emscripten's WebGL context helpers. */
43
+ GL?: {
44
+ createContext(canvas: unknown, attributes: Record<string, number>): number;
45
+ makeContextCurrent(handle: number): boolean;
46
+ deleteContext(handle: number): void;
47
+ };
48
+ }
49
+ /** Instantiates the engine module, with `dispatch` registered before any engine call can call back. */
50
+ export declare function loadModule(dispatch: NonNullable<TwipModule['twipDispatch']>): Promise<TwipModule>;
51
+ export {};
@@ -0,0 +1,17 @@
1
+ // Loading the engine module: the one place that knows where the WebAssembly build is and what it exports.
2
+ // The build copies the emscripten output (twip-wasm.js and twip_wasm.wasm) next to dist/index.js.
3
+ /**
4
+ * Where the engine module and its WebAssembly are, relative to this file in dist/transport/. Both are literal
5
+ * `new URL(..., import.meta.url)`s, so a bundler copies both files into the application's build.
6
+ */
7
+ const moduleUrl = new URL('../twip-wasm.js', import.meta.url);
8
+ const wasmUrl = new URL('../twip_wasm.wasm', import.meta.url);
9
+ /** Instantiates the engine module, with `dispatch` registered before any engine call can call back. */
10
+ export async function loadModule(dispatch) {
11
+ const { default: create } = (await import(moduleUrl.href));
12
+ // In Node the module finds its file itself; in a browser it's wherever the bundler put it.
13
+ const locateFile = moduleUrl.protocol === 'file:' ? undefined : (file) => (file.endsWith('.wasm') ? wasmUrl : new URL(file, moduleUrl)).href;
14
+ const module = await create({ twipDispatch: dispatch, locateFile });
15
+ module.twipDispatch = dispatch;
16
+ return module;
17
+ }