xbintsc 0.5.37 → 0.5.38

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 (68) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/codegen/generator/state.js +1 -0
  3. package/dist/src/codegen/generator/state.js.map +1 -1
  4. package/dist/src/codegen/generator/tables.js +1 -0
  5. package/dist/src/codegen/generator/tables.js.map +1 -1
  6. package/dist/src/driver/bundler/deferred.d.ts +63 -0
  7. package/dist/src/driver/bundler/deferred.js +279 -0
  8. package/dist/src/driver/bundler/deferred.js.map +1 -0
  9. package/dist/src/driver/bundler/merge.js +214 -2
  10. package/dist/src/driver/bundler/merge.js.map +1 -1
  11. package/dist/src/driver/runtime-objects.d.ts +1 -1
  12. package/dist/src/driver/runtime-objects.js +1 -0
  13. package/dist/src/driver/runtime-objects.js.map +1 -1
  14. package/dist/src/driver/version.d.ts +1 -1
  15. package/dist/src/driver/version.js +1 -1
  16. package/dist/src/extensions/gui/html.js +89 -13
  17. package/dist/src/extensions/gui/html.js.map +1 -1
  18. package/dist/src/extensions/gui/index.js +1 -0
  19. package/dist/src/extensions/gui/index.js.map +1 -1
  20. package/dist/src/extensions/gui/three.d.ts +4 -3
  21. package/dist/src/extensions/gui/three.js +4 -3
  22. package/dist/src/extensions/gui/three.js.map +1 -1
  23. package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
  24. package/dist/tests/e2e/gui-script-import-order.test.js +161 -0
  25. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
  26. package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
  27. package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
  28. package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
  29. package/dist/tests/e2e/url.test.d.ts +6 -0
  30. package/dist/tests/e2e/url.test.js +114 -0
  31. package/dist/tests/e2e/url.test.js.map +1 -0
  32. package/dist/tests/extensions/gui.test.js +22 -10
  33. package/dist/tests/extensions/gui.test.js.map +1 -1
  34. package/doc/gui/webgl.md +20 -10
  35. package/doc/gui-scripts.md +19 -19
  36. package/doc/gui.md +10 -8
  37. package/doc/implemented/fetch.md +5 -0
  38. package/doc/implemented/modules.md +13 -0
  39. package/doc/implemented/stdlib.md +17 -0
  40. package/doc/unimplemented/deviations.md +1 -0
  41. package/doc/zh-CN/gui/webgl.md +3 -3
  42. package/doc/zh-CN/gui-scripts.md +14 -13
  43. package/doc/zh-CN/gui.md +7 -6
  44. package/doc/zh-CN/implemented/fetch.md +4 -0
  45. package/doc/zh-CN/implemented/modules.md +10 -0
  46. package/doc/zh-CN/implemented/stdlib.md +15 -0
  47. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  48. package/package.json +7 -7
  49. package/runtime/ext_gui/dom_api_webgl.cpp +10 -0
  50. package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
  51. package/runtime/ext_gui/gui_engine.h +2 -0
  52. package/runtime/ext_gui/script.cpp +31 -0
  53. package/runtime/ext_gui/webgl_glsl.h +3 -0
  54. package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
  55. package/runtime/ext_gui/webgl_msl.cpp +4 -0
  56. package/runtime/ext_gui/webgl_shader.cpp +12 -6
  57. package/runtime/rt.h +3 -0
  58. package/runtime/xt_fetch/async.inc +60 -0
  59. package/runtime/xt_url_object.c +463 -0
  60. package/src/codegen/generator/state.ts +1 -0
  61. package/src/codegen/generator/tables.ts +1 -0
  62. package/src/driver/bundler/deferred.ts +351 -0
  63. package/src/driver/bundler/merge.ts +219 -2
  64. package/src/driver/runtime-objects.ts +1 -0
  65. package/src/driver/version.ts +1 -1
  66. package/src/extensions/gui/html.ts +91 -15
  67. package/src/extensions/gui/index.ts +1 -0
  68. package/src/extensions/gui/three.ts +4 -3
package/doc/gui/webgl.md CHANGED
@@ -23,8 +23,11 @@ display.
23
23
  Both **WebGL 1.0** and **WebGL 2.0** are available. A canvas keeps the first
24
24
  context kind it asks for: after `getContext("webgl2")` a later
25
25
  `getContext("webgl")` answers `null`, and the other way round. A WebGL 2 context
26
- also satisfies `instanceof WebGLRenderingContext` (its prototype inherits the
27
- WebGL 1 one) and adds the WebGL 2 constants and methods.
26
+ has its own `WebGL2RenderingContext` prototype (with a copy of the WebGL 1
27
+ constants and methods), so `gl instanceof WebGL2RenderingContext` is true while
28
+ `gl instanceof WebGLRenderingContext` is false, like a browser — libraries such
29
+ as three.js use exactly that check to pick their WebGL 2 path. `gl.constructor.name`
30
+ reports the type as well.
28
31
 
29
32
  ## How a frame reaches the screen
30
33
 
@@ -54,8 +57,9 @@ format, so the engine ships two statically linked translators:
54
57
 
55
58
  - **glslang** compiles the rewritten GLSL to SPIR-V, which the Vulkan backend
56
59
  consumes directly;
57
- - **SPIRV-Cross** cross-compiles that same SPIR-V to Metal Shading Language text,
58
- which the Metal backend compiles at run time.
60
+ - **SPIRV-Cross** cross-compiles that same SPIR-V to Metal Shading Language 2.0
61
+ text (needed for arrays of textures/samplers), which the Metal backend
62
+ compiles at run time.
59
63
 
60
64
  Both live in `gui.a` / `gui.lib`; the Metal path reuses the SPIR-V module rather
61
65
  than growing a second GLSL front end, so both backends accept exactly the same
