substrate-lang 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/compiler/{checker.4dd4556dd421@0+0.wasm → checker.1d67e1d4e91a@0+0.wasm} +0 -0
- package/build/compiler/{expander.967ce443d379@0+0.wasm → expander.34110a31a949@0+0.wasm} +0 -0
- package/build/compiler/{main.4fc52f204d54@0+0.wasm → main.ca9b5089c24d@0+0.wasm} +0 -0
- package/build/compiler/program +4 -4
- package/build/compiler/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
- package/build/sub/{build.459c5b9412bd@0+0.wasm → build.eef7dfa467ec@0+0.wasm} +0 -0
- package/build/sub/{common.1edc613607a0@0+0.wasm → common.7617940db2dc@0+0.wasm} +0 -0
- package/build/sub/deflate.23f7c4679260@0+0.wasm +0 -0
- package/build/sub/{doc.0775df5de6fe@0+0.wasm → doc.92d6d82d99c1@0+0.wasm} +0 -0
- package/build/sub/{doc.ef9965d5d402@0+0.wasm → doc.dab689b811ca@0+0.wasm} +0 -0
- package/build/sub/{fetch.96b2d10f025e@0+0.wasm → fetch.3367ff4f88c0@0+0.wasm} +0 -0
- package/build/sub/{files.ad0469fdf6fa@0+0.wasm → files.5ae9d3cebeca@0+0.wasm} +0 -0
- package/build/sub/gzip.16e372eda1cf@0+0.wasm +0 -0
- package/build/sub/{hash.660682e4f491@0+0.wasm → hash.c57c141b4862@0+0.wasm} +0 -0
- package/build/sub/{html.a23126eb949a@0+0.wasm → html.803f73c35c4a@0+0.wasm} +0 -0
- package/build/sub/{manifest.ef317480deb0@0+0.wasm → manifest.da52a88c7af7@0+0.wasm} +0 -0
- package/build/sub/{map.f455cca6a42a@0+0.wasm → map.5c8084999550@0+0.wasm} +0 -0
- package/build/sub/program +22 -22
- package/build/sub/{root.f10a4906f460@0+0.wasm → root.78253db36643@0+0.wasm} +0 -0
- package/build/sub/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
- package/build/sub/sha256.a6c32b04d799@0+0.wasm +0 -0
- package/build/sub/site.47fd989be8fb@0+0.wasm +0 -0
- package/build/sub/site.c908b1f6db12@0+0.wasm +0 -0
- package/build/sub/{store.a24d7a25daa6@0+0.wasm → store.048c77bb124d@0+0.wasm} +0 -0
- package/build/sub/sub.a618f89f2ec8@0+0.wasm +0 -0
- package/build/sub/{test.9bd59c73f65e@0+0.wasm → test.b529bf52ba3e@0+0.wasm} +0 -0
- package/build/sub/{text.1bc90482bd43@0+0.wasm → text.b214933a4984@0+0.wasm} +0 -0
- package/build/sub/{upgrade.39ee449b00d5@0+0.wasm → upgrade.7ec5651ed2e9@0+0.wasm} +0 -0
- package/package.json +1 -1
- package/std/http/server.sub +7 -7
- package/std/http/static.sub +66 -30
- package/std/package/manifest.sub +9 -4
- package/std/package/site.sub +209 -15
- package/std/package.sub +4 -3
- package/std/test/browser.sub +20 -14
- package/std/test/build.sub +8 -6
- package/std/test/page/agent.mjs +8 -2
- package/std/web/canvas.sub +4 -4
- 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 +53 -18
- package/substrate/VERSION +1 -1
- package/substrate/compiler/expander.sub +2 -3
- package/substrate/compiler/roots.sub +12 -5
- package/substrate/host/grants.mjs +3 -3
- package/substrate/host/node/net.mjs +17 -2
- package/substrate/host/node/worker.mjs +3 -1
- package/substrate/host/subhost.mjs +4 -1
- package/substrate/host/web/audio.mjs +18 -10
- package/substrate/host/web/audio.sub +9 -3
- package/substrate/host/web/dom.mjs +167 -66
- package/substrate/host/web/dom.sub +9 -27
- package/substrate/host/web/fs.mjs +3 -3
- package/substrate/host/web/gpu.mjs +65 -27
- package/substrate/host/web/gpu.sub +18 -4
- package/substrate/host/web/location.mjs +15 -1
- package/substrate/host/web/location.sub +11 -3
- package/substrate/host/web/start.mjs +11 -7
- package/substrate/host/web/subhost-web.mjs +54 -33
- package/substrate/host/web/worker-script.mjs +4 -4
- package/substrate/host/web/worker.mjs +4 -4
- package/substrate/host/worker.mjs +1 -1
- package/build/sub/site.1732b58ec0e8@0+0.wasm +0 -0
- package/build/sub/site.ae3913b0b0f4@0+0.wasm +0 -0
- package/build/sub/sub.2e3092140706@0+0.wasm +0 -0
|
@@ -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;
|
|
@@ -117,7 +144,7 @@ export async function gpu(options = {}) {
|
|
|
117
144
|
case 1: {
|
|
118
145
|
const id = w[i + 1];
|
|
119
146
|
const node = theDom && theDom.nodeOf ? theDom.nodeOf(w[i + 2]) : null;
|
|
120
|
-
if (!node) caller.trap(`gpu: surface ${id}:
|
|
147
|
+
if (!node) caller.trap(`gpu: surface ${id}: this module has no dom, or node ${w[i + 2]} is not in the document`);
|
|
121
148
|
if (!(node instanceof HTMLCanvasElement)) caller.trap(`gpu: surface ${id}: node ${w[i + 2]} is not a canvas`);
|
|
122
149
|
if (options.offscreen) {
|
|
123
150
|
const texture = device.createTexture({
|
|
@@ -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,11 +559,11 @@ 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
|
-
const blob = bytes ? new Blob([bytes]) : await fetch(name).then((r) => {
|
|
566
|
+
const blob = bytes ? new Blob([bytes]) : await fetch(app ? app.url(name) : name).then((r) => {
|
|
537
567
|
if (!r.ok) throw new Error(`the server answered ${r.status}`);
|
|
538
568
|
return r.blob();
|
|
539
569
|
});
|
|
@@ -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
|
|
2
|
-
;; event
|
|
3
|
-
|
|
1
|
+
;; The document'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)))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Starts a site's program with the files in
|
|
2
|
-
//
|
|
1
|
+
// Starts a site's program with the files in files.pack, the URL's arg parameters as its arguments, and the host modules
|
|
2
|
+
// it imports. The program, files.pack and assets.list sit beside the host directory. The assets load from the site
|
|
3
|
+
// when the program asks for them. It sets window.modules, sets window.ready once the program has started, and sets
|
|
3
4
|
// window.ended to the program's exit status and window.endReason to why it ended.
|
|
4
5
|
import { boot } from './subhost-web.mjs';
|
|
5
6
|
import { allGrants } from '../grants.mjs';
|
|
@@ -7,6 +8,7 @@ import { allGrants } from '../grants.mjs';
|
|
|
7
8
|
const query = new URLSearchParams(location.search);
|
|
8
9
|
const grants = allGrants();
|
|
9
10
|
const root = document.getElementById('root');
|
|
11
|
+
const built = new URL('../../', import.meta.url);
|
|
10
12
|
const made = {};
|
|
11
13
|
const makers = {
|
|
12
14
|
dom: async () => (await import('./dom.mjs')).dom(root),
|
|
@@ -26,9 +28,10 @@ const makers = {
|
|
|
26
28
|
const make = (name) => (made[name] ||= makers[name]());
|
|
27
29
|
|
|
28
30
|
window.app = await boot({
|
|
29
|
-
program: 'program/',
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
program: new URL('program/', built).href,
|
|
32
|
+
packs: [[new URL('files.pack', built).href, '/']],
|
|
33
|
+
assets: new URL('assets.list', built).href,
|
|
34
|
+
site: document.baseURI,
|
|
32
35
|
args: query.getAll('arg'),
|
|
33
36
|
grants,
|
|
34
37
|
onEnd: (code, reason) => {
|
|
@@ -36,8 +39,9 @@ window.app = await boot({
|
|
|
36
39
|
window.endReason = reason;
|
|
37
40
|
},
|
|
38
41
|
async modules(wanted) {
|
|
39
|
-
const
|
|
40
|
-
|
|
42
|
+
const names = [...wanted].filter((name) => Object.hasOwn(makers, name));
|
|
43
|
+
const given = await Promise.all(names.map(make));
|
|
44
|
+
const modules = Object.fromEntries(names.map((name, i) => [name, given[i]]));
|
|
41
45
|
window.modules = modules;
|
|
42
46
|
return modules;
|
|
43
47
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The web host: runs a program in a browser, with its files kept in memory. A web program has every grant and
|
|
2
2
|
// no fakes. Its workers can be given fewer grants and some fakes (worker-script.mjs).
|
|
3
3
|
import { createHost } from '../subhost-core.mjs';
|
|
4
4
|
import { fsModule } from '../fs.mjs';
|
|
5
5
|
import { allGrants, pairsFrom, requestOf } from '../grants.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { memoryFiles, resolvePath } from './fs.mjs';
|
|
7
7
|
|
|
8
8
|
const text = new TextDecoder();
|
|
9
9
|
const utf8 = new TextEncoder();
|
|
@@ -26,13 +26,13 @@ async function fetchBytes(url) {
|
|
|
26
26
|
return new Uint8Array(await response.arrayBuffer());
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async function
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
async function fetchAssets(url) {
|
|
30
|
+
const urls = new Map();
|
|
31
|
+
for (const line of text.decode(await fetchBytes(url)).split('\n')) {
|
|
32
|
+
const tab = line.indexOf('\t');
|
|
33
|
+
if (tab >= 0) urls.set(resolvePath('/', line.slice(0, tab)), line.slice(tab + 1));
|
|
34
|
+
}
|
|
35
|
+
return urls;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function lines(line) {
|
|
@@ -92,36 +92,50 @@ export function importedModules(bytes) {
|
|
|
92
92
|
return names;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
async function fetchProgram(base, wasm) {
|
|
96
|
+
const names = text.decode(await fetchBytes(`${base}program`)).split('\n').filter((n) => n !== '');
|
|
97
|
+
await Promise.all(names.map(async (name) => {
|
|
98
|
+
wasm.set(name, await fetchBytes(`${base}${encodeURIComponent(name)}.wasm`));
|
|
99
|
+
}));
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The compiled modules for sessions: the compiled file lists them, each one is in <hash>.subc, and compiled.map holds
|
|
104
|
+
// their module map.
|
|
105
|
+
async function fetchCompiled(base) {
|
|
106
|
+
const compiled = new Map();
|
|
107
|
+
const listed = await fetch(`${base}compiled`);
|
|
108
|
+
if (!listed.ok) return compiled;
|
|
109
|
+
const list = new Uint8Array(await listed.arrayBuffer());
|
|
110
|
+
const hashes = text.decode(list).split('\n').filter((l) => l !== '').map((l) => l.slice(0, l.indexOf(' ')));
|
|
111
|
+
const fetched = async (name) => compiled.set(name, await fetchBytes(`${base}${name}`));
|
|
112
|
+
const names = hashes.map((h) => `${h}.subc`);
|
|
113
|
+
compiled.set('compiled', list);
|
|
114
|
+
await Promise.all((hashes.length > 0 ? ['compiled.map', ...names] : []).map(fetched));
|
|
115
|
+
return compiled;
|
|
116
|
+
}
|
|
117
|
+
|
|
95
118
|
// options.modules is the modules, or a function that takes the names the program imports and gives the modules.
|
|
119
|
+
// options.assets is the URL of the site's assets.list.
|
|
96
120
|
export async function boot(options) {
|
|
97
121
|
const base = options.program.endsWith('/') ? options.program : `${options.program}/`;
|
|
98
122
|
const files = options.files || new Map();
|
|
99
|
-
for (const [url, dir] of options.lists || []) await fetchListed(url, dir, files);
|
|
100
|
-
for (const [url, dir] of options.packs || []) unpack(await fetchBytes(url), dir, files);
|
|
101
|
-
|
|
102
|
-
const names = text.decode(await fetchBytes(`${base}program`)).split('\n').filter((n) => n !== '');
|
|
103
123
|
const wasm = new Map();
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let { modules } = options;
|
|
108
|
-
if (typeof modules === 'function') {
|
|
124
|
+
const names = fetchProgram(base, wasm);
|
|
125
|
+
const ask = options.modules;
|
|
126
|
+
const modules = typeof ask !== 'function' ? ask : names.then(() => {
|
|
109
127
|
const wanted = new Set();
|
|
110
128
|
for (const bytes of wasm.values()) for (const name of importedModules(bytes)) wanted.add(name);
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (hashes.length > 0) compiled.set('compiled.map', await fetchBytes(`${base}compiled.map`));
|
|
122
|
-
await Promise.all(hashes.map(async (h) => compiled.set(`${h}.subc`, await fetchBytes(`${base}${h}.subc`))));
|
|
123
|
-
}
|
|
124
|
-
return run({ ...options, files, modules, compiled }, names, wasm);
|
|
129
|
+
return ask(wanted);
|
|
130
|
+
});
|
|
131
|
+
const [listed, given, compiled, urls] = await Promise.all([
|
|
132
|
+
names,
|
|
133
|
+
modules,
|
|
134
|
+
fetchCompiled(base),
|
|
135
|
+
options.assets ? fetchAssets(options.assets) : new Map(),
|
|
136
|
+
...(options.packs || []).map(async ([url, dir]) => unpack(await fetchBytes(url), dir, files)),
|
|
137
|
+
]);
|
|
138
|
+
return run({ ...options, files, modules: given, compiled, urls }, listed, wasm);
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
export function run(options, names, wasm) {
|
|
@@ -136,9 +150,16 @@ export function run(options, names, wasm) {
|
|
|
136
150
|
const output = options.output || ((fd, bytes) => write(fd, decoders[fd].decode(bytes, { stream: true })));
|
|
137
151
|
|
|
138
152
|
const app = { files, cwd, entry: null, ended: false, realTime: [] };
|
|
153
|
+
// app.url gives the URL to load a file from when it is not in memory.
|
|
154
|
+
const site = options.site || (globalThis.document ? document.baseURI : globalThis.location.href);
|
|
155
|
+
const urls = options.urls || new Map();
|
|
156
|
+
app.url = (name) => {
|
|
157
|
+
const path = resolvePath(cwd, name);
|
|
158
|
+
return new URL((urls.get(path) || path.slice(1)).split('/').map(encodeURIComponent).join('/'), site).href;
|
|
159
|
+
};
|
|
139
160
|
const modules = new Map();
|
|
140
161
|
for (const [name, make] of Object.entries(options.modules || {})) modules.set(name, (caller) => make(caller, app));
|
|
141
|
-
if (!modules.has('fs')) modules.set('fs', fsModule(
|
|
162
|
+
if (!modules.has('fs')) modules.set('fs', fsModule(memoryFiles(files, cwd, grants)));
|
|
142
163
|
const report = (message) => output(2, utf8.encode(`error: ${message}\n`));
|
|
143
164
|
const ended = (code, reason) => {
|
|
144
165
|
app.ended = true;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Runs a worker in a Web Worker. The worker can use its side of the worker module and the
|
|
1
|
+
// Runs a worker in a Web Worker. The worker can use its side of the worker module and the web program's modules that a
|
|
2
2
|
// worker may have, within its grants, with the fakes it was given in place of the modules they fake. Its files are a
|
|
3
|
-
// read-only copy of the
|
|
3
|
+
// read-only copy of the web program's files.
|
|
4
4
|
import { run } from './subhost-web.mjs';
|
|
5
5
|
import { fsModule } from '../fs.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { memoryFiles, resolvePath } from './fs.mjs';
|
|
7
7
|
import { workerSide } from '../worker-side.mjs';
|
|
8
8
|
|
|
9
9
|
const loaders = {
|
|
@@ -51,7 +51,7 @@ self.onmessage = async (e) => {
|
|
|
51
51
|
if (m.type === 'start') {
|
|
52
52
|
if (m.heard) side.limitOutput();
|
|
53
53
|
const wasm = new Map(m.modules);
|
|
54
|
-
const modules = { worker: side.worker, fs: fsModule(readOnly(
|
|
54
|
+
const modules = { worker: side.worker, fs: fsModule(readOnly(memoryFiles(m.files, m.cwd, m.grants))) };
|
|
55
55
|
const absent = new Set();
|
|
56
56
|
const fakes = await fakesOf(m);
|
|
57
57
|
for (const name of m.names) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// The host module "worker" in a browser. Each worker runs in a Web Worker, which gets a copy of the
|
|
2
|
-
// the fakes the worker's grants ask for.
|
|
1
|
+
// The host module "worker" in a browser. Each worker runs in a Web Worker, which gets a copy of the web program's files
|
|
2
|
+
// and the fakes the worker's grants ask for.
|
|
3
3
|
import { workerModule } from '../worker.mjs';
|
|
4
4
|
|
|
5
5
|
export function workers() {
|
|
6
6
|
return workerModule({
|
|
7
7
|
spawn(plan, app) {
|
|
8
|
-
if (plan.tls) throw new Error('worker.start: a
|
|
9
|
-
if (plan.ends) throw new Error('worker.start: a
|
|
8
|
+
if (plan.tls) throw new Error('worker.start: a web program\'s workers cannot have TLS files');
|
|
9
|
+
if (plan.ends) throw new Error('worker.start: a web program\'s workers do not end on their own');
|
|
10
10
|
const thread = new Worker(new URL('./worker-script.mjs', import.meta.url), { type: 'module' });
|
|
11
11
|
thread.postMessage({
|
|
12
12
|
type: 'start',
|
|
@@ -86,7 +86,7 @@ export function workerModule({ spawn }) {
|
|
|
86
86
|
|
|
87
87
|
return (theCaller, app) => {
|
|
88
88
|
caller = theCaller;
|
|
89
|
-
// What workers have been sent and not yet started on, which browser tests wait for as they settle a
|
|
89
|
+
// What workers have been sent and not yet started on, which browser tests wait for as they settle a document.
|
|
90
90
|
if (app && app.realTime) {
|
|
91
91
|
app.realTime.push({ pending: () => [...alive.values()].reduce((n, x) => n + x.owed, 0), progress: () => started });
|
|
92
92
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|