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.
Files changed (66) hide show
  1. package/build/compiler/{checker.4dd4556dd421@0+0.wasm → checker.1d67e1d4e91a@0+0.wasm} +0 -0
  2. package/build/compiler/{expander.967ce443d379@0+0.wasm → expander.34110a31a949@0+0.wasm} +0 -0
  3. package/build/compiler/{main.4fc52f204d54@0+0.wasm → main.ca9b5089c24d@0+0.wasm} +0 -0
  4. package/build/compiler/program +4 -4
  5. package/build/compiler/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
  6. package/build/sub/{build.459c5b9412bd@0+0.wasm → build.eef7dfa467ec@0+0.wasm} +0 -0
  7. package/build/sub/{common.1edc613607a0@0+0.wasm → common.7617940db2dc@0+0.wasm} +0 -0
  8. package/build/sub/deflate.23f7c4679260@0+0.wasm +0 -0
  9. package/build/sub/{doc.0775df5de6fe@0+0.wasm → doc.92d6d82d99c1@0+0.wasm} +0 -0
  10. package/build/sub/{doc.ef9965d5d402@0+0.wasm → doc.dab689b811ca@0+0.wasm} +0 -0
  11. package/build/sub/{fetch.96b2d10f025e@0+0.wasm → fetch.3367ff4f88c0@0+0.wasm} +0 -0
  12. package/build/sub/{files.ad0469fdf6fa@0+0.wasm → files.5ae9d3cebeca@0+0.wasm} +0 -0
  13. package/build/sub/gzip.16e372eda1cf@0+0.wasm +0 -0
  14. package/build/sub/{hash.660682e4f491@0+0.wasm → hash.c57c141b4862@0+0.wasm} +0 -0
  15. package/build/sub/{html.a23126eb949a@0+0.wasm → html.803f73c35c4a@0+0.wasm} +0 -0
  16. package/build/sub/{manifest.ef317480deb0@0+0.wasm → manifest.da52a88c7af7@0+0.wasm} +0 -0
  17. package/build/sub/{map.f455cca6a42a@0+0.wasm → map.5c8084999550@0+0.wasm} +0 -0
  18. package/build/sub/program +22 -22
  19. package/build/sub/{root.f10a4906f460@0+0.wasm → root.78253db36643@0+0.wasm} +0 -0
  20. package/build/sub/{roots.cefd10e93684@0+0.wasm → roots.2f1ad1a79459@0+0.wasm} +0 -0
  21. package/build/sub/sha256.a6c32b04d799@0+0.wasm +0 -0
  22. package/build/sub/site.47fd989be8fb@0+0.wasm +0 -0
  23. package/build/sub/site.c908b1f6db12@0+0.wasm +0 -0
  24. package/build/sub/{store.a24d7a25daa6@0+0.wasm → store.048c77bb124d@0+0.wasm} +0 -0
  25. package/build/sub/sub.a618f89f2ec8@0+0.wasm +0 -0
  26. package/build/sub/{test.9bd59c73f65e@0+0.wasm → test.b529bf52ba3e@0+0.wasm} +0 -0
  27. package/build/sub/{text.1bc90482bd43@0+0.wasm → text.b214933a4984@0+0.wasm} +0 -0
  28. package/build/sub/{upgrade.39ee449b00d5@0+0.wasm → upgrade.7ec5651ed2e9@0+0.wasm} +0 -0
  29. package/package.json +1 -1
  30. package/std/http/server.sub +7 -7
  31. package/std/http/static.sub +66 -30
  32. package/std/package/manifest.sub +9 -4
  33. package/std/package/site.sub +209 -15
  34. package/std/package.sub +4 -3
  35. package/std/test/browser.sub +20 -14
  36. package/std/test/build.sub +8 -6
  37. package/std/test/page/agent.mjs +8 -2
  38. package/std/web/canvas.sub +4 -4
  39. package/std/web/dom.sub +45 -11
  40. package/std/web/gpu-commands.sub +439 -0
  41. package/std/web/gpu.sub +17 -424
  42. package/std/web/patches.sub +53 -18
  43. package/substrate/VERSION +1 -1
  44. package/substrate/compiler/expander.sub +2 -3
  45. package/substrate/compiler/roots.sub +12 -5
  46. package/substrate/host/grants.mjs +3 -3
  47. package/substrate/host/node/net.mjs +17 -2
  48. package/substrate/host/node/worker.mjs +3 -1
  49. package/substrate/host/subhost.mjs +4 -1
  50. package/substrate/host/web/audio.mjs +18 -10
  51. package/substrate/host/web/audio.sub +9 -3
  52. package/substrate/host/web/dom.mjs +167 -66
  53. package/substrate/host/web/dom.sub +9 -27
  54. package/substrate/host/web/fs.mjs +3 -3
  55. package/substrate/host/web/gpu.mjs +65 -27
  56. package/substrate/host/web/gpu.sub +18 -4
  57. package/substrate/host/web/location.mjs +15 -1
  58. package/substrate/host/web/location.sub +11 -3
  59. package/substrate/host/web/start.mjs +11 -7
  60. package/substrate/host/web/subhost-web.mjs +54 -33
  61. package/substrate/host/web/worker-script.mjs +4 -4
  62. package/substrate/host/web/worker.mjs +4 -4
  63. package/substrate/host/worker.mjs +1 -1
  64. package/build/sub/site.1732b58ec0e8@0+0.wasm +0 -0
  65. package/build/sub/site.ae3913b0b0f4@0+0.wasm +0 -0
  66. 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