@@ -96,9 +100,13 @@ reflected offsets; `uniform1i` on a sampler stores the texture unit. The block
96
100
  is pushed with the dynamic uniform-buffer API on every draw.
97
101
 
98
102
  Limits of the translator: struct-typed uniforms, user uniform blocks, uniform
99
- arrays whose size is not resolved by the preprocessor, sampler arrays are rejected (the link/shader info log says so). Shader code inside
100
- `#ifdef` is fine; non-sampler uniform declarations are moved to the enclosing
101
- top-level position so the block stays unconditional.
103
+ arrays whose size is not resolved by the preprocessor, and a sampler array
104
+ indexed by a non-constant expression are rejected (the link/shader info log
105
+ says so). A `#pragma unroll_loop_start` loop is expanded before rewriting, so
106
+ the `sampler2D shadowMap[ NUM_DIR_LIGHT_SHADOWS ]` / `shadowMap[ i ]` shape
107
+ three.js emits becomes constant-indexed. Shader code inside `#ifdef` is fine;
108
+ non-sampler uniform declarations are moved to the enclosing top-level position
109
+ so the block stays unconditional.
102
110
 
103
111
  ### GLSL ES 3.00 (WebGL 2)
104
112
 
@@ -299,9 +307,11 @@ same assertions as the SPIR-V one.
299
307
 
300
308
  ## three.js
301
309
 
302
- The `.js` asset loader only patches three.js's shader unroller; `WebGLRenderer`
303
- runs unchanged on this engine's real WebGL 2 backend, so **no Canvas2D fallback
304
- is bundled any more**. Verified with three.js r160 through SDL_GPU:
310
+ The shader rewriter expands three.js's `#pragma unroll_loop_start` loops into
311
+ constant indices (its sampler arrays would otherwise fail Vulkan GLSL), and the
312
+ `.js` asset loader keeps the renderer's own unroller out of the way.
313
+ `WebGLRenderer` runs unchanged on this engine's real WebGL 2 backend, so **no
314
+ Canvas2D fallback is bundled any more**. Verified with three.js r160 through SDL_GPU:
305
315
  `MeshBasicMaterial`, `MeshStandardMaterial` and `MeshPhongMaterial`,
306
316
  `CanvasTexture`, linear fog, ambient/directional/point/spot/hemisphere lights,
307
317
  `Sprite`, `Line`, `Points` and `InstancedMesh`.
@@ -155,17 +155,18 @@ export default __html;
155
155
  ### 2b. External `<script src>`
156
156
 
157
157
  `<script src="./app.ts">` is read relative to the HTML file, parsed, and split:
158
- its top-level `import`/`export … from` statements are **hoisted** to the
159
- generated module (with relative specifiers rewritten so they resolve from the
160
- HTML file's directory), and the remaining statements are wrapped in the script
161
- function. This is how an external script still runs at `loadHTML` time while
162
- being able to `import` other modules.
163
-
164
- Two limitations are enforced with clear errors rather than silent breakage:
165
-
166
- - a missing `src` file throws (turned into a bundler diagnostic), and
167
- - two `src` scripts that import the same local binding name collide; the user
168
- must alias one of them. (Non-conflicting or identical imports are fine.)
158
+ its top-level `import`/`export … from` statements are removed from the body, and
159
+ the remaining statements are wrapped in the script function. Relative imports
160
+ are **deferred**, like a browser module script: the loader emits a synthetic
161
+ `__xt_deferred_module` marker and the generated script calls the module wrapper
162
+ at `loadHTML` time. The bundler wraps the whole local import graph in functions
163
+ that return their exports (memoised by `__xt_defer`), so top-level module code
164
+ runs after the document exists and two scripts importing the same module share
165
+ one instance. Bare specifiers (`gui`, a package) stay eager.
166
+
167
+ A missing `src` file is still a clear error (turned into a bundler diagnostic),
168
+ and `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are
169
+ not compile-time assets and never run.
169
170
 
170
171
  `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are not
171
172
  compile-time assets and never run.
@@ -302,11 +303,11 @@ legacy `target` field over the handle.
302
303
  - **M9b** ✅ — gui `.html` loader (`src/extensions/gui/html.ts`), the
303
304
  `__registerScript` builtin, the script registry (`runtime/ext_gui/script.cpp`),
304
305
  `loadHTML` execution and `DOMContentLoaded`/`load`; e2e coverage.
305
- - **M9c** ✅ — `<script src>` files are read, their top-level imports are
306
- hoisted (specifiers rewritten to resolve from the HTML file) and their body
307
- is wrapped and registered; missing files and import-binding collisions
308
- surface as diagnostics. All scripts run in document order (effectively
309
- deferred). e2e coverage.
306
+ - **M9c** ✅ — `<script src>` files are read and their body is wrapped and
307
+ registered; relative imports are deferred and evaluated by a memoised
308
+ module wrapper at `loadHTML` time, so the import graph sees a parsed
309
+ document and shares one instance per module. Missing files surface as
310
+ diagnostics. All scripts run in document order. e2e coverage.
310
311
  - **M9d** *(optional)* — detect inline HTML in template literals passed to
311
312
  `win.loadHTML(...)` and transform them too (fragile; deferred).
312
313
  - **M10 — polish** ✅ — `requestAnimationFrame`/`cancelAnimationFrame` on the
@@ -330,9 +331,8 @@ legacy `target` field over the handle.
330
331
  network fetches) do not run. Documented.
331
332
  - **Template-literal HTML** passed to `win.loadHTML(…)` is not transformed in
332
333
  M9b (only imported assets are). M9d addresses this if needed.
333
- - **Import-binding collisions** between external `<script src>` files. Detected
334
- and reported (alias the import); a per-script rename with reference rewriting
335
- is possible later.
334
+ - **Top-level `await`** inside a deferred `<script src>` module is not
335
+ supported: the module body is wrapped in a synchronous init function.
336
336
  - **Assets.** `<script src>` is resolved relative to the HTML asset; URL `src`
