xbintsc 0.5.31 → 0.5.32

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 (181) hide show
  1. package/README.md +16 -10
  2. package/dist/src/binder/references.js +14 -1
  3. package/dist/src/binder/references.js.map +1 -1
  4. package/dist/src/cli/help.d.ts +1 -1
  5. package/dist/src/codegen/generator/module.d.ts +1 -0
  6. package/dist/src/codegen/generator/module.js +54 -0
  7. package/dist/src/codegen/generator/module.js.map +1 -1
  8. package/dist/src/codegen/generator/statements/variables.js +6 -4
  9. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  10. package/dist/src/driver/version.d.ts +1 -1
  11. package/dist/src/driver/version.js +1 -1
  12. package/dist/src/extensions/gui/css.d.ts +26 -0
  13. package/dist/src/extensions/gui/css.js +100 -0
  14. package/dist/src/extensions/gui/css.js.map +1 -0
  15. package/dist/src/extensions/gui/html.js +9 -2
  16. package/dist/src/extensions/gui/html.js.map +1 -1
  17. package/dist/src/extensions/gui/index.js +16 -2
  18. package/dist/src/extensions/gui/index.js.map +1 -1
  19. package/dist/src/extensions/gui/three.d.ts +16 -0
  20. package/dist/src/extensions/gui/three.js +22 -0
  21. package/dist/src/extensions/gui/three.js.map +1 -0
  22. package/dist/src/extensions/media/index.d.ts +6 -5
  23. package/dist/src/extensions/media/index.js +12 -14
  24. package/dist/src/extensions/media/index.js.map +1 -1
  25. package/dist/tests/e2e/compile-builtins.test.js +24 -0
  26. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  27. package/dist/tests/e2e/compile-objects-classes.test.js +23 -0
  28. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  29. package/dist/tests/e2e/gui-browser.test.js +1 -1
  30. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  31. package/dist/tests/e2e/gui-effects.test.d.ts +6 -0
  32. package/dist/tests/e2e/gui-effects.test.js +262 -0
  33. package/dist/tests/e2e/gui-effects.test.js.map +1 -0
  34. package/dist/tests/e2e/gui-example-webgl.test.d.ts +10 -0
  35. package/dist/tests/e2e/gui-example-webgl.test.js +75 -0
  36. package/dist/tests/e2e/gui-example-webgl.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-example-webgl2.test.d.ts +8 -0
  38. package/dist/tests/e2e/gui-example-webgl2.test.js +72 -0
  39. package/dist/tests/e2e/gui-example-webgl2.test.js.map +1 -0
  40. package/dist/tests/e2e/gui-layout.test.js +48 -0
  41. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  42. package/dist/tests/e2e/gui-media.test.js +5 -4
  43. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  44. package/dist/tests/e2e/gui-webgl.test.d.ts +11 -0
  45. package/dist/tests/e2e/gui-webgl.test.js +179 -0
  46. package/dist/tests/e2e/gui-webgl.test.js.map +1 -0
  47. package/dist/tests/e2e/gui-webgl2.test.d.ts +10 -0
  48. package/dist/tests/e2e/gui-webgl2.test.js +238 -0
  49. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -0
  50. package/dist/tests/extensions/gui.test.js +41 -0
  51. package/dist/tests/extensions/gui.test.js.map +1 -1
  52. package/dist/tests/extensions/media.test.js +7 -3
  53. package/dist/tests/extensions/media.test.js.map +1 -1
  54. package/doc/ai/extensions.md +29 -9
  55. package/doc/ai/troubleshooting.md +12 -8
  56. package/doc/ai/zh-CN/extensions.md +26 -8
  57. package/doc/ai/zh-CN/troubleshooting.md +10 -6
  58. package/doc/gui/api.md +3 -2
  59. package/doc/gui/build.md +72 -16
  60. package/doc/gui/canvas.md +11 -3
  61. package/doc/gui/dom.md +33 -18
  62. package/doc/gui/known-issues.md +57 -24
  63. package/doc/gui/layout.md +18 -0
  64. package/doc/gui/media.md +17 -13
  65. package/doc/gui/rendering.md +50 -3
  66. package/doc/gui/webgl.md +256 -0
  67. package/doc/gui.md +108 -17
  68. package/doc/npm-package.md +6 -4
  69. package/doc/requirements.md +18 -9
  70. package/doc/zh-CN/gui/api.md +3 -2
  71. package/doc/zh-CN/gui/build.md +64 -15
  72. package/doc/zh-CN/gui/canvas.md +9 -2
  73. package/doc/zh-CN/gui/dom.md +20 -7
  74. package/doc/zh-CN/gui/known-issues.md +45 -19
  75. package/doc/zh-CN/gui/layout.md +14 -0
  76. package/doc/zh-CN/gui/media.md +15 -11
  77. package/doc/zh-CN/gui/rendering.md +41 -4
  78. package/doc/zh-CN/gui/webgl.md +115 -0
  79. package/doc/zh-CN/gui.md +89 -14
  80. package/doc/zh-CN/npm-package.md +9 -6
  81. package/doc/zh-CN/requirements.md +12 -6
  82. package/llms.txt +2 -0
  83. package/package.json +1 -1
  84. package/runtime/ext_gui/browser.cpp +39 -13
  85. package/runtime/ext_gui/canvas.cpp +3 -18
  86. package/runtime/ext_gui/canvas_internal.h +10 -0
  87. package/runtime/ext_gui/canvas_raster.cpp +60 -0
  88. package/runtime/ext_gui/clip_values.cpp +246 -0
  89. package/runtime/ext_gui/css.cpp +94 -0
  90. package/runtime/ext_gui/css.h +22 -1
  91. package/runtime/ext_gui/css_match.cpp +3 -0
  92. package/runtime/ext_gui/css_selector.cpp +9 -0
  93. package/runtime/ext_gui/css_sheet.cpp +89 -45
  94. package/runtime/ext_gui/document.cpp +4 -1
  95. package/runtime/ext_gui/document.h +2 -0
  96. package/runtime/ext_gui/dom.h +16 -0
  97. package/runtime/ext_gui/dom_api.cpp +1 -0
  98. package/runtime/ext_gui/dom_api_canvas.cpp +17 -80
  99. package/runtime/ext_gui/dom_api_canvas_extra.cpp +130 -1
  100. package/runtime/ext_gui/dom_api_canvas_pending.cpp +41 -0
  101. package/runtime/ext_gui/dom_api_internal.h +259 -0
  102. package/runtime/ext_gui/dom_api_webgl.cpp +376 -0
  103. package/runtime/ext_gui/dom_api_webgl2.cpp +434 -0
  104. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +269 -0
  105. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +212 -0
  106. package/runtime/ext_gui/dom_api_webgl_rt.h +30 -0
  107. package/runtime/ext_gui/dom_api_webgl_shader.cpp +443 -0
  108. package/runtime/ext_gui/dom_api_webgl_state.cpp +444 -0
  109. package/runtime/ext_gui/dom_api_webgl_texture.cpp +445 -0
  110. package/runtime/ext_gui/effect_values.cpp +312 -0
  111. package/runtime/ext_gui/gradient_values.cpp +476 -0
  112. package/runtime/ext_gui/grid_values.cpp +230 -0
  113. package/runtime/ext_gui/gui_canvas.cpp +50 -2
  114. package/runtime/ext_gui/gui_engine.h +27 -0
  115. package/runtime/ext_gui/gui_events.cpp +4 -1
  116. package/runtime/ext_gui/gui_media.cpp +1 -1
  117. package/runtime/ext_gui/gui_webgl.cpp +83 -0
  118. package/runtime/ext_gui/layout.cpp +21 -0
  119. package/runtime/ext_gui/layout_flow.cpp +81 -4
  120. package/runtime/ext_gui/layout_flow_internal.h +31 -0
  121. package/runtime/ext_gui/layout_grid.cpp +374 -0
  122. package/runtime/ext_gui/media.h +1 -1
  123. package/runtime/ext_gui/media_api.h +2 -2
  124. package/runtime/ext_gui/media_query.cpp +211 -0
  125. package/runtime/ext_gui/paint.cpp +111 -77
  126. package/runtime/ext_gui/paint.h +33 -3
  127. package/runtime/ext_gui/paint_background.cpp +229 -0
  128. package/runtime/ext_gui/paint_clip.cpp +268 -0
  129. package/runtime/ext_gui/paint_effects.cpp +324 -0
  130. package/runtime/ext_gui/paint_internal.h +85 -0
  131. package/runtime/ext_gui/renderer.cpp +8 -31
  132. package/runtime/ext_gui/renderer_clip.cpp +196 -0
  133. package/runtime/ext_gui/renderer_image.cpp +13 -8
  134. package/runtime/ext_gui/renderer_internal.h +15 -8
  135. package/runtime/ext_gui/renderer_text.cpp +6 -31
  136. package/runtime/ext_gui/style.cpp +343 -20
  137. package/runtime/ext_gui/style.h +144 -3
  138. package/runtime/ext_gui/style_print.cpp +134 -1
  139. package/runtime/ext_gui/style_values.cpp +152 -126
  140. package/runtime/ext_gui/style_values_internal.h +35 -0
  141. package/runtime/ext_gui/webgl.h +466 -0
  142. package/runtime/ext_gui/webgl2_constants.h +274 -0
  143. package/runtime/ext_gui/webgl_buffer.cpp +565 -0
  144. package/runtime/ext_gui/webgl_constants.h +317 -0
  145. package/runtime/ext_gui/webgl_context.cpp +466 -0
  146. package/runtime/ext_gui/webgl_draw.cpp +495 -0
  147. package/runtime/ext_gui/webgl_glsl.h +125 -0
  148. package/runtime/ext_gui/webgl_glsl_compile.cpp +431 -0
  149. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +349 -0
  150. package/runtime/ext_gui/webgl_glsl_scan.cpp +581 -0
  151. package/runtime/ext_gui/webgl_msl.cpp +62 -0
  152. package/runtime/ext_gui/webgl_msl.h +33 -0
  153. package/runtime/ext_gui/webgl_pipeline.cpp +274 -0
  154. package/runtime/ext_gui/webgl_program_internal.h +18 -0
  155. package/runtime/ext_gui/webgl_shader.cpp +582 -0
  156. package/runtime/ext_gui/webgl_texture.cpp +293 -0
  157. package/runtime/ext_gui/webgl_uniform.cpp +167 -0
  158. package/runtime/ext_gui/webgl_webgl2.cpp +224 -0
  159. package/runtime/ext_media/media.cpp +1 -1
  160. package/runtime/ext_media/media_bridge.c +9 -3
  161. package/runtime/rt_internal.h +1 -0
  162. package/runtime/xt_containers/access.inc +8 -0
  163. package/runtime/xt_regex/api.inc +2 -0
  164. package/runtime/xt_regex/matcher.inc +16 -3
  165. package/runtime/xt_regex/parser.inc +8 -2
  166. package/runtime/xt_regex/types.inc +3 -0
  167. package/runtime/xt_stdlib2/regexp-match.inc +60 -21
  168. package/runtime/xt_stdlib2/regexp.inc +1 -0
  169. package/runtime/xt_typed_array.c +1 -1
  170. package/scripts/build-gui.ts +90 -3
  171. package/scripts/build-media.ts +323 -58
  172. package/scripts/setup-vs-toolchain.ps1 +152 -0
  173. package/src/binder/references.ts +13 -1
  174. package/src/codegen/generator/module.ts +50 -0
  175. package/src/codegen/generator/statements/variables.ts +6 -3
  176. package/src/driver/version.ts +1 -1
  177. package/src/extensions/gui/css.ts +113 -0
  178. package/src/extensions/gui/html.ts +9 -2
  179. package/src/extensions/gui/index.ts +16 -2
  180. package/src/extensions/gui/three.ts +25 -0
  181. package/src/extensions/media/index.ts +13 -16