- let loading = 0;
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, 0, kind === ERROR ? errors.invalid : 0, data);
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) => error(0, e.error.message));
84
- device.lost.then((info) => error(0, `the device was lost (${info.reason}): ${info.message}`));
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}: the page gave this module no dom, or node ${w[i + 2]} is not on the page`);
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
- loading++;
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
- loading--;
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
- loading() {
637
- return loading;
674
+ unlisten(id) {
675
+ listeners.delete(id);
638
676
  },
639
677
  };
640
678
  };
@@ -1,5 +1,8 @@
1
- ;; WebGPU: the host module "gpu".
2
- (provide gpu-source gpu-error gpu-loaded gpu-pixels gpu-bytes gpu-apply gpu-loading)
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 gpu-apply "gpu" "apply" (i32 i32 ->))
11
- (import gpu-loading "gpu" "loading" (-> i32))
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 popped = () => caller.event(CHANGED, 0, 0, 0, utf8.encode(globalThis.location.href));
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, an untagged changed
2
- ;; event brings the URL as its bytes.
3
- (provide location-source location-changed location-get location-push location-replace location-back)
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 assets.list and files.pack, the URL's arg parameters as its arguments, and
2
- // the host modules it imports. It sets window.modules, sets window.ready once the program has started, and sets
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
- lists: [['assets.list', '/']],
31
- packs: [['files.pack', '/']],
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 modules = {};
40
- for (const name of wanted) if (Object.hasOwn(makers, name)) modules[name] = await make(name);
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 host for a web page: runs a program in a browser, with its files kept in memory. A page has every grant and
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 { pageFiles, resolvePath } from './fs.mjs';
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 fetchListed(url, dir, files) {
30
- const list = new URL(url, globalThis.document ? document.baseURI : globalThis.location.href);
31
- const paths = text.decode(await fetchBytes(list)).split('\n').filter((p) => p !== '');
32
- await Promise.all(paths.map(async (path) => {
33
- const from = new URL(path.split('/').map(encodeURIComponent).join('/'), list);
34
- files.set(resolvePath(dir, path), await fetchBytes(from));
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
- await Promise.all(names.map(async (name) => {
105
- wasm.set(name, await fetchBytes(`${base}${encodeURIComponent(name)}.wasm`));
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
- modules = await modules(wanted);
112
- }
113
- // The compiled modules for sessions: the compiled file lists them, each one is in <hash>.subc, and compiled.map holds
114
- // their module map.
115
- const compiled = new Map();
116
- const listed = await fetch(`${base}compiled`);
117
- if (listed.ok) {
118
- const list = new Uint8Array(await listed.arrayBuffer());
119
- const hashes = text.decode(list).split('\n').filter((l) => l !== '').map((l) => l.slice(0, l.indexOf(' ')));
120
- compiled.set('compiled', list);
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(pageFiles(files, cwd, grants)));
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 page's modules that a
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 page's files.
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 { pageFiles, resolvePath } from './fs.mjs';
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(pageFiles(m.files, m.cwd, m.grants))) };
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 page's files and
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 page\'s workers cannot have TLS files');
9
- if (plan.ends) throw new Error('worker.start: a page\'s workers do not end on their own');
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 page.
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