337
337
  values are ignored. There is no fetch/network loading.
338
338
 
package/doc/gui.md CHANGED
@@ -219,10 +219,11 @@ engine were Rust.
219
219
  registered through the `__registerScript` builtin and replaced by
220
220
  `<script data-xt-id="<hash>">` markers; `win.loadHTML` runs the matching
221
221
  functions after parsing and fires `DOMContentLoaded` then `load`.
222
- - **M9c** ✅ — `<script src>` files are read relative to the HTML, their
223
- top-level imports are hoisted (specifiers rewritten from the HTML dir) and
224
- their body is wrapped/registered; missing files and import-binding
225
- collisions become diagnostics. Everything runs in document order.
222
+ - **M9c** ✅ — `<script src>` files are read relative to the HTML and their
223
+ body is wrapped/registered; relative imports are deferred to `loadHTML`
224
+ through a memoised module wrapper, so imported modules see the parsed
225
+ document and share state. Missing files become diagnostics. Everything
226
+ runs in document order.
226
227
  10. **M10 — polish** ✅
227
228
  - `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`; callbacks
228
229
  run at the top of each frame with the frame timestamp and may mutate the
@@ -387,10 +388,11 @@ engine were Rust.
387
388
  bodies into AOT functions registered at startup and run by `win.loadHTML`
388
389
  (before first layout; `DOMContentLoaded` then `load`). Unit tests in
389
390
  `tests/extensions/gui.test.ts`, e2e in `tests/e2e/gui-*.test.ts`.
390
- - **M9c** ✅ external `<script src>` files are read, their imports hoisted
391
- (specifiers rewritten from the HTML dir) and their body wrapped/registered;
392
- missing files and import-binding collisions are diagnostics. Loader errors are
393
- caught by `loadGraph` and reported as build errors.
391
+ - **M9c** ✅ external `<script src>` files are read and their body
392
+ wrapped/registered; relative imports are deferred to `loadHTML` through a
393
+ memoised module wrapper (so module-level DOM work sees the parsed document),
394
+ while missing files are diagnostics. Loader errors are caught by `loadGraph`
395
+ and reported as build errors.
394
396
  - **M10** ✅ `requestAnimationFrame`/`cancelAnimationFrame` on window handles
395
397
  (callbacks run with the frame timestamp before layout each frame) plus
396
398
  `offsetWidth`/`offsetHeight`/`contains` on element handles; e2e coverage in
@@ -13,6 +13,11 @@ Location: `runtime/xt_fetch.c`, `runtime/xt_fetch/async.inc`, `runtime/xt_async_
13
13
  exchange, up to 20 redirects, and a 301/302/303 switches POST to GET.
14
14
  - It accepts `http://` URLs only — `https://` rejects with a `TypeError` because
15
15
  no TLS backend is linked.
16
+ - A reference with **no scheme** (a relative path) and `file://` URLs read a
17
+ local file, so a compiled program can load an asset from disk without an HTTP
18
+ server. Relative paths resolve against the working directory; `GET` / `HEAD`
19
+ are supported. On Windows the standard `file:///C:/dir/file` spelling and
20
+ `file://C:/dir/file` / `file://C:\dir\file` all name the same file.
16
21
  - Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect`
17
22
  (`follow` / `manual` / `error`).
18
23
  - The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` /
@@ -19,6 +19,19 @@ Location: `src/parser/parser.ts` (syntax), `src/driver/modules.ts` (bundling),
19
19
  - Imported bindings and namespace members are snapshots at module-evaluation time (no live bindings) — see [Unimplemented: module system](../unimplemented/modules.md).
20
20
  - Type-only imports and re-exports (`import type { T } from "..."`, `import { type T } from "..."`, `export type { T } from "..."`) are erased. They are **not** module edges: the target is not loaded or bundled, and a type-only cycle between modules is not a circular dependency. Use a side-effect import (`import "./mod"`) when a module must be evaluated without binding a name.
21
21
 
22
+ ## Deferred evaluation
23
+
24
+ - The `gui` `.html` asset loader marks relative `<script src>` imports with a
25
+ synthetic `__xt_deferred_module` binding. The bundler then wraps that module
26
+ and its local import graph in functions that return their exports, and the
27
+ generated script calls them after `loadHTML` (`__xt_defer` memoises each
28
+ module). This matches a browser module script, where the graph is evaluated
29
+ once the document exists.
30
+ - Modules also reachable through a normal (non-marker) import stay eager and get
31
+ a thin wrapper over their already-initialised exports.
32
+ - The wrapper body is synchronous: top-level `await` inside a deferred script
33
+ module is not supported.
34
+
22
35
  ## import.meta
23
36
 
24
37
  - `import.meta.url` / `.dirname` / `.filename`, resolved from the running executable's path.