@@ -9,6 +9,7 @@ import {
9
9
  type ArrowFunction,
10
10
  type Block,
11
11
  type Expression,
12
+ type FunctionDeclaration,
12
13
  type Parameter,
13
14
  } from "../../ast/nodes.js";
14
15
  import type { ClassInfo, FunctionInfo, SymbolInfo } from "../../binder/binder.js";
@@ -39,6 +40,7 @@ function symbolSuffix(name: string): string {
39
40
  export interface ModuleMethods {
40
41
  run(this: Generator): string;
41
42
  emitFunction(this: Generator, fn: FunctionInfo): void;
43
+ emitHoistedFunctions(this: Generator, fn: FunctionInfo): void;
42
44
  emitFunctionReturn(this: Generator, value: string): void;
43
45
  emitMain(this: Generator): void;
44
46
  functionName(this: Generator, fn: FunctionInfo): string;
@@ -192,6 +194,14 @@ export const moduleMethods: ModuleMethods = {
192
194
  this.defineCaptureSlot(symbol, value);
193
195
  }
194
196
 
197
+ // Hoisted function declarations: create their closure values in this
198
+ // frame (before the body runs) so a nested function that references one
199
+ // gets the closure built against the declaring frame, not against the
200
+ // caller's slots. Captured function symbols are boxed by the binder, so
201
+ // the second pass publishes the closures into those boxes and mutual
202
+ // recursion sees the same values.
203
+ this.emitHoistedFunctions(fn);
204
+
195
205
  // Arrow functions inherit `this` lexically; it travels as an extra capture.
196
206
  if (fn.isArrow && fn.capturesThis) {
197
207
  const inherited = this.reg();
@@ -337,6 +347,46 @@ export const moduleMethods: ModuleMethods = {
337
347
  return name;
338
348
  },
339
349
 
350
+ emitHoistedFunctions(fn: FunctionInfo): void {
351
+ if (fn.isModule) return;
352
+ const body = (fn.node as { body?: Block }).body;
353
+ if (!body || body.kind !== SyntaxKind.Block) return;
354
+ const declarations: FunctionDeclaration[] = [];
355
+ for (const statement of body.statements) {
356
+ if (statement.kind === SyntaxKind.FunctionDeclaration) {
357
+ declarations.push(statement as FunctionDeclaration);
358
+ }
359
+ }
360
+ if (declarations.length === 0) return;
361
+ /* Captured locals of this frame may be declared textually after the
362
+ * function that uses them. Function declarations are hoisted, so create
363
+ * their boxes now; the `let`/`const`/`var` statement fills the box when it
364
+ * runs (emitVariableStatement writes into an existing slot). */
365
+ for (const declaration of declarations) {
366
+ const child = this.binding.functionOfNode.get(declaration);
367
+ if (!child) continue;
368
+ for (const captured of child.captures) {
369
+ if (captured.fn !== fn) continue;
370
+ if (!this.current.slots.has(captured.id)) this.declareSlot(captured, i64(XT_UNDEFINED));
371
+ }
372
+ }
373
+ /* First pass: a slot (and a box, when captured) exists for every name
374
+ * before any closure is built, so sibling references cannot observe an
375
+ * uninitialised slot. */
376
+ const ready: Array<{ symbol: SymbolInfo; child: FunctionInfo }> = [];
377
+ for (const declaration of declarations) {
378
+ const symbol = this.binding.symbolOfDeclaration.get(declaration);
379
+ const child = this.binding.functionOfNode.get(declaration);
380
+ if (!symbol || !child) continue;
381
+ if (!this.current.slots.has(symbol.id)) this.declareSlot(symbol, i64(XT_UNDEFINED));
382
+ ready.push({ symbol, child });
383
+ }
384
+ /* Second pass: build and store the closures. */
385
+ for (const entry of ready) {
386
+ this.writeSlot(entry.symbol, this.emitClosureValue(entry.child));
387
+ }
388
+ },
389
+
340
390
  emitClassReference(classInfo: ClassInfo): string {
341
391
  const globalName = this.classGlobal(classInfo);
342
392
  const value = this.reg();
@@ -33,9 +33,12 @@ export const variableStatementMethods: VariableStatementMethods = {
33
33
  continue;
34
34
  }
35
35
  if (this.current.slots.has(symbol.id)) {
36
- // Hoisted `var` (or an already-declared slot): keep evaluating the
37
- // initializer for its side effects, but do not redeclare the slot.
38
- if (declaration.initializer) this.emitExpression(declaration.initializer);
36
+ // Hoisted `var`, or a box created early for a captured binding
37
+ // declared textually after a hoisted function: do not redeclare the
38
+ // slot, but assigning an initializer is still part of the statement.
39
+ if (declaration.initializer) {
40
+ this.writeSlot(symbol, this.emitExpression(declaration.initializer));
41
+ }
39
42
  continue;
40
43
  }
41
44
  if (symbol.boxed) {
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.31";
12
+ export const COMPILER_VERSION = "0.5.32";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * GUI extension: inline external stylesheets into the HTML asset.
3
+ *
4
+ * The engine's CSS cascade only reads `<style>` elements (`document.cpp` skips
5
+ * `<link rel="stylesheet">` because resolving it would need I/O at runtime).
6
+ * A page that ships its CSS as a separate file therefore loses *all* styling
7
+ * once it is compiled. The bundler, unlike the runtime, can read files, so this
8
+ * loader rewrites every local stylesheet link (and every local `@import` inside
9
+ * it) into an inline `<style>` block. The page keeps its author-time structure
10
+ * and the compiled binary has no runtime file dependency.
11
+ *
12
+ * Remote URLs are left untouched: there is no network-backed style loader, so
13
+ * the engine simply ignores them, exactly as before.
14
+ */
15
+
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { dirname, resolve } from "node:path";
18
+
19
+ /** Read a stylesheet as a byte string (one code unit per UTF-8 byte).
20
+ *
21
+ * The bundler hands asset loaders the same shape (`sourceTextBytes`), so CSS
22
+ * must join the HTML *as bytes*. Decoding it to UTF-16 and putting that into
23
+ * the byte-string document would make codegen encode the HTML's own bytes a
24
+ * second time. `toString("latin1")` is exactly the byte-string mapping; the
25
+ * self-hosted runtime's `readFileSync` already returns bytes. */
26
+ function readCssText(file: string): string {
27
+ const data: string | Buffer = readFileSync(file);
28
+ return typeof data === "string" ? data : data.toString("latin1");
29
+ }
30
+
31
+ /** A resolved local stylesheet: its inlined text is returned to the caller. */
32
+ export interface InlinedStylesheets {
33
+ readonly html: string;
34
+ /** Every stylesheet file that was read (for bundler cache invalidation). */
35
+ readonly dependencies: readonly string[];
36
+ }
37
+
38
+ /** True for a URL the compiler cannot read (http:, data:, //host, …). */
39
+ const URL_SPEC = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
40
+
41
+ /** Read one attribute from an element's attribute text. */
42
+ function attribute(attributes: string, name: string): string | undefined {
43
+ const pattern = new RegExp(
44
+ `(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>\`]+))`,
45
+ "i",
46
+ );
47
+ const match = pattern.exec(attributes);
48
+ if (!match) return undefined;
49
+ return match[1] ?? match[2] ?? match[3];
50
+ }
51
+
52
+ /** Strip a `?query` / `#fragment`, then resolve against a base file. */
53
+ function localPath(baseFile: string, specifier: string): string | undefined {
54
+ if (specifier === "" || URL_SPEC.test(specifier)) return undefined;
55
+ const clean = specifier.split(/[?#]/, 1)[0] ?? specifier;
56
+ return resolve(dirname(baseFile), clean);
57
+ }
58
+
59
+ /** True when the `rel` attribute lists `stylesheet` among its tokens. */
60
+ function isStylesheet(rel: string | undefined): boolean {
61
+ if (rel === undefined) return false;
62
+ return rel.toLowerCase().split(/\s+/).includes("stylesheet");
63
+ }
64
+
65
+ /**
66
+ * Inline every local `@import` in `css`. Imports are resolved relative to
67
+ * `cssFile`, which is the file the rules came from (not the HTML page), so a
68
+ * partial that imports a sibling keeps working. A cycle is cut at the repeated
69
+ * file rather than recursing forever.
70
+ */
71
+ function inlineImports(
72
+ css: string,
73
+ cssFile: string,
74
+ dependencies: Set<string>,
75
+ stack: readonly string[],
76
+ ): string {
77
+ /* `@import url("./x.css") screen;` and `@import "./x.css";` are the two
78
+ * spellings the CSS parser accepts. Match one at a time so a single bad
79
+ * import cannot swallow the rest of the sheet. */
80
+ const pattern =
81
+ /@import\s+(?:url\(\s*(?:"([^"]*)"|'([^']*)'|([^'")\s]+))\s*\)|"([^"]*)"|'([^']*)')\s*[^;]*;/gi;
82
+ return css.replace(pattern, (match, a, b, c, d, e) => {
83
+ const specifier = a ?? b ?? c ?? d ?? e;
84
+ if (specifier === undefined) return "";
85
+ const file = localPath(cssFile, specifier);
86
+ if (file === undefined) return match;
87
+ if (stack.includes(file)) return "";
88
+ if (!existsSync(file)) return match;
89
+ dependencies.add(file);
90
+ const nested = readCssText(file);
91
+ return inlineImports(nested, file, dependencies, [...stack, file]);
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Replace `<link rel="stylesheet">` elements with inline `<style>` blocks,
97
+ * recursively resolving local `@import` rules. Returns the rewritten HTML and
98
+ * the list of stylesheet files it read.
99
+ */
100
+ export function inlineStylesheets(htmlFile: string, html: string): InlinedStylesheets {
101
+ const dependencies = new Set<string>();
102
+ const rewritten = html.replace(/<link\b([^>]*)>/gi, (match, attributes: string) => {
103
+ if (!isStylesheet(attribute(attributes, "rel"))) return match;
104
+ const href = attribute(attributes, "href");
105
+ if (href === undefined) return match;
106
+ const file = localPath(htmlFile, href);
107
+ if (file === undefined || !existsSync(file)) return match;
108
+ dependencies.add(file);
109
+ const css = inlineImports(readCssText(file), file, dependencies, [file]);
110
+ return `<style data-xt-href="${href.replace(/"/g, "&quot;")}">\n${css}\n</style>`;
111
+ });
112
+ return { html: rewritten, dependencies: [...dependencies] };
113
+ }
@@ -38,6 +38,7 @@ import { Parser } from "../../parser/parser.js";
38
38
  import { artifactPath } from "../../canonical.js";
39
39
  import { hashString } from "../../driver/cache.js";
40
40
  import type { AssetLoadResult, AssetSourceRange } from "../registry.js";
41
+ import { inlineStylesheets } from "./css.js";
41
42
 
42
43
  /** Matches an inline `<script …>body</script>` element, attributes included. */
43
44
  const SCRIPT_RE = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
@@ -191,7 +192,10 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
191
192
  const importedText = new Set<string>();
192
193
  const bound = new Set<string>();
193
194
 
194
- const html = source.replace(SCRIPT_RE, (match, attributes: string, inlineBody: string) => {
195
+ /* The engine's cascade only reads `<style>` elements, so a linked stylesheet
196
+ * would be dropped at runtime. Inline it (and its `@import`s) now. */
197
+ const styles = inlineStylesheets(path, source);
198
+ const html = styles.html.replace(SCRIPT_RE, (match, attributes: string, inlineBody: string) => {
195
199
  if (!isExecutable(attribute(attributes, "type"), attribute(attributes, "lang"))) return match;
196
200
  const src = attribute(attributes, "src");
197
201
 
@@ -244,6 +248,9 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
244
248
  offset += part.length + 1;
245
249
  }
246
250
  parts.push(`export default ${JSON.stringify(html)};`);
247
- const result: AssetLoadResult = { moduleSource: parts.join("\n") };
251
+ const result: AssetLoadResult = {
252
+ moduleSource: parts.join("\n"),
253
+ ...(styles.dependencies.length > 0 ? { dependencies: styles.dependencies } : {}),
254
+ };
248
255
  return sourceRanges.length > 0 ? { ...result, sourceRanges } : result;
249
256
  }
@@ -26,6 +26,7 @@ import type { Extension, ExtensionModule } from "../registry.js";
26
26
  import { findRuntimeDir, platformSlug } from "../../driver/paths.js";
27
27
  import { findRuntimeLibrary } from "../../driver/runtime-lib.js";
28
28
  import { loadHtmlAsset } from "./html.js";
29
+ import { loadScriptAsset } from "./three.js";
29
30
 
30
31
  /** Top-level `gui` module bindings. */
31
32
  const guiModule: ExtensionModule = {
@@ -77,6 +78,19 @@ const hostTypes = [
77
78
  "ReadableStream",
78
79
  "WebGLRenderingContext",
79
80
  "WebGL2RenderingContext",
81
+ "WebGLBuffer",
82
+ "WebGLTexture",
83
+ "WebGLShader",
84
+ "WebGLProgram",
85
+ "WebGLFramebuffer",
86
+ "WebGLRenderbuffer",
87
+ "WebGLUniformLocation",
88
+ "WebGLActiveInfo",
89
+ "WebGLVertexArrayObject",
90
+ "WebGLSampler",
91
+ "WebGLQuery",
92
+ "WebGLSync",
93
+ "WebGLTransformFeedback",
80
94
  "XRWebGLLayer",
81
95
  "XRWebGLBinding",
82
96
  ] as const;
@@ -186,6 +200,6 @@ export const guiExtension: Extension = {
186
200
  }),
187
201
  /** Browser globals (`window`, `document`, the DOM type names). */
188
202
  globalValues: () => browserGlobals,
189
- /** Rewrite imported `.html` assets (see `html.ts`). */
190
- assetLoaders: () => ({ ".html": loadHtmlAsset }),
203
+ /** Rewrite imported `.html` assets and patch three.js's shader unroller. */
204
+ assetLoaders: () => ({ ".html": loadHtmlAsset, ".js": loadScriptAsset, ".mjs": loadScriptAsset }),
191
205
  };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * GUI extension: three.js shader-unroller compatibility.
3
+ *
4
+ * three.js's `WebGLRenderer` runs unchanged on this engine's real WebGL 2
5
+ * backend; no Canvas2D fallback (and no renderer wrapper) is needed. The one
6
+ * shader-side workaround is its `unrollLoops` pass: its lazy global regex does
7
+ * not behave reliably on this runtime, and the shader rewriter already defines
8
+ * `UNROLLED_LOOP_INDEX` and keeps the numeric `for` loop. Disabling the pattern
9
+ * therefore leaves valid GLSL instead of a half-expanded loop.
10
+ */
11
+
12
+ import type { AssetLoadResult } from "../registry.js";
13
+
14
+ /** three.js's unroll-loop regex declaration (r150–r16x naming). */
15
+ const LOOP_PATTERN = /const unrollLoopPattern = [^\n]*;/;
16
+
17
+ /**
18
+ * Patch a three.js ES module so its shaders keep their light loops. Any other
19
+ * `.js` asset is returned verbatim.
20
+ */
21
+ export function loadScriptAsset(path: string, source: string): AssetLoadResult {
22
+ return {
23
+ moduleSource: source.replace(LOOP_PATTERN, "const unrollLoopPattern = /__xt_never_match__/;"),
24
+ };
25
+ }
@@ -6,8 +6,9 @@
6
6
  * so a program that only needs windows and HTML/CSS does not pay for a media
7
7
  * stack it never uses. This extension supplies the decoding half:
8
8
  *
9
- * - `media.a` (built by `npm run media`) contains the FFmpeg-linked player
10
- * from `runtime/ext_media/media.cpp` and the static FFmpeg libraries;
9
+ * - `media.a` (`media.lib` with the MSVC ABI on Windows, built by
10
+ * `npm run media`) contains the FFmpeg-linked player from
11
+ * `runtime/ext_media/media.cpp` and the static FFmpeg libraries;
11
12
  * - `runtime/ext_media/media_bridge.c` is compiled and linked directly. Its
12
13
  * constructor installs the player table into the GUI engine and references
13
14
  * the backend entry points, which makes the linker extract the archive
@@ -19,9 +20,9 @@
19
20
  *
20
21
  * xbintsc run app.ts --ext gui,media
21
22
  *
22
- * The archive is POSIX-only for now (the build needs FFmpeg's `configure` and
23
- * an MSVC-compatible FFmpeg has not been built), so Windows reports an
24
- * actionable error instead of linking an inert element.
23
+ * The archive is built for every host platform. On Windows FFmpeg is compiled
24
+ * with the MSVC-targeting clang-cl so its COFF objects and static CRT match
25
+ * `gui.lib`; see `doc/gui/build.md`.
25
26
  */
26
27
 
27
28
  import { join } from "node:path";
@@ -31,7 +32,7 @@ import { findRuntimeLibrary } from "../../driver/runtime-lib.js";
31
32
 
32
33
  /** Absolute path to the per-platform prebuilt media archive. */
33
34
  function mediaArchive(): string {
34
- return join(findRuntimeDir(), "lib", platformSlug(), "media.a");
35
+ return join(findRuntimeDir(), "lib", platformSlug(), process.platform === "win32" ? "media.lib" : "media.a");
35
36
  }
36
37
 
37
38
  export const mediaExtension: Extension = {
@@ -45,23 +46,19 @@ export const mediaExtension: Extension = {
45
46
  */
46
47
  runtimeSources: () => [join(findRuntimeDir(), "ext_media", "media_bridge.c")],
47
48
  nativeObjects: () => {
48
- if (process.platform === "win32") {
49
- throw new Error(
50
- "the media extension is not available on Windows yet: it needs an MSVC-compatible " +
51
- "FFmpeg build (see doc/gui/known-issues.md). Build without --ext media; media " +
52
- "elements then report an 'error' event.",
53
- );
54
- }
55
49
  const archive = findRuntimeLibrary("media");
56
50
  if (archive === undefined) {
57
51
  throw new Error(
58
- `media native library not found at ${mediaArchive()} (or media.lib).\n` +
52
+ `media native library not found at ${mediaArchive()} (media.a or media.lib).\n` +
59
53
  `Build it for ${platformSlug()} first with \`npm run media\` (see doc/gui/build.md).`,
60
54
  );
61
55
  }
62
56
  return [archive];
63
57
  },
64
58
  /** `media.a` is C++ and FFmpeg itself needs libm; the GUI extension already
65
- * contributes the C++ runtime and the OS frameworks. */
66
- linkerFlags: () => (process.platform === "win32" ? [] : ["-lm"]),
59
+ * contributes the C++ runtime and the OS frameworks. On Windows the FFmpeg
60
+ * objects reference the Win32 crypto/COM services (`bcrypt`, `ole32`) and
61
+ * `user32`. */
62
+ linkerFlags: () =>
63
+ process.platform === "win32" ? ["-lbcrypt", "-lole32", "-luser32"] : ["-lm"],
67
64
  };