substrate-lang 0.10.0 → 0.10.1
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/package.json +1 -1
- package/std/package.sub +4 -3
- package/std/web/canvas.sub +3 -3
- package/std/web/dom.sub +45 -11
- package/std/web/gpu-commands.sub +439 -0
- package/std/web/gpu.sub +17 -424
- package/std/web/patches.sub +45 -10
- package/substrate/VERSION +1 -1
- package/substrate/host/web/audio.mjs +17 -9
- package/substrate/host/web/audio.sub +9 -3
- package/substrate/host/web/dom.mjs +165 -64
- package/substrate/host/web/dom.sub +9 -27
- package/substrate/host/web/gpu.mjs +63 -25
- package/substrate/host/web/gpu.sub +18 -4
- package/substrate/host/web/location.mjs +15 -1
- package/substrate/host/web/location.sub +11 -3
|
@@ -10,6 +10,9 @@ const HEARD = 1;
|
|
|
10
10
|
const IMAGE_LOADED = 2;
|
|
11
11
|
const IMAGE_FAILED = 3;
|
|
12
12
|
|
|
13
|
+
// The document's node number (std/web/patches.sub).
|
|
14
|
+
const DOCUMENT = 1;
|
|
15
|
+
|
|
13
16
|
const imageTypes = {
|
|
14
17
|
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',
|
|
15
18
|
avif: 'image/avif', bmp: 'image/bmp', svg: 'image/svg+xml',
|
|
@@ -17,9 +20,14 @@ const imageTypes = {
|
|
|
17
20
|
|
|
18
21
|
export function dom(root) {
|
|
19
22
|
const templates = new Map();
|
|
20
|
-
const nodes = new Map([[0, root]]);
|
|
23
|
+
const nodes = new Map([[0, root], [DOCUMENT, root.ownerDocument]]);
|
|
21
24
|
const images = new Map();
|
|
22
|
-
|
|
25
|
+
const loading = new Map();
|
|
26
|
+
const counted = (tag, n) => {
|
|
27
|
+
const k = (loading.get(tag) || 0) + n;
|
|
28
|
+
if (k === 0) loading.delete(tag);
|
|
29
|
+
else loading.set(tag, k);
|
|
30
|
+
};
|
|
23
31
|
let app = null;
|
|
24
32
|
let caller = null;
|
|
25
33
|
|
|
@@ -30,6 +38,73 @@ export function dom(root) {
|
|
|
30
38
|
return e && e.source === SOURCE ? e.data : null;
|
|
31
39
|
};
|
|
32
40
|
|
|
41
|
+
const kinds = ['Node', 'Event', 'NodeList', 'HTMLCollection', 'DOMRectReadOnly', 'DOMTokenList',
|
|
42
|
+
'CSSStyleDeclaration', 'DOMStringMap', 'ValidityState', 'FileList', 'Blob', 'DataTransfer', 'TouchList', 'Touch',
|
|
43
|
+
'IntersectionObserverEntry'].map((name) => globalThis[name]).filter((kind) => kind !== undefined);
|
|
44
|
+
const reachable = (x) => kinds.some((kind) => x instanceof kind);
|
|
45
|
+
|
|
46
|
+
function guarded(path, f) {
|
|
47
|
+
try {
|
|
48
|
+
return f();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
caller.trap(`dom: ${path}: ${e.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function step(x, name, path) {
|
|
55
|
+
if (!(name in Object(x))) caller.trap(`dom: ${path}: there is no ${name}`);
|
|
56
|
+
return guarded(path, () => x[name]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function owner(root, path) {
|
|
60
|
+
const parts = path.split('.');
|
|
61
|
+
const last = parts.pop();
|
|
62
|
+
let x = root;
|
|
63
|
+
for (let k = 0; k < parts.length; k++) {
|
|
64
|
+
x = step(x, parts[k], path);
|
|
65
|
+
if (x === null || x === undefined) return [null, last];
|
|
66
|
+
if (!reachable(x)) {
|
|
67
|
+
caller.trap(`dom: ${path}: ${parts.slice(0, k + 1).join('.')} is not one of the page's objects`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [x, last];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function walk(root, path) {
|
|
74
|
+
const [x, last] = owner(root, path);
|
|
75
|
+
return x === null ? null : step(x, last, path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function read(root, path) {
|
|
79
|
+
const v = walk(root, path);
|
|
80
|
+
const plain = v === null || v === undefined || ['number', 'string', 'boolean'].includes(typeof v);
|
|
81
|
+
if (!plain) caller.trap(`dom: ${path} is not a number, text, or true or false`);
|
|
82
|
+
return v;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function write(root, path, value) {
|
|
86
|
+
const [x, last] = owner(root, path);
|
|
87
|
+
if (x === null || !(last in x)) caller.trap(`dom: ${path}: there is no ${last} to set`);
|
|
88
|
+
guarded(path, () => { x[last] = value; });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function call(root, path, args) {
|
|
92
|
+
const [x, last] = owner(root, path);
|
|
93
|
+
if (x === null || typeof x[last] !== 'function') caller.trap(`dom: ${path} is not a method`);
|
|
94
|
+
const result = guarded(path, () => x[last](...args));
|
|
95
|
+
if (result && typeof result.catch === 'function') result.catch(() => {});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const numberOf = (v) => (v === null || v === undefined ? 0 : Number(v));
|
|
99
|
+
const textOf = (v) => (v === null || v === undefined ? '' : String(v));
|
|
100
|
+
|
|
101
|
+
const bits = new DataView(new ArrayBuffer(8));
|
|
102
|
+
const float64 = (low, high) => {
|
|
103
|
+
bits.setUint32(0, low, true);
|
|
104
|
+
bits.setUint32(4, high, true);
|
|
105
|
+
return bits.getFloat64(0, true);
|
|
106
|
+
};
|
|
107
|
+
|
|
33
108
|
const listener = (e) => {
|
|
34
109
|
const messages = e.currentTarget.subListeners;
|
|
35
110
|
if (messages && e.type in messages) give(e, messages[e.type]);
|
|
@@ -66,12 +141,27 @@ export function dom(root) {
|
|
|
66
141
|
|
|
67
142
|
const windowTarget = (type) => (type === 'pointerlockchange' || type === 'pointerlockerror' ? document : window);
|
|
68
143
|
|
|
144
|
+
const watched = new Map();
|
|
145
|
+
let observer = null;
|
|
146
|
+
|
|
147
|
+
function watch(node, message) {
|
|
148
|
+
observer ||= new IntersectionObserver((entries) => {
|
|
149
|
+
for (const entry of entries) if (watched.has(entry.target)) give(entry, watched.get(entry.target));
|
|
150
|
+
});
|
|
151
|
+
watched.set(node, message);
|
|
152
|
+
observer.observe(node);
|
|
153
|
+
}
|
|
154
|
+
|
|
69
155
|
const unlisten = (id) => {
|
|
70
156
|
const n = nodes.get(id);
|
|
71
157
|
if (n instanceof Node && n.subListeners) {
|
|
72
158
|
for (const message of Object.values(n.subListeners)) caller.forget(message);
|
|
73
159
|
n.subListeners = null;
|
|
74
160
|
}
|
|
161
|
+
if (watched.has(n)) {
|
|
162
|
+
observer.unobserve(n);
|
|
163
|
+
watched.delete(n);
|
|
164
|
+
}
|
|
75
165
|
for (const { type, message, handler } of windowListeners.get(id) || []) {
|
|
76
166
|
windowTarget(type).removeEventListener(type, handler);
|
|
77
167
|
caller.forget(message);
|
|
@@ -260,68 +350,75 @@ export function dom(root) {
|
|
|
260
350
|
const node = nodes.get(w[i + 1]);
|
|
261
351
|
const type = string(i + 2);
|
|
262
352
|
(node.subListeners ||= {})[type] = w[i + 4];
|
|
263
|
-
|
|
353
|
+
if (type === 'visibility') watch(node, w[i + 4]);
|
|
354
|
+
else node.addEventListener(type, listener);
|
|
264
355
|
i += 5;
|
|
265
356
|
break;
|
|
266
357
|
}
|
|
267
|
-
case 10:
|
|
268
|
-
const node = nodes.get(w[i + 1]);
|
|
269
|
-
after.push(() => node.focus());
|
|
270
|
-
i += 2;
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
case 11: {
|
|
274
|
-
const node = nodes.get(w[i + 1]);
|
|
275
|
-
after.push(() => { node.scrollTop = node.scrollHeight; });
|
|
276
|
-
i += 2;
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
case 12:
|
|
358
|
+
case 10:
|
|
280
359
|
listenWindow(w[i + 1], string(i + 2), w[i + 4]);
|
|
281
360
|
i += 5;
|
|
282
361
|
break;
|
|
283
|
-
case
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
i += 3;
|
|
362
|
+
case 11: {
|
|
363
|
+
const canvas = nodes.get(w[i + 1]);
|
|
364
|
+
const count = w[i + 2];
|
|
365
|
+
const f = new Float32Array(w.buffer, w.byteOffset, w.length);
|
|
366
|
+
draw(canvas.getContext('2d'), w, f, i + 3, i + 3 + count, string);
|
|
367
|
+
i += 3 + count;
|
|
291
368
|
break;
|
|
292
369
|
}
|
|
293
|
-
case
|
|
370
|
+
case 12: {
|
|
294
371
|
const name = string(i + 2);
|
|
295
372
|
const image = new Image();
|
|
296
373
|
const bytes = app.files.get(resolvePath(app.cwd, name));
|
|
297
374
|
const type = imageTypes[name.slice(name.lastIndexOf('.') + 1).toLowerCase()] || '';
|
|
298
375
|
const url = bytes ? URL.createObjectURL(new Blob([bytes], { type })) : name;
|
|
299
376
|
const id = w[i + 1];
|
|
377
|
+
const tag = w[i + 4];
|
|
300
378
|
const finished = () => {
|
|
301
|
-
|
|
379
|
+
counted(tag, -1);
|
|
302
380
|
if (bytes) URL.revokeObjectURL(url);
|
|
303
381
|
};
|
|
304
|
-
|
|
382
|
+
counted(tag, 1);
|
|
305
383
|
image.onload = () => {
|
|
306
384
|
finished();
|
|
307
|
-
caller.event(IMAGE_LOADED, id,
|
|
385
|
+
caller.event(IMAGE_LOADED, id, tag, 0);
|
|
308
386
|
};
|
|
309
387
|
image.onerror = () => {
|
|
310
388
|
finished();
|
|
311
389
|
console.error(`dom: cannot load the image ${name}`);
|
|
312
|
-
caller.event(IMAGE_FAILED, id,
|
|
390
|
+
caller.event(IMAGE_FAILED, id, tag, errors.missing, utf8.encode(`cannot load the image ${name}`));
|
|
313
391
|
};
|
|
314
392
|
image.src = url;
|
|
315
393
|
images.set(w[i + 1], image);
|
|
316
|
-
i +=
|
|
394
|
+
i += 5;
|
|
317
395
|
break;
|
|
318
396
|
}
|
|
319
397
|
case 13: {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
398
|
+
const style = document.createElement('style');
|
|
399
|
+
style.textContent = string(i + 1);
|
|
400
|
+
root.ownerDocument.head.appendChild(style);
|
|
401
|
+
i += 3;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case 14: {
|
|
405
|
+
const node = nodeOf(w[i + 1]);
|
|
406
|
+
const path = string(i + 2);
|
|
407
|
+
const value = w[i + 4] === 0 ? float64(w[i + 5], w[i + 6]) : string(i + 5);
|
|
408
|
+
after.push(() => write(node, path, value));
|
|
409
|
+
i += 7;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
case 15: {
|
|
413
|
+
const node = nodeOf(w[i + 1]);
|
|
414
|
+
const path = string(i + 2);
|
|
415
|
+
const args = [];
|
|
416
|
+
for (let k = 0; k < w[i + 4]; k++) {
|
|
417
|
+
const at = i + 5 + 3 * k;
|
|
418
|
+
args.push(w[at] === 0 ? float64(w[at + 1], w[at + 2]) : string(at + 1));
|
|
419
|
+
}
|
|
420
|
+
after.push(() => call(node, path, args));
|
|
421
|
+
i += 5 + 3 * w[i + 4];
|
|
325
422
|
break;
|
|
326
423
|
}
|
|
327
424
|
default:
|
|
@@ -340,45 +437,49 @@ export function dom(root) {
|
|
|
340
437
|
caller = theCaller;
|
|
341
438
|
app = theApp;
|
|
342
439
|
const given = (s, out) => caller.give(utf8.encode(s), out);
|
|
440
|
+
const current = () => {
|
|
441
|
+
const e = event();
|
|
442
|
+
if (!e) caller.trap('dom: there is no current event');
|
|
443
|
+
return e;
|
|
444
|
+
};
|
|
445
|
+
const known = (id) => {
|
|
446
|
+
if (!nodes.has(id)) caller.trap(`dom: there is no node ${id}`);
|
|
447
|
+
return nodeOf(id);
|
|
448
|
+
};
|
|
343
449
|
return {
|
|
344
450
|
source: SOURCE,
|
|
345
451
|
apply(ptr, length) {
|
|
346
452
|
apply(caller.memory(), caller.u(ptr), length);
|
|
347
453
|
},
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const target = e && e.target;
|
|
351
|
-
return given(target && typeof target.value === 'string' ? target.value : '', out);
|
|
454
|
+
event_number(ptr, length) {
|
|
455
|
+
return numberOf(read(current(), caller.string(ptr, length)));
|
|
352
456
|
},
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return given(e && typeof e.key === 'string' ? e.key : '', out);
|
|
457
|
+
event_text(ptr, length, out) {
|
|
458
|
+
return given(textOf(read(current(), caller.string(ptr, length))), out);
|
|
356
459
|
},
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!e) return 0;
|
|
360
|
-
return (e.shiftKey ? 1 : 0) | (e.ctrlKey ? 2 : 0) | (e.altKey ? 4 : 0) | (e.metaKey ? 8 : 0) |
|
|
361
|
-
(e.target && e.target.checked === true ? 16 : 0) | (document.pointerLockElement ? 32 : 0);
|
|
460
|
+
event_call(ptr, length) {
|
|
461
|
+
call(current(), caller.string(ptr, length), []);
|
|
362
462
|
},
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
463
|
+
event_is(node, ptr, length) {
|
|
464
|
+
return walk(current(), caller.string(ptr, length)) === known(node) ? 1 : 0;
|
|
465
|
+
},
|
|
466
|
+
read_number(node, ptr, length) {
|
|
467
|
+
return numberOf(read(known(node), caller.string(ptr, length)));
|
|
468
|
+
},
|
|
469
|
+
read_text(node, ptr, length, out) {
|
|
470
|
+
return given(textOf(read(known(node), caller.string(ptr, length))), out);
|
|
471
|
+
},
|
|
472
|
+
is(node, from, ptr, length) {
|
|
473
|
+
return walk(known(from), caller.string(ptr, length)) === known(node) ? 1 : 0;
|
|
375
474
|
},
|
|
376
|
-
images_loading() {
|
|
377
|
-
return loading;
|
|
475
|
+
images_loading(tag) {
|
|
476
|
+
return loading.get(tag) || 0;
|
|
378
477
|
},
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
478
|
+
element(ptr, length, node) {
|
|
479
|
+
const found = root.ownerDocument.getElementById(caller.string(ptr, length));
|
|
480
|
+
if (!found) return 0;
|
|
481
|
+
nodes.set(node, found);
|
|
482
|
+
return 1;
|
|
382
483
|
},
|
|
383
484
|
};
|
|
384
485
|
};
|
|
@@ -1,34 +1,16 @@
|
|
|
1
|
-
;; The page: the host module "dom". A heard event carries the id and tag its listener was given.
|
|
2
|
-
;; the
|
|
3
|
-
;;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
dom-time dom-x dom-y dom-movement-x dom-movement-y dom-button
|
|
7
|
-
dom-apply dom-event-value dom-event-key dom-event-flags dom-event-number dom-prevent-default dom-images-loading)
|
|
1
|
+
;; The page: the host module "dom". A heard event carries the id and tag its listener was given. A visibility listener
|
|
2
|
+
;; hears when its element comes into the viewport or leaves it. dom-bind-element gives the element with an id a node
|
|
3
|
+
;; number, for dom-element (std/web/dom.sub). The reads of the page are in std/web/dom.sub too. dom-images-loading
|
|
4
|
+
;; counts the images loading for a tag.
|
|
5
|
+
(provide dom-source dom-heard dom-image-loaded dom-image-failed dom-apply dom-bind-element dom-images-loading)
|
|
8
6
|
|
|
9
7
|
(const dom-source u32 1)
|
|
10
8
|
(const dom-heard u32 1)
|
|
11
9
|
(const dom-image-loaded u32 2)
|
|
12
10
|
(const dom-image-failed u32 3)
|
|
13
11
|
|
|
14
|
-
(const dom-shift u32 1)
|
|
15
|
-
(const dom-control u32 2)
|
|
16
|
-
(const dom-alt u32 4)
|
|
17
|
-
(const dom-meta u32 8)
|
|
18
|
-
(const dom-checked u32 16)
|
|
19
|
-
(const dom-locked u32 32)
|
|
20
|
-
|
|
21
|
-
(const dom-time i32 0)
|
|
22
|
-
(const dom-x i32 1)
|
|
23
|
-
(const dom-y i32 2)
|
|
24
|
-
(const dom-movement-x i32 3)
|
|
25
|
-
(const dom-movement-y i32 4)
|
|
26
|
-
(const dom-button i32 5)
|
|
27
|
-
|
|
28
12
|
(import dom-apply "dom" "apply" (i32 i32 ->))
|
|
29
|
-
(import
|
|
30
|
-
(import dom-
|
|
31
|
-
|
|
32
|
-
(
|
|
33
|
-
(import dom-prevent-default "dom" "prevent_default" (->))
|
|
34
|
-
(import dom-images-loading "dom" "images_loading" (-> i32))
|
|
13
|
+
(import images-loading "dom" "images_loading" (i32 -> i32))
|
|
14
|
+
(import dom-bind-element "dom" "element" (i32 i32 i32 -> i32))
|
|
15
|
+
|
|
16
|
+
(define (dom-images-loading [tag (ptr void)]) -> u32 (cast u32 (images-loading (cast i32 tag))))
|
|
@@ -39,6 +39,8 @@ function unavailable(reason) {
|
|
|
39
39
|
source: SOURCE,
|
|
40
40
|
apply() { caller.trap(`gpu: WebGPU is not available: ${reason}`); },
|
|
41
41
|
loading() { return 0; },
|
|
42
|
+
listen() { return 0; },
|
|
43
|
+
unlisten() {},
|
|
42
44
|
});
|
|
43
45
|
module.available = false;
|
|
44
46
|
module.adapter = {};
|
|
@@ -71,17 +73,31 @@ export async function gpu(options = {}) {
|
|
|
71
73
|
const layered = new Map();
|
|
72
74
|
const bindGroups = new Map();
|
|
73
75
|
const tables = { 1: surfaces, 2: shaders, 3: pipelines, 4: buffers, 5: textures, 6: samplers, 7: layouts, 8: bindGroups };
|
|
74
|
-
|
|
76
|
+
const loading = new Map();
|
|
77
|
+
const counted = (t, n) => {
|
|
78
|
+
const k = (loading.get(t) || 0) + n;
|
|
79
|
+
if (k === 0) loading.delete(t);
|
|
80
|
+
else loading.set(t, k);
|
|
81
|
+
};
|
|
75
82
|
|
|
76
83
|
let app = null;
|
|
77
84
|
let caller = null;
|
|
78
|
-
function event(kind, id, data) {
|
|
79
|
-
if (caller) caller.event(kind, id,
|
|
85
|
+
function event(kind, id, data, to = 0) {
|
|
86
|
+
if (caller) caller.event(kind, id, to, kind === ERROR ? errors.invalid : 0, data);
|
|
87
|
+
}
|
|
88
|
+
const error = (id, message, to = 0) => event(ERROR, id, utf8.encode(message), to);
|
|
89
|
+
|
|
90
|
+
let tag = 0;
|
|
91
|
+
const listeners = new Map();
|
|
92
|
+
let nextListener = 1;
|
|
93
|
+
|
|
94
|
+
function deviceError(message) {
|
|
95
|
+
if (listeners.size === 0) error(0, message);
|
|
96
|
+
for (const t of listeners.values()) error(0, message, t);
|
|
80
97
|
}
|
|
81
|
-
const error = (id, message) => event(ERROR, id, utf8.encode(message));
|
|
82
98
|
|
|
83
|
-
device.addEventListener('uncapturederror', (e) =>
|
|
84
|
-
device.lost.then((info) =>
|
|
99
|
+
device.addEventListener('uncapturederror', (e) => deviceError(e.error.message));
|
|
100
|
+
device.lost.then((info) => deviceError(`the device was lost (${info.reason}): ${info.message}`));
|
|
85
101
|
|
|
86
102
|
function get(kind, id) {
|
|
87
103
|
const r = tables[kind].get(id);
|
|
@@ -100,7 +116,18 @@ export async function gpu(options = {}) {
|
|
|
100
116
|
return v;
|
|
101
117
|
};
|
|
102
118
|
|
|
103
|
-
function run(memory, w, f, string) {
|
|
119
|
+
function run(memory, w, f, string, batchTag) {
|
|
120
|
+
tag = batchTag;
|
|
121
|
+
device.pushErrorScope('validation');
|
|
122
|
+
try {
|
|
123
|
+
commands(memory, w, f, string);
|
|
124
|
+
} finally {
|
|
125
|
+
tag = 0;
|
|
126
|
+
device.popErrorScope().then((e) => { if (e) error(0, e.message, batchTag); });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function commands(memory, w, f, string) {
|
|
104
131
|
const encoder = device.createCommandEncoder();
|
|
105
132
|
let pass = null;
|
|
106
133
|
let computing = false;
|
|
@@ -137,6 +164,7 @@ export async function gpu(options = {}) {
|
|
|
137
164
|
}
|
|
138
165
|
case 2: {
|
|
139
166
|
const id = w[i + 1];
|
|
167
|
+
const to = tag;
|
|
140
168
|
device.pushErrorScope('validation');
|
|
141
169
|
const module = device.createShaderModule({ label: `shader ${id}`, code: string(i + 2) });
|
|
142
170
|
shaders.set(id, module);
|
|
@@ -144,7 +172,7 @@ export async function gpu(options = {}) {
|
|
|
144
172
|
if (!e) return;
|
|
145
173
|
const errors = (await module.getCompilationInfo()).messages.filter((m) => m.type === 'error');
|
|
146
174
|
error(id, errors.length === 0 ? e.message : `shader ${id} does not compile:\n` +
|
|
147
|
-
errors.map((m) => ` line ${m.lineNum}, column ${m.linePos}: ${m.message}`).join('\n'));
|
|
175
|
+
errors.map((m) => ` line ${m.lineNum}, column ${m.linePos}: ${m.message}`).join('\n'), to);
|
|
148
176
|
});
|
|
149
177
|
i += 4;
|
|
150
178
|
break;
|
|
@@ -246,7 +274,7 @@ export async function gpu(options = {}) {
|
|
|
246
274
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
|
|
247
275
|
});
|
|
248
276
|
textures.set(id, { texture: placeholder, format: 'rgba8unorm', width: 1, height: 1, layers: 1 });
|
|
249
|
-
loadTexture(id, name);
|
|
277
|
+
loadTexture(id, name, tag);
|
|
250
278
|
i += 4;
|
|
251
279
|
break;
|
|
252
280
|
}
|
|
@@ -311,7 +339,7 @@ export async function gpu(options = {}) {
|
|
|
311
339
|
entries.push({ binding: w[i], kind, id: w[i + 2], offset: w[i + 3], size: w[i + 4] });
|
|
312
340
|
i += 5;
|
|
313
341
|
}
|
|
314
|
-
const description = { id, layout, entries, layers: layoutId !== 0 ? layered.get(layoutId) : new Set() };
|
|
342
|
+
const description = { id, layout, entries, layers: layoutId !== 0 ? layered.get(layoutId) : new Set(), tag };
|
|
315
343
|
description.group = makeBindGroup(description);
|
|
316
344
|
bindGroups.set(id, description);
|
|
317
345
|
break;
|
|
@@ -347,17 +375,19 @@ export async function gpu(options = {}) {
|
|
|
347
375
|
}
|
|
348
376
|
case 14: {
|
|
349
377
|
const id = w[i + 1];
|
|
378
|
+
const to = tag;
|
|
350
379
|
const read = readTexture(encoder, id, w[i + 2], w[i + 3], w[i + 4], w[i + 5], w[i + 6], w[i + 7]);
|
|
351
|
-
reads.push(() => read().then((bytes) => event(PIXELS, id, bytes), (e) => error(id, e.message)));
|
|
380
|
+
reads.push(() => read().then((bytes) => event(PIXELS, id, bytes, to), (e) => error(id, e.message, to)));
|
|
352
381
|
i += 8;
|
|
353
382
|
break;
|
|
354
383
|
}
|
|
355
384
|
case 15: {
|
|
356
385
|
const id = w[i + 1];
|
|
386
|
+
const to = tag;
|
|
357
387
|
const len = w[i + 3];
|
|
358
388
|
const staging = device.createBuffer({ size: (len + 3) & ~3, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ });
|
|
359
389
|
encoder.copyBufferToBuffer(get(4, id), w[i + 2], staging, 0, (len + 3) & ~3);
|
|
360
|
-
reads.push(() => readStaging(staging, len).then((bytes) => event(BYTES, id, bytes), (e) => error(id, e.message)));
|
|
390
|
+
reads.push(() => readStaging(staging, len).then((bytes) => event(BYTES, id, bytes, to), (e) => error(id, e.message, to)));
|
|
361
391
|
i += 4;
|
|
362
392
|
break;
|
|
363
393
|
}
|
|
@@ -499,10 +529,10 @@ export async function gpu(options = {}) {
|
|
|
499
529
|
for (const read of reads) read();
|
|
500
530
|
}
|
|
501
531
|
|
|
502
|
-
function checked(what, id, make) {
|
|
532
|
+
function checked(what, id, make, to = tag) {
|
|
503
533
|
device.pushErrorScope('validation');
|
|
504
534
|
const resource = make();
|
|
505
|
-
device.popErrorScope().then((e) => { if (e) error(id, `${what} is invalid: ${e.message}
|
|
535
|
+
device.popErrorScope().then((e) => { if (e) error(id, `${what} is invalid: ${e.message}`, to); });
|
|
506
536
|
return resource;
|
|
507
537
|
}
|
|
508
538
|
|
|
@@ -529,8 +559,8 @@ export async function gpu(options = {}) {
|
|
|
529
559
|
});
|
|
530
560
|
}
|
|
531
561
|
|
|
532
|
-
async function loadTexture(id, name) {
|
|
533
|
-
|
|
562
|
+
async function loadTexture(id, name, to) {
|
|
563
|
+
counted(to, 1);
|
|
534
564
|
try {
|
|
535
565
|
const bytes = app && app.files.get(resolvePath(app.cwd, name));
|
|
536
566
|
const blob = bytes ? new Blob([bytes]) : await fetch(name).then((r) => {
|
|
@@ -564,15 +594,15 @@ export async function gpu(options = {}) {
|
|
|
564
594
|
if (description.entries.some((e) => e.kind === 3 && e.id === id)) description.group = makeBindGroup(description);
|
|
565
595
|
}
|
|
566
596
|
t.texture.destroy();
|
|
567
|
-
event(LOADED, id, new Uint8Array(0));
|
|
597
|
+
event(LOADED, id, new Uint8Array(0), to);
|
|
568
598
|
} catch (e) {
|
|
569
|
-
error(id, `cannot load the texture ${name}: ${e.message}
|
|
599
|
+
error(id, `cannot load the texture ${name}: ${e.message}`, to);
|
|
570
600
|
} finally {
|
|
571
|
-
|
|
601
|
+
counted(to, -1);
|
|
572
602
|
}
|
|
573
603
|
}
|
|
574
604
|
|
|
575
|
-
function makeBindGroup({ id, layout, entries, layers }) {
|
|
605
|
+
function makeBindGroup({ id, layout, entries, layers, tag: to }) {
|
|
576
606
|
return checked(`bind group ${id}`, id, () => device.createBindGroup({
|
|
577
607
|
label: `bind group ${id}`,
|
|
578
608
|
layout,
|
|
@@ -590,7 +620,7 @@ export async function gpu(options = {}) {
|
|
|
590
620
|
}
|
|
591
621
|
return { binding: e.binding, resource };
|
|
592
622
|
}),
|
|
593
|
-
}));
|
|
623
|
+
}), to);
|
|
594
624
|
}
|
|
595
625
|
|
|
596
626
|
function readTexture(encoder, id, mip, layer, x, y, width, height) {
|
|
@@ -624,17 +654,25 @@ export async function gpu(options = {}) {
|
|
|
624
654
|
app = theApp;
|
|
625
655
|
return {
|
|
626
656
|
source: SOURCE,
|
|
627
|
-
apply(ptr, len) {
|
|
657
|
+
apply(ptr, len, t) {
|
|
628
658
|
const memory = caller.memory();
|
|
629
659
|
const w = ptr % 4 === 0
|
|
630
660
|
? new Uint32Array(memory.buffer, memory.byteOffset + ptr, len >>> 2)
|
|
631
661
|
: new Uint32Array(memory.slice(ptr, ptr + len).buffer);
|
|
632
662
|
const f = new Float32Array(w.buffer, w.byteOffset, w.length);
|
|
633
663
|
const string = (at) => text.decode(memory.subarray(w[at], w[at] + w[at + 1]));
|
|
634
|
-
run(memory, w, f, string);
|
|
664
|
+
run(memory, w, f, string, t);
|
|
665
|
+
},
|
|
666
|
+
loading(t) {
|
|
667
|
+
return loading.get(t) || 0;
|
|
668
|
+
},
|
|
669
|
+
listen(t) {
|
|
670
|
+
const id = nextListener++;
|
|
671
|
+
listeners.set(id, t);
|
|
672
|
+
return id;
|
|
635
673
|
},
|
|
636
|
-
|
|
637
|
-
|
|
674
|
+
unlisten(id) {
|
|
675
|
+
listeners.delete(id);
|
|
638
676
|
},
|
|
639
677
|
};
|
|
640
678
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
;; WebGPU: the host module "gpu".
|
|
2
|
-
|
|
1
|
+
;; WebGPU: the host module "gpu". gpu-apply runs a batch of commands, and the events it causes go to its tag: errors,
|
|
2
|
+
;; loaded texture files and finished reads. gpu-loading counts the texture files loading for a tag. Each listener's tag
|
|
3
|
+
;; hears the errors about the whole device, such as its loss. gpu-listen gives the listener's number, and gpu-unlisten
|
|
4
|
+
;; takes it.
|
|
5
|
+
(provide gpu-source gpu-error gpu-loaded gpu-pixels gpu-bytes gpu-apply gpu-loading gpu-listen gpu-unlisten)
|
|
3
6
|
|
|
4
7
|
(const gpu-source u32 2)
|
|
5
8
|
(const gpu-error u32 1)
|
|
@@ -7,5 +10,16 @@
|
|
|
7
10
|
(const gpu-pixels u32 3)
|
|
8
11
|
(const gpu-bytes u32 4)
|
|
9
12
|
|
|
10
|
-
(import
|
|
11
|
-
(import
|
|
13
|
+
(import apply "gpu" "apply" (i32 i32 i32 ->))
|
|
14
|
+
(import loading "gpu" "loading" (i32 -> i32))
|
|
15
|
+
(import listen "gpu" "listen" (i32 -> i32))
|
|
16
|
+
(import unlisten "gpu" "unlisten" (i32 ->))
|
|
17
|
+
|
|
18
|
+
(define (gpu-apply [commands (slice u8)] [tag (ptr void)])
|
|
19
|
+
(apply (cast i32 (-> commands items)) (cast i32 (-> commands length)) (cast i32 tag)))
|
|
20
|
+
|
|
21
|
+
(define (gpu-loading [tag (ptr void)]) -> u32 (cast u32 (loading (cast i32 tag))))
|
|
22
|
+
|
|
23
|
+
(define (gpu-listen [tag (ptr void)]) -> u32 (cast u32 (listen (cast i32 tag))))
|
|
24
|
+
|
|
25
|
+
(define (gpu-unlisten [id u32]) (unlisten (cast i32 id)))
|
|
@@ -8,7 +8,12 @@ const CHANGED = 1;
|
|
|
8
8
|
|
|
9
9
|
export function locationModule() {
|
|
10
10
|
return (caller) => {
|
|
11
|
-
const
|
|
11
|
+
const listeners = new Map();
|
|
12
|
+
let next = 1;
|
|
13
|
+
const popped = () => {
|
|
14
|
+
const url = utf8.encode(globalThis.location.href);
|
|
15
|
+
for (const [id, tag] of listeners) caller.event(CHANGED, id, tag, 0, url);
|
|
16
|
+
};
|
|
12
17
|
addEventListener('popstate', popped);
|
|
13
18
|
const move = (go, ptr, len, err) => {
|
|
14
19
|
try {
|
|
@@ -30,6 +35,15 @@ export function locationModule() {
|
|
|
30
35
|
back() {
|
|
31
36
|
history.back();
|
|
32
37
|
},
|
|
38
|
+
listen(tag) {
|
|
39
|
+
const id = next++;
|
|
40
|
+
listeners.set(id, tag);
|
|
41
|
+
return id;
|
|
42
|
+
},
|
|
43
|
+
unlisten(id) {
|
|
44
|
+
listeners.delete(id);
|
|
45
|
+
caller.forget(id);
|
|
46
|
+
},
|
|
33
47
|
};
|
|
34
48
|
};
|
|
35
49
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
;; The page's URL and history: the host module "location". When the user goes back or forward,
|
|
2
|
-
;; event
|
|
3
|
-
|
|
1
|
+
;; The page's URL and history: the host module "location". When the user goes back or forward, each listener's tag
|
|
2
|
+
;; gets a changed event with the URL as its bytes. location-listen gives the listener's number, and location-unlisten
|
|
3
|
+
;; takes it.
|
|
4
|
+
(provide location-source location-changed location-get location-push location-replace location-back location-listen
|
|
5
|
+
location-unlisten)
|
|
4
6
|
|
|
5
7
|
(import get "location" "get" (i32 -> i32))
|
|
6
8
|
(import push "location" "push" (i32 i32 i32 -> i32))
|
|
7
9
|
(import replace "location" "replace" (i32 i32 i32 -> i32))
|
|
8
10
|
(import back "location" "back" (->))
|
|
11
|
+
(import listen "location" "listen" (i32 -> i32))
|
|
12
|
+
(import unlisten "location" "unlisten" (i32 ->))
|
|
9
13
|
|
|
10
14
|
(const location-source u32 12)
|
|
11
15
|
(const location-changed u32 1)
|
|
@@ -23,3 +27,7 @@
|
|
|
23
27
|
(host-failed! (& why) err))))
|
|
24
28
|
|
|
25
29
|
(define (location-back) (back))
|
|
30
|
+
|
|
31
|
+
(define (location-listen [tag (ptr void)]) -> u32 (cast u32 (listen (cast i32 tag))))
|
|
32
|
+
|
|
33
|
+
(define (location-unlisten [id u32]) (unlisten (cast i32 id)))
|