@@ -55,6 +55,23 @@ own pages: [Collections](./collections.md), [Typed arrays and binary data](./typ
55
55
  - `Symbol(description)` primitives (`XT_OBJECT_KIND_SYMBOL`), the 13 well-known symbols (`Symbol.iterator`, `Symbol.asyncIterator`, `Symbol.match`, ...), `Symbol.for` / `Symbol.keyFor` global registry, `symbol.description` / `toString()` / `valueOf()`.
56
56
  - Symbols are valid property keys (`Object.getOwnPropertySymbols`, symbol keyed `get`/`set`/`in`/`delete`), are skipped by `Object.keys` / `values` / `entries` / `for...in` / `JSON.stringify`, and print as `Symbol(desc)`.
57
57
 
58
+ ## URL
59
+
60
+ - `URL` is implemented in `runtime/xt_url_object.c`: `new URL(input)` and
61
+ `new URL(input, base)` (the base may be another `URL`), resolving relative
62
+ references against a hierarchical `scheme://authority/path` base.
63
+ - A reference with **no scheme is a filesystem path**: `new URL("./model.glb")`
64
+ resolves against the working directory and yields a `file:` URL (an explicit
65
+ base is still honoured, so `new URL("./x", import.meta.url)` works too).
66
+ - Read-only `href`, `protocol`, `origin`, `host`, `hostname`, `port`,
67
+ `pathname`, `search`, `hash`, `searchParams` and `toString()` / `toJSON()`;
68
+ absolute inputs are kept as written, except that the Windows spellings of a
69
+ `file:` URL (`file://C:/dir` and `file://C:\dir`) are canonicalized to
70
+ `file:///C:/dir`, matching Node.
71
+ - Not a full WHATWG parser: path percent-encoding, IDNA, setters and live
72
+ `searchParams` synchronisation are not implemented — see
73
+ [Runtime deviations](../unimplemented/deviations.md).
74
+
58
75
  ## URLSearchParams
59
76
 
60
77
  - `URLSearchParams` is implemented in `runtime/xt_url.c`.
@@ -8,6 +8,7 @@ These features **compile and run**, but the result does not fully match ECMAScri
8
8
  | --- | --- |
9
9
  | RegExp byte-oriented matching | The engine matches UTF-8 bytes: literal multi-byte text works, but `.` and character classes consume one byte per repetition (`\d` / `\w` stay ASCII). Offsets returned by `exec` / `search` / replacement callbacks are converted to UTF-16 code units |
10
10
  | Global RegExp `lastIndex` | `test` / `exec` do not advance or honour a caller-set `lastIndex` for `/g` / `/y` regexes |
11
+ | `URL` parser | Read-only WHATWG subset: no path percent-encoding, IDNA, setters or live `searchParams`; a reference with no scheme resolves against the working directory as a `file:` URL |
11
12
  | String `normalize` | Not implemented |
12
13
  | First-class built-in methods | Implemented as unbound method values (see [Functions](./functions.md)); `fn.toString()` returns a placeholder rather than source text |
13
14
  | `for...in` | Enumerates own keys of objects / arrays / strings; does not include prototype-chain properties |
@@ -16,7 +16,7 @@ if (gl == null) {
16
16
 
17
17
  2D 上下文把图形光栅化到 CPU 位图再上传为纹理;WebGL 上下文则**在 GPU 上渲染**:顶点变换、光照、纹理采样、深度测试与混合全部在 SDL_GPU 管线中执行(Windows/Linux 上是 Vulkan,macOS 上是 Metal)。页面直接合成该上下文的颜色纹理,因此显示时不需要把像素读回 CPU。
18
18
 
19
- 同时提供 **WebGL 1.0** 与 **WebGL 2.0**。一个 canvas 会固定为它第一次申请的上下文类型:先调用 `getContext("webgl2")` 之后,`getContext("webgl")` 返回 `null`,反之亦然。WebGL 2 上下文也满足 `instanceof WebGLRenderingContext`(其原型继承 WebGL 1 原型),并在其上增加 WebGL 2 的常量与方法。
19
+ 同时提供 **WebGL 1.0** 与 **WebGL 2.0**。一个 canvas 会固定为它第一次申请的上下文类型:先调用 `getContext("webgl2")` 之后,`getContext("webgl")` 返回 `null`,反之亦然。WebGL 2 上下文拥有自己的 `WebGL2RenderingContext` 原型(其中复制了 WebGL 1 的常量与方法),因此 `gl instanceof WebGL2RenderingContext` 为真,而 `gl instanceof WebGLRenderingContext` 为假,与浏览器一致——three.js 等库正是据此选择 WebGL 2 路径。`gl.constructor.name` 也会报告对应类型名。
20
20
 
21
21
  ## 一帧如何到达屏幕
22
22
 
@@ -53,7 +53,7 @@ SDL_GPU 不能在运行时编译 GLSL,且每个后端只消费自己的格式
53
53
  | `gl_FragData[0]` | `xt_FragColor` |
54
54
  | 缺少 `#version` / `#version 100` | `#version 310 es` |
55
55
 
56
- 两个阶段的非采样器 uniform 会合并成同一个块,并在每个阶段各声明一次(顶点 set 1、片元 set 3),与 SDL_GPU 的 Vulkan 描述符布局一致。在 Metal 上同一批声明会变成 `[[buffer(0)]]` 与 `[[texture(N)]]`/`[[sampler(N)]]`,正是 SDL_GPU Metal 后端绑定的槽位。`uniformMatrix4fv` 按列主序写入反射出的偏移;对采样器调用 `uniform1i` 时记录纹理单元。每次绘制都用动态 uniform 缓冲接口推送该块。
56
+ 实现限制:结构体 uniform、用户 uniform 块、预处理阶段无法确定长度的 uniform 数组,以及用非常量表达式索引的采样器数组会被拒绝(shader/link info log 会说明)。`#pragma unroll_loop_start` 循环会在改写前展开,因此 three.js 生成的 `sampler2D shadowMap[ NUM_DIR_LIGHT_SHADOWS ]` / `shadowMap[ i ]` 会变成常量索引。`#ifdef` 内的着色器代码可用;非采样器 uniform 声明会被移动到顶层,保证块保持无条件。两个阶段的非采样器 uniform 会合并成同一个块,并在每个阶段各声明一次(顶点 set 1、片元 set 3),与 SDL_GPU 的 Vulkan 描述符布局一致。在 Metal 上同一批声明会变成 `[[buffer(0)]]` 与 `[[texture(N)]]`/`[[sampler(N)]]`,正是 SDL_GPU Metal 后端绑定的槽位。`uniformMatrix4fv` 按列主序写入反射出的偏移;对采样器调用 `uniform1i` 时记录纹理单元。每次绘制都用动态 uniform 缓冲接口推送该块。
57
57
 
58
58
  翻译器的限制:结构体 uniform、用户 uniform 块、预处理后仍无法确定长度的 uniform 数组、采样器数组与采样器类型不匹配的声明会被拒绝(着色器/链接日志会说明)。`#ifdef` 内的代码没问题;非采样器 uniform 声明会被移到外层顶层位置,保证块是无条件的。
59
59
 
@@ -116,6 +116,6 @@ transform feedback(入口为空操作)与 MSAA/采样缓冲(SDL_GPU 的 Me
116
116
 
117
117
  ## three.js
118
118
 
119
- `.js` 资源加载器现在只修补 three.js 的着色器循环展开;`WebGLRenderer` 在本引擎真正的 WebGL 2 后端上原样运行,**不再打包 Canvas2D 回退**。已用 three.js r160 通过 SDL_GPU 验证:`MeshBasicMaterial`、`MeshStandardMaterial`、`MeshPhongMaterial`、`CanvasTexture`、线性雾、环境光/平行光/点光/聚光/半球光、`Sprite`、`Line`、`Points` 与 `InstancedMesh`。
119
+ 着色器改写器会把 three.js 的 `#pragma unroll_loop_start` 循环展开为常量索引(否则其采样器数组无法通过 Vulkan GLSL),`.js` 资源加载器则避免渲染器自带的展开器重复处理。`WebGLRenderer` 在本引擎真正的 WebGL 2 后端上原样运行,**不再打包 Canvas2D 回退**。已用 three.js r160 通过 SDL_GPU 验证:`MeshBasicMaterial`、`MeshStandardMaterial`、`MeshPhongMaterial`、`CanvasTexture`、线性雾、环境光/平行光/点光/聚光/半球光、`Sprite`、`Line`、`Points` 与 `InstancedMesh`。
120
120
 
121
121
  本引擎子集之外的能力仍无法模拟:transform feedback 与 MSAA;立方体贴图/环境贴图、深度纹理、HDR/浮点渲染目标与压缩纹理已经可用。需要这些能力的场景无法正确渲染;此前 Canvas2D 回退能显示的内容现在全部走 GPU。
@@ -146,15 +146,16 @@ export default __html;
146
146
  ### 2b. 外部 `<script src>`
147
147
 
148
148
  `<script src="./app.ts">` 相对 HTML 文件读取、解析并拆分:其顶层
149
- `import`/`export … from` 语句被**提升**到生成的模块(相对说明符被重写以便
150
- 从 HTML 文件所在目录解析),其余语句被包装进脚本函数。这样外部脚本既能在
151
- `loadHTML` 时运行,又能 `import` 其他模块。
152
-
153
- 两个限制被强制执行并以清晰的错误报告,而不是悄悄出错:
154
-
155
- - 缺失的 `src` 文件会抛错(被转换为 bundler 诊断),并且
156
- - 两个 `src` 脚本导入同一个本地绑定名会冲突;用户必须给其中一个起别名。
157
- (不冲突或完全相同的导入没问题。)
149
+ `import`/`export … from` 语句从主体中移除,其余语句被包装进脚本函数。相对
150
+ import 会像浏览器的模块脚本一样**延迟**:loader 生成一个合成的
151
+ `__xt_deferred_module` 标记,生成的脚本在 `loadHTML` 时调用模块包装函数。
152
+ bundler 把整个本地 import 图包装成返回各自导出的函数(由 `__xt_defer`
153
+ 记忆化),因此模块顶层代码在文档就绪后才运行,且两个脚本导入同一模块时共享
154
+ 同一个实例。裸说明符(`gui`、npm 包)仍然立即加载。
155
+
156
+ 缺失的 `src` 文件仍会抛出清晰的错误(被转换为 bundler 诊断);
157
+ `src` URL(`https://…`、`data:…`、`//host`)保持原样 —— 它们不是编译期资产,
158
+ 永不运行。
158
159
 
159
160
  `src` URL(`https://…`、`data:…`、`//host`)保持原样 —— 它们不是编译期资产,
160
161
  永不运行。
@@ -283,10 +284,10 @@ Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.cla
283
284
  - **M9b** ✅ —— gui `.html` 加载器(`src/extensions/gui/html.ts`)、
284
285
  `__registerScript` 内置函数、脚本注册表(`runtime/ext_gui/script.cpp`)、
285
286
  `loadHTML` 执行与 `DOMContentLoaded`/`load`;e2e 覆盖。
286
- - **M9c** ✅ —— 读取 `<script src>` 文件,其顶层 import 被提升(说明符被
287
- 重写以便从 HTML 文件解析),其主体被包装并注册;缺失文件与 import 绑定
288
- 冲突作为诊断浮现。所有脚本按文档顺序运行(实际上是 deferred)。e2e
289
- 覆盖。
287
+ - **M9c** ✅ —— 读取 `<script src>` 文件,其主体被包装并注册;相对 import
288
+ 通过带记忆化的模块包装函数延迟到 `loadHTML` 求值,因此 import 图能看到
289
+ 已解析的文档,且同一模块只实例化一次。缺失文件作为诊断浮现。所有脚本按
290
+ 文档顺序运行。e2e 覆盖。
290
291
  - **M9d** *(可选)* —— 检测传给 `win.loadHTML(...)` 的模板字面量中的内联
291
292
  HTML 并同样转换它们(脆弱;推迟)。
292
293
  - **M10 — 打磨** ✅ —— 窗口上的
package/doc/zh-CN/gui.md CHANGED
@@ -167,9 +167,10 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
167
167
  `__registerScript` 内置函数注册,并替换为
168
168
  `<script data-xt-id="<hash>">` 标记;`win.loadHTML` 在解析后运行匹配的
169
169
  函数并触发 `DOMContentLoaded` 然后是 `load`。
170
- - **M9c** ✅ —— `<script src>` 文件相对 HTML 读取,其顶层 import 被提升
171
- (说明符从 HTML 目录重写),其主体被包装/注册;缺失文件与 import 绑定
172
- 冲突会成为诊断。一切都按文档顺序运行。
170
+ - **M9c** ✅ —— `<script src>` 文件相对 HTML 读取,其主体被包装/注册;
171
+ 相对 import 通过带记忆化的模块包装函数延迟到 `loadHTML` 求值,因此被导入
172
+ 模块能看到已解析的文档并共享状态。缺失文件会成为诊断。一切都按文档顺序
173
+ 运行。
173
174
  10. **M10 — 打磨** ✅
174
175
  - `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`;回调
175
176
  在每帧顶部以帧时间戳运行,并可修改 DOM
@@ -317,9 +318,9 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
317
318
  编译为启动时注册、由 `win.loadHTML` 运行的 AOT 函数(在首次布局之前;
318
319
  `DOMContentLoaded` 然后是 `load`)。`tests/extensions/gui.test.ts` 中的单元
319
320
  测试,`tests/e2e/gui-*.test.ts` 中的 e2e。
320
- - **M9c** ✅ 外部 `<script src>` 文件被读取,其 import 被提升(说明符从 HTML
321
- 目录重写),其主体被包装/注册;缺失文件与 import 绑定冲突会成为诊断。
322
- 加载器错误被 `loadGraph` 捕获并报告为构建错误。
321
+ - **M9c** ✅ 外部 `<script src>` 文件被读取,其主体被包装/注册;相对 import
322
+ 通过带记忆化的模块包装函数延迟到 `loadHTML`(模块级 DOM 工作因此能看到已解析
323
+ 文档);缺失文件会成为诊断。加载器错误被 `loadGraph` 捕获并报告为构建错误。
323
324
  - **M10** ✅ 窗口句柄上的 `requestAnimationFrame`/`cancelAnimationFrame`
324
325
  (回调在每帧布局之前以帧时间戳运行),以及元素句柄上的
325
326
  `offsetWidth`/`offsetHeight`/`contains`;e2e 覆盖在
@@ -7,6 +7,10 @@
7
7
  - 全局 `fetch(input, init)` 是 HTTP/1.1 客户端。阻塞的套接字交换(`getaddrinfo`、connect、send、receive)运行在异步 I/O 工作线程池上,因此进行中的请求**不会**卡住事件循环:定时器、套接字与其他 `await` 继续运行,promise 在 reactor 派发完成结果时 settle。
8
8
  - 重定向在主线程上处理:每一跳是一次 offload 交换,最多 20 次;301/302/303 会把 POST 改成 GET。
9
9
  - 只接受 `http://` URL —— 由于未链接 TLS 后端,`https://` 会以 `TypeError` 拒绝。
10
+ - **没有协议的引用**(相对路径)与 `file://` URL 会读取本地文件,因此编译后的程序
11
+ 无需 HTTP 服务即可加载磁盘资源。相对路径相对工作目录解析;支持 `GET` / `HEAD`。
12
+ 在 Windows 上,标准写法 `file:///C:/dir/file` 与 `file://C:/dir/file` /
13
+ `file://C:\dir\file` 都指向同一个文件。
10
14
  - 选项:`method`、`headers`(普通对象或 `Headers`)、`body` 与 `redirect`(`follow` / `manual` / `error`)。
11
15
  - 解析出的 `Response` 形状对象暴露 `ok` / `status` / `statusText` / `url` / `headers` 以及 `text()` / `json()` / `arrayBuffer()` / `bytes()` / `clone()`。响应体在 promise settle 前已完整缓冲;不支持流式响应体。
12
16
  - `Headers` 大小写不敏感,提供 `get` / `has` / `set` / `append` / `delete` / `keys` / `values` / `entries` / `forEach` / `getSetCookie`。
@@ -19,6 +19,16 @@
19
19
  - 导入绑定与命名空间成员是模块求值时的快照(无实时绑定)—— 见[未实现:模块系统](../unimplemented/modules.md)。
20
20
  - 纯类型导入与再导出(`import type { T } from "..."`、`import { type T } from "..."`、`export type { T } from "..."`)会被擦除,**不构成模块依赖边**:目标模块不会被加载或打包,模块之间的纯类型循环也不属于循环依赖。若需要仅求值一个模块而不绑定名字,请使用副作用导入(`import "./mod"`)。
21
21
 
22
+ ## 延迟求值
23
+
24
+ - `gui` 的 `.html` 资产加载器用合成的 `__xt_deferred_module` 绑定标记相对的
25
+ `<script src>` import。bundler 会把该模块及其本地 import 图包装成返回各自
26
+ 导出的函数,生成的脚本在 `loadHTML` 之后调用它们(`__xt_defer` 对每个模块
27
+ 做记忆化)。这与浏览器的模块脚本一致:文档就绪后才求值 import 图。
28
+ - 同时被普通(非标记)import 引用的模块保持立即求值,并获得一个返回已初始化
29
+ 导出的薄包装。
30
+ - 包装体是同步的:延迟脚本模块中的顶层 `await` 不受支持。
31
+
22
32
  ## import.meta
23
33
 
24
34
  - `import.meta.url` / `.dirname` / `.filename`,由正在运行的可执行文件路径解析。
@@ -53,6 +53,21 @@
53
53
  - `Symbol(description)` 原始值(`XT_OBJECT_KIND_SYMBOL`)、13 个著名符号(`Symbol.iterator`、`Symbol.asyncIterator`、`Symbol.match` 等)、`Symbol.for` / `Symbol.keyFor` 全局注册表、`symbol.description` / `toString()` / `valueOf()`。
54
54
  - symbol 可作为属性键(`Object.getOwnPropertySymbols`,symbol 键的 `get`/`set`/`in`/`delete`),会被 `Object.keys` / `values` / `entries` / `for...in` / `JSON.stringify` 跳过,打印为 `Symbol(desc)`。
55
55
 
56
+ ## URL
57
+
58
+ - `URL` 实现在 `runtime/xt_url_object.c`:支持 `new URL(input)` 与
59
+ `new URL(input, base)`(base 也可以是另一个 `URL`),并按层级式
60
+ `scheme://authority/path` base 解析相对引用。
61
+ - **没有协议的引用按文件路径处理**:`new URL("./model.glb")` 相对工作目录解析,
62
+ 得到 `file:` URL(显式 base 仍然生效,因此 `new URL("./x", import.meta.url)`
63
+ 也可用)。
64
+ - 只读属性 `href`、`protocol`、`origin`、`host`、`hostname`、`port`、
65
+ `pathname`、`search`、`hash`、`searchParams` 以及 `toString()` / `toJSON()`;
66
+ 绝对输入按原样保留,但 `file:` URL 的 Windows 写法(`file://C:/dir` 与
67
+ `file://C:\dir`)会规范化为 `file:///C:/dir`,与 Node 一致。
68
+ - 不是完整的 WHATWG 解析器:未实现路径百分号编码、IDNA、setter 与
69
+ `searchParams` 的实时同步 —— 见[运行时偏差](../unimplemented/deviations.md)。
70
+
56
71
  ## URLSearchParams
57
72
 
58
73
  - `URLSearchParams` 实现在 `runtime/xt_url.c`。
@@ -8,6 +8,7 @@
8
8
  | --- | --- |
9
9
  | 正则按字节匹配 | 引擎按 UTF-8 字节匹配:多字节字面量仍可匹配,但 `.` 与字符类每次只消费一个字节(`\d`/`\w` 仍是 ASCII)。`exec` / `search` / 替换回调返回的偏移会转换为 UTF-16 码元 |
10
10
  | 全局正则 `lastIndex` | `/g`、`/y` 正则的 `test` / `exec` 不推进也不读取调用方设置的 `lastIndex` |
11
+ | `URL` 解析器 | 只读 WHATWG 子集:未实现路径百分号编码、IDNA、setter 与 `searchParams` 实时联动;无协议引用按工作目录解析为 `file:` URL |
11
12
  | `String.normalize` | 未实现 |
12
13
  | 内置方法一等公民 | 以未绑定方法值实现(见[函数](./functions.md));`fn.toString()` 返回占位字符串而非源码文本 |
13
14
  | `for...in` | 枚举对象 / 数组 / 字符串的自身键;不含原型链属性 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.5.37",
3
+ "version": "0.5.38",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -72,12 +72,12 @@
72
72
  "vitest": "^2.1.8"
73
73
  },
74
74
  "optionalDependencies": {
75
- "@xbintsc/linux-x64": "0.5.37",
76
- "@xbintsc/linux-arm64": "0.5.37",
77
- "@xbintsc/darwin-x64": "0.5.37",
78
- "@xbintsc/darwin-arm64": "0.5.37",
79
- "@xbintsc/win32-x64": "0.5.37",
80
- "@xbintsc/win32-arm64": "0.5.37"
75
+ "@xbintsc/linux-x64": "0.5.38",
76
+ "@xbintsc/linux-arm64": "0.5.38",
77
+ "@xbintsc/darwin-x64": "0.5.38",
78
+ "@xbintsc/darwin-arm64": "0.5.38",
79
+ "@xbintsc/win32-x64": "0.5.38",
80
+ "@xbintsc/win32-arm64": "0.5.38"
81
81
  },
82
82
  "engines": {
83
83
  "node": ">=22"
@@ -223,6 +223,16 @@ xt_value webgl_proto() {
223
223
  define_getter(proto, "canvas", (void *)webgl_canvas_get);
224
224
  define_getter(proto, "drawingBufferWidth", (void *)webgl_drawing_buffer_width_get);
225
225
  define_getter(proto, "drawingBufferHeight", (void *)webgl_drawing_buffer_height_get);
226
+ /* `gl.constructor.name` is how three.js tells a WebGL 2 context from a
227
+ * WebGL 1 one. A host-type value from `xt_browser_type` later replaces this
228
+ * with the real constructor (and its `prototype`), but the context must
229
+ * answer the name even when the page never read the global. */
230
+ {
231
+ xt_value constructor = xt_object_new();
232
+ xt_object_set(constructor, xt_string_from_cstr("name"),
233
+ xt_string_from_cstr("WebGLRenderingContext"));
234
+ xt_object_set(proto, xt_string_from_cstr("constructor"), constructor);
235
+ }
226
236
  g_webgl_proto = proto;
227
237
  return proto;
228
238
  }
@@ -121,12 +121,44 @@ xt_value uint_array(const unsigned int *values, int count) {
121
121
  xt_value webgl2_proto() {
122
122
  xt_gc_add_root(&g_webgl2_proto);
123
123
  if (XT_IS_OBJECT(g_webgl2_proto)) return g_webgl2_proto;
124
- xt_value proto = xt_object_new_with_proto(webgl_proto());
124
+ /* WebGL 2 shares the WebGL 1 surface but not its prototype: in a browser
125
+ * `gl2 instanceof WebGLRenderingContext` is false, which is how three.js and
126
+ * other libraries detect WebGL 2. Copy the WebGL 1 members instead of
127
+ * inheriting the WebGL 1 prototype. */
128
+ xt_value webgl1 = webgl_proto();
129
+ xt_value proto = xt_object_new();
130
+ xt_value keys = xt_object_keys(webgl1);
131
+ const double count = xt_to_number(xt_array_length(keys));
132
+ for (int32_t i = 0; i < (int32_t)count; i++) {
133
+ xt_value key = xt_array_get(keys, xt_number((double)i));
134
+ const char *name = xt_string_data(key);
135
+ if (name == nullptr) continue;
136
+ /* `constructor` gets the WebGL 2 name below. The three host getters are
137
+ * accessors, not values: reading them here would run them against the
138
+ * WebGL 1 prototype and copy `undefined`. Reinstall them as accessors. */
139
+ if (strcmp(name, "constructor") == 0 || strcmp(name, "canvas") == 0 ||
140
+ strcmp(name, "drawingBufferWidth") == 0 || strcmp(name, "drawingBufferHeight") == 0) {
141
+ continue;
142
+ }
143
+ xt_object_set(proto, key, xt_object_get(webgl1, key));
144
+ }
145
+ xt_object_define_getter(proto, xt_string_from_cstr("canvas"),
146
+ xt_closure_new((void *)webgl_canvas_get, 0, nullptr));
147
+ xt_object_define_getter(proto, xt_string_from_cstr("drawingBufferWidth"),
148
+ xt_closure_new((void *)webgl_drawing_buffer_width_get, 0, nullptr));
149
+ xt_object_define_getter(proto, xt_string_from_cstr("drawingBufferHeight"),
150
+ xt_closure_new((void *)webgl_drawing_buffer_height_get, 0, nullptr));
125
151
  #define XT_WEBGL2_CONSTANT(name, value) \
126
152
  xt_object_set(proto, xt_string_from_cstr(#name), xt_number((double)(value)));
127
153
  XT_WEBGL2_CONSTANTS(XT_WEBGL2_CONSTANT)
128
154
  #undef XT_WEBGL2_CONSTANT
129
155
  for (const MethodEntry &method : kMethods) define_method(proto, method.name, method.fn);
156
+ {
157
+ xt_value constructor = xt_object_new();
158
+ xt_object_set(constructor, xt_string_from_cstr("name"),
159
+ xt_string_from_cstr("WebGL2RenderingContext"));
160
+ xt_object_set(proto, xt_string_from_cstr("constructor"), constructor);
161
+ }
130
162
  g_webgl2_proto = proto;
131
163
  return proto;
132
164
  }
@@ -24,6 +24,8 @@ xt_value xt_gui_run(int32_t argc, xt_value *argv);
24
24
  xt_value xt_gui_quit(int32_t argc, xt_value *argv);
25
25
  /** `__registerScript(id, fn)`: record an AOT-compiled `<script>` body. */
26
26
  xt_value xt_register_script(int32_t argc, xt_value *argv);
27
+ /** `__xt_defer(id, init)`: evaluate a deferred module once and cache its exports. */
28
+ xt_value xt_gui_defer_module(int32_t argc, xt_value *argv);
27
29
  /** Which SDL_GPU backend the device is using (`"vulkan"`, `"direct3d12"`,
28
30
  * `"metal"`, …), or `""` before the device exists. */
29
31
  const char *xt_gui_driver(void);
@@ -25,6 +25,14 @@ static std::unordered_map<std::string, xt_value> &script_registry() {
25
25
  return registry;
26
26
  }
27
27
 
28
+ /** Deferred module id -> its exports object. A `<script src>`'s imports are
29
+ * evaluated on first use (after `loadHTML`) and then shared by every script
30
+ * that imports the same module. */
31
+ static std::unordered_map<std::string, xt_value> &deferred_registry() {
32
+ static std::unordered_map<std::string, xt_value> registry;
33
+ return registry;
34
+ }
35
+
28
36
  extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
29
37
  if (argc >= 2) {
30
38
  xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
@@ -42,6 +50,29 @@ extern "C" xt_value xt_register_script(int32_t argc, xt_value *argv) {
42
50
 
43
51
  void xt_gui_script_gc_scan(void) {
44
52
  for (const auto &entry : script_registry()) xt_gc_mark_value(entry.second);
53
+ for (const auto &entry : deferred_registry()) xt_gc_mark_value(entry.second);
54
+ }
55
+
56
+ /** `__xt_defer(id, init)`: evaluate `init` once and return its exports. The
57
+ * placeholder is published before the call so a cyclic import sees the same
58
+ * object instead of recursing. */
59
+ extern "C" xt_value xt_gui_defer_module(int32_t argc, xt_value *argv) {
60
+ if (argc < 2) return XT_UNDEFINED;
61
+ xt_value id_value = xt_to_string(xt_arg(argc, argv, 0));
62
+ const char *id = xt_string_data(id_value);
63
+ if (id == nullptr) return XT_UNDEFINED;
64
+ std::unordered_map<std::string, xt_value> &registry = deferred_registry();
65
+ auto found = registry.find(std::string(id));
66
+ if (found != registry.end()) return found->second;
67
+ xt_gc_register_root_provider(xt_gui_script_gc_scan);
68
+ xt_value exports = xt_object_new();
69
+ registry[std::string(id)] = exports;
70
+ xt_value init = xt_arg(argc, argv, 1);
71
+ if (XT_IS_FUNCTION(init)) {
72
+ exports = xt_call_with_this(init, XT_UNDEFINED, 0, nullptr);
73
+ registry[std::string(id)] = exports;
74
+ }
75
+ return exports;
45
76
  }
46
77
 
47
78
  /** Collect `data-xt-id` markers from `<script>` elements, in document order. */
@@ -126,6 +126,9 @@ struct GlslLinkLayout {
126
126
  std::vector<GlslUniformDecl> block_members;
127
127
  };
128
128
 
129
+ /** Expand three.js's `#pragma unroll_loop_start` loops into constant indices. */
130
+ std::string unroll_glsl_pragmas(const std::string &source);
131
+
129
132
  /** Scan `source` for the declarations WebGL needs to know about. */
130
133
  void scan_glsl(const std::string &source, bool fragment, GlslShaderInfo *info);
131
134