xbintsc 0.5.20 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/dist/src/driver/compiler.js +1 -1
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/node/fs/index.d.ts +4 -3
- package/dist/src/extensions/node/fs/index.js +12 -3
- package/dist/src/extensions/node/fs/index.js.map +1 -1
- package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
- package/dist/src/extensions/node/fs-promises/index.js +15 -6
- package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +1 -1
- package/dist/tests/e2e/async-fs.test.d.ts +9 -0
- package/dist/tests/e2e/async-fs.test.js +119 -0
- package/dist/tests/e2e/async-fs.test.js.map +1 -0
- package/dist/tests/e2e/fetch.test.js +24 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
- package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
- package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
- package/doc/ai/contributing.md +2 -1
- package/doc/ai/extensions.md +4 -3
- package/doc/ai/language-support.md +2 -1
- package/doc/ai/zh-CN/contributing.md +1 -1
- package/doc/ai/zh-CN/extensions.md +4 -3
- package/doc/ai/zh-CN/language-support.md +2 -1
- package/doc/gui/api.md +8 -0
- package/doc/gui/known-issues.md +10 -9
- package/doc/gui/media.md +32 -3
- package/doc/gui.md +24 -2
- package/doc/implemented/async.md +16 -3
- package/doc/implemented/fetch.md +38 -6
- package/doc/implemented/node/fs-promises.md +19 -8
- package/doc/implemented/node.md +1 -1
- package/doc/implemented.md +1 -1
- package/doc/unimplemented/deviations.md +1 -1
- package/doc/unimplemented/node/async.md +6 -3
- package/doc/unimplemented/node/fs-promises.md +10 -6
- package/doc/unimplemented/node/fs.md +7 -8
- package/doc/unimplemented/node.md +2 -2
- package/doc/unimplemented/toolchain.md +1 -1
- package/doc/zh-CN/gui/api.md +7 -0
- package/doc/zh-CN/gui/known-issues.md +8 -7
- package/doc/zh-CN/gui/media.md +26 -2
- package/doc/zh-CN/gui.md +18 -1
- package/doc/zh-CN/implemented/async.md +13 -2
- package/doc/zh-CN/implemented/fetch.md +23 -4
- package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
- package/doc/zh-CN/implemented/node.md +1 -1
- package/doc/zh-CN/implemented.md +1 -1
- package/doc/zh-CN/unimplemented/deviations.md +1 -1
- package/doc/zh-CN/unimplemented/node/async.md +6 -3
- package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
- package/doc/zh-CN/unimplemented/node/fs.md +2 -2
- package/doc/zh-CN/unimplemented/node.md +2 -2
- package/doc/zh-CN/unimplemented/toolchain.md +1 -1
- package/package.json +1 -1
- package/runtime/ext_gui/inline_svg.cpp +219 -0
- package/runtime/ext_gui/inline_svg.h +41 -0
- package/runtime/ext_gui/layout.cpp +18 -0
- package/runtime/ext_gui/layout.h +7 -0
- package/runtime/ext_gui/layout_flow.cpp +4 -3
- package/runtime/ext_gui/paint.cpp +14 -0
- package/runtime/ext_gui/paint.h +5 -1
- package/runtime/ext_gui/renderer_image.cpp +53 -25
- package/runtime/ext_gui/style_ua.cpp +3 -0
- package/runtime/ext_gui/svg.cpp +122 -39
- package/runtime/ext_gui/svg.h +14 -3
- package/runtime/ext_gui/window.cpp +28 -0
- package/runtime/ext_node/fs/async_api.c +314 -0
- package/runtime/ext_node/fs/async_complete.c +99 -0
- package/runtime/ext_node/fs/async_fh.c +206 -0
- package/runtime/ext_node/fs/async_job.c +81 -0
- package/runtime/ext_node/fs/async_job.h +132 -0
- package/runtime/ext_node/fs/async_worker.c +534 -0
- package/runtime/ext_node/fs/async_worker_dir.c +439 -0
- package/runtime/ext_node/fs/async_worker_dir.h +33 -0
- package/runtime/ext_node/fs/async_worker_fd.c +74 -0
- package/runtime/ext_node/fs/dir.c +60 -15
- package/runtime/ext_node/fs/fs_common.h +9 -0
- package/runtime/ext_node/fs/fs_ops.c +18 -29
- package/runtime/ext_node/fs/parts/builtins.inc +34 -0
- package/runtime/ext_node/fs/parts/platform.inc +66 -0
- package/runtime/ext_node/fs/promises.c +44 -234
- package/runtime/rt_internal.h +2 -0
- package/runtime/xt_async_io.c +333 -0
- package/runtime/xt_async_io.h +47 -0
- package/runtime/xt_fetch/async.inc +295 -0
- package/runtime/xt_fetch/request.inc +6 -183
- package/runtime/xt_fetch/socket.inc +5 -4
- package/runtime/xt_fetch.c +6 -4
- package/runtime/xt_loop.c +44 -3
- package/src/driver/compiler.ts +1 -1
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/node/fs/index.ts +12 -3
- package/src/extensions/node/fs-promises/index.ts +15 -6
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end tests for the `gui` extension — inline `<svg>` elements.
|
|
3
|
+
*
|
|
4
|
+
* An inline `<svg>` tree is not an SVG *file*: the document already holds the
|
|
5
|
+
* nodes, so the engine serializes the subtree back into an SVG document and
|
|
6
|
+
* rasterises it exactly like `<img src="logo.svg">`. These tests pin the
|
|
7
|
+
* replaced-element sizing, the display-list entry, the serializer's canonical
|
|
8
|
+
* SVG names (the HTML parser lowercases `viewBox`/`linearGradient`) and the
|
|
9
|
+
* fact that a DOM mutation re-serializes the artwork.
|
|
10
|
+
*/
|
|
11
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
12
|
+
import { createGuiHarness, guiAvailable } from "./gui-helpers.js";
|
|
13
|
+
const harness = createGuiHarness();
|
|
14
|
+
describe.skipIf(!guiAvailable)("gui extension — inline svg", () => {
|
|
15
|
+
beforeAll(() => harness.setup());
|
|
16
|
+
afterAll(() => harness.cleanup());
|
|
17
|
+
it("sizes and paints an inline svg as a replaced element", () => {
|
|
18
|
+
const { stdout, stderr } = harness.compileAndRun("inline-svg", `
|
|
19
|
+
import { createWindow, run } from "gui";
|
|
20
|
+
|
|
21
|
+
const HTML = \`
|
|
22
|
+
<html><head><style>
|
|
23
|
+
body { margin: 0; }
|
|
24
|
+
#scaled { width: 64px; height: 48px; }
|
|
25
|
+
</style></head><body>
|
|
26
|
+
<svg id="natural" width="8" height="6" viewBox="0 0 8 6">
|
|
27
|
+
<rect width="8" height="6" fill="#ff0000"/>
|
|
28
|
+
<circle cx="4" cy="3" r="2" fill="#0000ff"/>
|
|
29
|
+
</svg>
|
|
30
|
+
<svg id="viewbox" viewBox="0 0 20 10">
|
|
31
|
+
<rect width="20" height="10" fill="#00ff00"/>
|
|
32
|
+
</svg>
|
|
33
|
+
<svg id="scaled" width="8" height="6" viewBox="0 0 8 6">
|
|
34
|
+
<rect width="8" height="6" fill="#ff0000"/>
|
|
35
|
+
</svg>
|
|
36
|
+
</body></html>
|
|
37
|
+
\`;
|
|
38
|
+
|
|
39
|
+
const win = createWindow({ title: "inline-svg", width: 400, height: 300 });
|
|
40
|
+
win.on("ready", () => {
|
|
41
|
+
const natural = win.getBoundingClientRect("#natural");
|
|
42
|
+
console.log("natural=" + natural.width + "x" + natural.height);
|
|
43
|
+
const viewbox = win.getBoundingClientRect("#viewbox");
|
|
44
|
+
console.log("viewbox=" + viewbox.width + "x" + viewbox.height);
|
|
45
|
+
const scaled = win.getBoundingClientRect("#scaled");
|
|
46
|
+
console.log("scaled=" + scaled.width + "x" + scaled.height);
|
|
47
|
+
console.log("display=" + win.computedStyle("#natural", "display"));
|
|
48
|
+
console.log("hit=" + win.hitTest(4, 3));
|
|
49
|
+
console.log("no-rects=" + (win.layoutTree().indexOf("<rect>") < 0));
|
|
50
|
+
console.log("paint-list-start");
|
|
51
|
+
console.log(win.paintList());
|
|
52
|
+
console.log("paint-list-end");
|
|
53
|
+
const info = win.inlineSvg("#natural");
|
|
54
|
+
console.log("markup=" + info.markup);
|
|
55
|
+
console.log("parsed=" + info.width + "x" + info.height + " shapes=" + info.shapes);
|
|
56
|
+
});
|
|
57
|
+
win.loadHTML(HTML);
|
|
58
|
+
run();
|
|
59
|
+
`);
|
|
60
|
+
// width/height attributes give the intrinsic size; a bare viewBox is the
|
|
61
|
+
// fallback; an explicit CSS size wins over both.
|
|
62
|
+
expect(stdout).toContain("natural=8x6");
|
|
63
|
+
expect(stdout).toContain("viewbox=20x10");
|
|
64
|
+
expect(stdout).toContain("scaled=64x48");
|
|
65
|
+
// The `<svg>` box itself is the hit-test target; its children are vector
|
|
66
|
+
// art, not HTML boxes, so the walk stays at the svg element.
|
|
67
|
+
expect(stdout).toContain("hit=svg#natural");
|
|
68
|
+
expect(stdout).toContain("display=inline-block");
|
|
69
|
+
// The svg subtree produces no HTML boxes: only the replaced box is laid out.
|
|
70
|
+
expect(stdout).toContain("no-rects=true");
|
|
71
|
+
// A source newline between two inline elements is a hard line break in this
|
|
72
|
+
// engine (like `<br>`), so the three boxes stack in document order.
|
|
73
|
+
const list = stdout.slice(stdout.indexOf("paint-list-start"), stdout.indexOf("paint-list-end"));
|
|
74
|
+
expect(list).toContain("image x=0.0 y=0.0 w=8.0 h=6.0 inline-svg=");
|
|
75
|
+
expect(list).toContain("image x=0.0 y=6.0 w=20.0 h=10.0 inline-svg=");
|
|
76
|
+
expect(list).toContain("image x=0.0 y=16.0 w=64.0 h=48.0 inline-svg=");
|
|
77
|
+
// The serialized document restores the case-sensitive SVG names the HTML
|
|
78
|
+
// parser lowercased, and only the svg subtree is included.
|
|
79
|
+
const markup = stdout
|
|
80
|
+
.split("\n")
|
|
81
|
+
.find((line) => line.startsWith("markup="))
|
|
82
|
+
.slice("markup=".length);
|
|
83
|
+
expect(markup.startsWith('<svg xmlns="http://www.w3.org/2000/svg" id="natural" width="8" height="6" viewBox="0 0 8 6">')).toBe(true);
|
|
84
|
+
expect(markup).toContain('<rect width="8" height="6" fill="#ff0000"></rect>');
|
|
85
|
+
expect(markup).toContain('<circle cx="4" cy="3" r="2" fill="#0000ff"></circle>');
|
|
86
|
+
expect(markup).not.toContain("viewbox=");
|
|
87
|
+
expect(stdout).toContain("parsed=8x6 shapes=2");
|
|
88
|
+
// Rasterising a parsed document must actually succeed.
|
|
89
|
+
expect(stderr).not.toContain("could not rasterise inline SVG");
|
|
90
|
+
});
|
|
91
|
+
it("parses gradients, re-serializes on mutation and restyles the box", () => {
|
|
92
|
+
const { stdout, stderr } = harness.compileAndRun("inline-svg-mutate", `
|
|
93
|
+
import { createWindow, run } from "gui";
|
|
94
|
+
|
|
95
|
+
const HTML = \`
|
|
96
|
+
<html><head><style>
|
|
97
|
+
body { margin: 0; }
|
|
98
|
+
#icon { background: #123456; border: 2px solid #00ff00; }
|
|
99
|
+
</style></head><body>
|
|
100
|
+
<svg id="icon" width="24" height="24" viewBox="0 0 24 24">
|
|
101
|
+
<defs>
|
|
102
|
+
<linearGradient id="grad" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="24" y2="24">
|
|
103
|
+
<stop offset="0%" stop-color="#ff0000"/>
|
|
104
|
+
<stop offset="100%" stop-color="#0000ff"/>
|
|
105
|
+
</linearGradient>
|
|
106
|
+
</defs>
|
|
107
|
+
<rect id="box" width="24" height="24" fill="url(#grad)"/>
|
|
108
|
+
</svg>
|
|
109
|
+
</body></html>
|
|
110
|
+
\`;
|
|
111
|
+
|
|
112
|
+
const win = createWindow({ title: "inline-svg-mutate", width: 400, height: 300 });
|
|
113
|
+
win.on("ready", () => {
|
|
114
|
+
const first = win.inlineSvg("#icon");
|
|
115
|
+
console.log("shapes=" + first.shapes);
|
|
116
|
+
console.log("gradient=" + (first.markup.indexOf("linearGradient") >= 0));
|
|
117
|
+
console.log("units=" + (first.markup.indexOf('gradientUnits="userSpaceOnUse"') >= 0));
|
|
118
|
+
console.log("stops=" + first.markup.split("<stop").length);
|
|
119
|
+
|
|
120
|
+
const icon = win.document.getElementById("icon");
|
|
121
|
+
icon.setAttribute("width", "48");
|
|
122
|
+
const rect = win.document.querySelector("rect");
|
|
123
|
+
rect.setAttribute("fill", "#00ff00");
|
|
124
|
+
const grown = win.getBoundingClientRect("#icon");
|
|
125
|
+
const second = win.inlineSvg("#icon");
|
|
126
|
+
console.log("grown=" + grown.width + "x" + grown.height);
|
|
127
|
+
console.log("moved=" + (second.markup.indexOf('fill="#00ff00"') >= 0));
|
|
128
|
+
|
|
129
|
+
icon.innerHTML = '<circle cx="12" cy="12" r="10" fill="#ffff00"/>';
|
|
130
|
+
const third = win.inlineSvg("#icon");
|
|
131
|
+
console.log("replaced=" + (third.markup.indexOf("<circle") >= 0) + " shapes=" + third.shapes);
|
|
132
|
+
console.log("paint-list-start");
|
|
133
|
+
console.log(win.paintList());
|
|
134
|
+
console.log("paint-list-end");
|
|
135
|
+
});
|
|
136
|
+
win.loadHTML(HTML);
|
|
137
|
+
run();
|
|
138
|
+
`);
|
|
139
|
+
expect(stdout).toContain("shapes=1");
|
|
140
|
+
// The HTML parser lowercases `linearGradient`; the serializer restores it,
|
|
141
|
+
// or NanoSVG would silently drop the gradient definition.
|
|
142
|
+
expect(stdout).toContain("gradient=true");
|
|
143
|
+
expect(stdout).toContain("units=true");
|
|
144
|
+
// start + two stops = three occurrences of "<stop".
|
|
145
|
+
expect(stdout).toContain("stops=3");
|
|
146
|
+
// A presentation-attribute mutation changes the intrinsic size, and a paint
|
|
147
|
+
// mutation changes the serialized document the renderer rasterises.
|
|
148
|
+
expect(stdout).toContain("grown=52x28");
|
|
149
|
+
expect(stdout).toContain("moved=true");
|
|
150
|
+
// `innerHTML` swaps the artwork for a new subtree.
|
|
151
|
+
expect(stdout).toContain("replaced=true shapes=1");
|
|
152
|
+
const list = stdout.slice(stdout.indexOf("paint-list-start"), stdout.indexOf("paint-list-end"));
|
|
153
|
+
// The svg element is still an ordinary styled box: background + 4 borders.
|
|
154
|
+
expect(list).toContain("color=#123456ff");
|
|
155
|
+
expect((list.match(/#00ff00ff/g) ?? []).length).toBe(4);
|
|
156
|
+
expect(list).toContain("w=48.0 h=24.0 inline-svg=");
|
|
157
|
+
expect(stderr).not.toContain("could not rasterise inline SVG");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=gui-inline-svg.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gui-inline-svg.test.js","sourceRoot":"","sources":["../../../tests/e2e/gui-inline-svg.test.ts"],"names":[],"mappings":"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"}
|
package/doc/ai/contributing.md
CHANGED
|
@@ -110,7 +110,8 @@ diff scratch/ref.ll scratch/gen2.ll
|
|
|
110
110
|
therefore never allocate per access.
|
|
111
111
|
- The GC is a non-moving mark-sweep collector: new heap objects go through
|
|
112
112
|
`xt_alloc`, and a value must be reachable from an explicit root slot, a
|
|
113
|
-
registered root provider (the event loop, the microtask queue
|
|
113
|
+
registered root provider (the event loop, the microtask queue, the async I/O
|
|
114
|
+
job roots) or the
|
|
114
115
|
conservative C-stack scan while it is live. A value held only in a C local that
|
|
115
116
|
is not on the scanned stack is a bug.
|
|
116
117
|
|
package/doc/ai/extensions.md
CHANGED
|
@@ -36,7 +36,7 @@ resolve to the same implementation.
|
|
|
36
36
|
| Module | Notes |
|
|
37
37
|
| --- | --- |
|
|
38
38
|
| `fs` | **synchronous API only** (`readFileSync`, `writeFileSync`, …); `fs/promises` is separate |
|
|
39
|
-
| `fs/promises` | promise-based file APIs |
|
|
39
|
+
| `fs/promises` | promise-based file APIs; the blocking I/O runs on the async I/O worker pool and settles on the reactor |
|
|
40
40
|
| `path` | also hooks namespace dispatch, so `path.join(...)` works |
|
|
41
41
|
| `os` | |
|
|
42
42
|
| `process` | `process.cwd()`, `argv`, `env`, …; namespace dispatch too |
|
|
@@ -54,8 +54,9 @@ Coverage per module (exact functions and options) is in
|
|
|
54
54
|
[../unimplemented/node.md](../unimplemented/node.md).
|
|
55
55
|
|
|
56
56
|
Remember the runtime model before building on `net`/`http`: the event loop runs
|
|
57
|
-
**after** the program body, but `async`/`await`
|
|
58
|
-
|
|
57
|
+
**after** the program body, but `async`/`await` suspends on pending promises, so a
|
|
58
|
+
promise settled from a socket callback, a timer or an offloaded file/HTTP job can
|
|
59
|
+
be awaited. `net.connect` itself is still blocking. See
|
|
59
60
|
[language-support.md](./language-support.md).
|
|
60
61
|
|
|
61
62
|
### `gui` — HTML/CSS window
|
|
@@ -75,7 +75,8 @@ Canonical detail (always current, much longer):
|
|
|
75
75
|
| `setTimeout` and callbacks | ✅ | timers run on the core event loop; `await new Promise(r => setTimeout(r, ms))` works |
|
|
76
76
|
| `queueMicrotask` / `setImmediate` / `performance.now` | ✅ | microtasks share the promise FIFO queue; `setImmediate` is a zero-delay timer with numeric ids; `performance.now` is monotonic fractional milliseconds |
|
|
77
77
|
| Sockets / servers (`http`, `net`, `dgram`) | ✅ | callbacks run on the event loop and can settle a promise you await (`net` / `http` examples included in the tests) |
|
|
78
|
-
| `fs/promises` |
|
|
78
|
+
| `fs/promises` | ✅ | file, directory and `FileHandle` operations run on the async I/O worker pool and settle on the reactor, so timers and sockets keep running while they wait |
|
|
79
|
+
| Global `fetch` | ✅ | HTTP/1.1 client whose connect/send/receive runs on the same worker pool; `await fetch(...)` yields and redirects are followed on the reactor. `http://` only (no TLS) and the body is buffered before resolving |
|
|
79
80
|
| Worker threads | 🚫 | workers are child processes |
|
|
80
81
|
| `async` generators | ✅ | suspend at `await` and `yield`; every `.next()` returns a promise; `for await...of` supported |
|
|
81
82
|
|
|
@@ -93,7 +93,7 @@ diff scratch/ref.ll scratch/gen2.ll
|
|
|
93
93
|
- 字符串字面量会被驻留:每个不同字面量只在 `main` 中创建一次,存入 GC 根全局,
|
|
94
94
|
每次使用只做一次 load;属性名因此不再逐次分配。
|
|
95
95
|
- GC 是非移动标记清扫:新堆对象走 `xt_alloc`;值存活期间必须能从显式根槽、已注册的
|
|
96
|
-
|
|
96
|
+
根提供者(事件循环、微任务队列、异步 I/O job 根)或 C 栈保守扫描到达。只存在于未被扫描的 C 局部
|
|
97
97
|
变量里的值就是 bug。
|
|
98
98
|
|
|
99
99
|
## 修改运行时
|
|
@@ -35,7 +35,7 @@ xbintsc run app.ts --ext node
|
|
|
35
35
|
| 模块 | 说明 |
|
|
36
36
|
| --- | --- |
|
|
37
37
|
| `fs` | **仅同步 API**(`readFileSync`、`writeFileSync` 等);`fs/promises` 是独立模块 |
|
|
38
|
-
| `fs/promises` | 基于 Promise 的文件 API |
|
|
38
|
+
| `fs/promises` | 基于 Promise 的文件 API;阻塞 I/O 跑在异步 I/O 线程池上并在 reactor 上 settle |
|
|
39
39
|
| `path` | 同时接入命名空间分发,`path.join(...)` 可用 |
|
|
40
40
|
| `os` | |
|
|
41
41
|
| `process` | `process.cwd()`、`argv`、`env` 等;也接入命名空间分发 |
|
|
@@ -53,8 +53,9 @@ xbintsc run app.ts --ext node
|
|
|
53
53
|
[../../zh-CN/unimplemented/node.md](../../zh-CN/unimplemented/node.md)。
|
|
54
54
|
|
|
55
55
|
在 `net`/`http` 上搭东西之前,先记住运行时模型:事件循环在**程序主体之后**才运行,
|
|
56
|
-
但 `async`/`await`
|
|
57
|
-
|
|
56
|
+
但 `async`/`await` 会在 pending Promise 上挂起,因此由套接字回调、定时器或 offload 的
|
|
57
|
+
文件/HTTP job settle 的 Promise 都可以 await。`net.connect` 本身仍是阻塞的。见
|
|
58
|
+
[language-support.md](./language-support.md)。
|
|
58
59
|
|
|
59
60
|
### `gui` —— HTML/CSS 窗口
|
|
60
61
|
|
|
@@ -72,7 +72,8 @@ xbintsc 编译 TypeScript 的一个**实用子集**。本页用来判断你能
|
|
|
72
72
|
| `setTimeout` 与回调 | ✅ | 定时器运行在核心事件循环上;`await new Promise(r => setTimeout(r, ms))` 可用 |
|
|
73
73
|
| `queueMicrotask` / `setImmediate` / `performance.now` | ✅ | 微任务与 Promise 共用 FIFO 队列;`setImmediate` 是零延迟定时器,返回数字 id;`performance.now` 返回单调递增的毫秒小数 |
|
|
74
74
|
| 套接字/服务端(`http`、`net`、`dgram`) | ✅ | 回调在事件循环上执行,可以 settle 你正在 await 的 Promise(测试含 `net`/`http` 示例) |
|
|
75
|
-
| `fs/promises` |
|
|
75
|
+
| `fs/promises` | ✅ | 文件、目录与 `FileHandle` 操作跑在异步 I/O 线程池上,并在 reactor 上 settle,等待期间定时器与套接字继续运行 |
|
|
76
|
+
| 全局 `fetch` | ✅ | HTTP/1.1 客户端,connect/send/receive 跑在同一线程池;`await fetch(...)` 会让出,重定向在 reactor 上处理。仅 `http://`(无 TLS),resolve 前缓冲整个响应体 |
|
|
76
77
|
| worker 线程 | 🚫 | worker 是子进程 |
|
|
77
78
|
| `async` 生成器 | ✅ | 可在 `await` 与 `yield` 处挂起;每次 `.next()` 返回 Promise;支持 `for await...of` |
|
|
78
79
|
|
package/doc/gui/api.md
CHANGED
|
@@ -65,6 +65,7 @@ win.documentTree() // serialized DOM (debugging)
|
|
|
65
65
|
win.layoutTree() // serialized layout boxes (debugging)
|
|
66
66
|
win.paintCount() // number of shapes in the display list
|
|
67
67
|
win.paintList() // serialized display list (debugging)
|
|
68
|
+
win.inlineSvg(selector) // serialized inline <svg>: { markup, width, height, shapes }
|
|
68
69
|
win.measureText(text, fontSize?, family?) // shaped advance width in pixels
|
|
69
70
|
win.fontMetrics(fontSize?, family?) // { ascent, descent, lineHeight, ready }
|
|
70
71
|
win.hitTest(x, y) // deepest element descriptor, or ""
|
|
@@ -73,6 +74,13 @@ win.advance(ms) // step the CSS transition clock (testing
|
|
|
73
74
|
win.stats() // engine/runtime counters (memory debugging)
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
`win.inlineSvg(selector)` returns the standalone SVG document the engine builds
|
|
78
|
+
from the first `<svg>` element matching `selector` (`markup`), together with the
|
|
79
|
+
intrinsic size and shape count NanoSVG parses out of it; it is `undefined` when
|
|
80
|
+
nothing matches or the element is not an `<svg>`. It makes the serializer's
|
|
81
|
+
canonical casing (`viewBox`, `linearGradient`, `gradientUnits`, …) testable
|
|
82
|
+
without a GPU — the HTML parser delivers those names lowercased.
|
|
83
|
+
|
|
76
84
|
`win.stats()` returns `{ heapBytes, heapAllocations, treeNodes, detachedRoots,`
|
|
77
85
|
`detachedNodes, handles, handleSlots, listeners }`: the live runtime heap, the
|
|
78
86
|
size of the DOM tree, the detached-node pool, and the element-handle tables. In a
|
package/doc/gui/known-issues.md
CHANGED
|
@@ -34,20 +34,21 @@
|
|
|
34
34
|
*class change* on a flex container has also been observed to crash the same
|
|
35
35
|
way; `examples/gui/pelican-bike` reveals its game-over overlay with `opacity`
|
|
36
36
|
instead.
|
|
37
|
+
- **SVG is NanoSVG's subset.** SVG *files* (`<img src="logo.svg">`) and inline
|
|
38
|
+
`<svg>` trees share the vendored NanoSVG parser/rasteriser
|
|
39
|
+
(`runtime/ext_gui/svg.cpp`, `inline_svg.cpp`), which renders
|
|
40
|
+
`path`/`rect`/`circle`/`ellipse`/`line`/`polyline`/`polygon`, groups, `defs`
|
|
41
|
+
gradients and class-based `<style>` rules. `<text>`, `<use>`, filters, masks
|
|
42
|
+
and clip paths are not rendered, and CSS applied by the document cascade does
|
|
43
|
+
not reach inside the artwork (fill/stroke are SVG presentation attributes,
|
|
44
|
+
not CSS properties here). Vector art that needs those has to be rasterised
|
|
45
|
+
beforehand, drawn from boxes, or restricted to the supported syntax. See
|
|
46
|
+
[Media](./media.md#images-m6a).
|
|
37
47
|
|
|
38
48
|
## Open questions
|
|
39
49
|
|
|
40
50
|
- Whether Linux ships X11, Wayland, or both in the first cut. (Decision:
|
|
41
51
|
X11 first, Wayland later.)
|
|
42
|
-
- **SVG:** SVG *files* are supported through the image loader
|
|
43
|
-
(`<img src="logo.svg">`): `runtime/ext_gui/svg.cpp` wraps the vendored
|
|
44
|
-
**NanoSVG** parser/rasteriser and `image.{h,cpp}` treats an SVG as a vector
|
|
45
|
-
image, rasterised at the drawn pixel size. Inline `<svg>` **trees** in a
|
|
46
|
-
document are still not supported: the HTML parser knows elements and
|
|
47
|
-
attributes, the layout engine knows boxes, and paint knows
|
|
48
|
-
rectangles/text/images, so `<svg>` subtrees would need a whole second
|
|
49
|
-
geometry+paint path. Vector art therefore has to arrive as an SVG/PNG file or
|
|
50
|
-
be drawn from boxes, as `examples/gui/pelican-bike` does.
|
|
51
52
|
- **Windows: the C++ runtime must not be named on the link line.** `gui.lib` is
|
|
52
53
|
built with the *static* CRT (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`) and
|
|
53
54
|
every engine object records `libcpmt` as a dependent library, which is also
|
package/doc/gui/media.md
CHANGED
|
@@ -12,7 +12,9 @@ size cache (`stbi_info`). Paths accept a `file://` prefix and percent-encoding.
|
|
|
12
12
|
library (parse + anti-aliased scanline rasteriser), detects an SVG by extension
|
|
13
13
|
or by its contents, and produces the same RGBA8 `Image` the pipeline already
|
|
14
14
|
consumes. `stb_image` cannot decode SVG, so this is what makes
|
|
15
|
-
`<img src="logo.svg">` work.
|
|
15
|
+
`<img src="logo.svg">` work. The same parser is used for an inline `<svg>`
|
|
16
|
+
subtree, whose DOM nodes are serialized into a document first
|
|
17
|
+
(`inline_svg.{h,cpp}`).
|
|
16
18
|
|
|
17
19
|
- `<img>` is a replaced element (`display: inline-block`): layout gives it the
|
|
18
20
|
CSS size when set, otherwise the intrinsic pixel size, and preserves the
|
|
@@ -27,12 +29,39 @@ consumes. `stb_image` cannot decode SVG, so this is what makes
|
|
|
27
29
|
pixels (logical size × the window's pixel density), preserving the viewBox
|
|
28
30
|
aspect ratio, and cached by that size, so scaling or a HiDPI display produces
|
|
29
31
|
a crisp render instead of a magnified bitmap.
|
|
32
|
+
- An **inline** `<svg>` element is a replaced element like `<img>`, but its
|
|
33
|
+
source is the DOM subtree itself. `runtime/ext_gui/inline_svg.{h,cpp}`
|
|
34
|
+
serializes that subtree back into a standalone SVG document, restoring the
|
|
35
|
+
case-sensitive spellings NanoSVG matches (`viewBox`, `preserveAspectRatio`,
|
|
36
|
+
`linearGradient`, `gradientUnits`, …) that the HTML parser lowercased. Layout
|
|
37
|
+
sizes the box from the `width`/`height` attributes, else the `viewBox`, else
|
|
38
|
+
300x150; paint carries the markup on its image op and the renderer parses and
|
|
39
|
+
rasterises it at the drawn device pixel size through the same NanoSVG cache as
|
|
40
|
+
an SVG file. A DOM mutation re-serializes the subtree on the next layout (so
|
|
41
|
+
`setAttribute` and `innerHTML` both repaint the artwork), and the markup cache
|
|
42
|
+
is bounded (least-recently-used) so an animated document cannot grow it
|
|
43
|
+
without limit.
|
|
30
44
|
- The parse is cached per path; the parse and rasteriser state are released in
|
|
31
45
|
`xt_svg_shutdown` (`xt_image_shutdown`).
|
|
32
46
|
|
|
47
|
+
Document CSS styles the `<svg>` **box** — size, margins, background, borders,
|
|
48
|
+
`transform`, opacity, `display` — exactly as it styles any other element. The
|
|
49
|
+
artwork inside it keeps SVG semantics: presentation attributes and the
|
|
50
|
+
`class`-based rules of an SVG `<style>` block are read by NanoSVG, not by the
|
|
51
|
+
HTML cascade, so `fill`/`stroke` are not CSS properties here. For layout, paint,
|
|
52
|
+
hit testing and events the whole `<svg>` is one replaced box: its children get no
|
|
53
|
+
DOM boxes, so a click's `e.target` is the `<svg>` element, not a `<rect>`. The
|
|
54
|
+
renderer caches one texture per markup-and-size pair, so a program that rewrites
|
|
55
|
+
its artwork every frame keeps a texture per variant; prefer animating the box
|
|
56
|
+
(`transform`, `opacity`) or swapping between a small set of documents.
|
|
57
|
+
|
|
33
58
|
Still to do for images: CSS `background-image: url(...)`, `data:` URIs,
|
|
34
|
-
`object-fit
|
|
35
|
-
|
|
59
|
+
`object-fit` and 9-slice borders.
|
|
60
|
+
|
|
61
|
+
Both kinds of SVG share NanoSVG's subset: `path`, `rect`, `circle`, `ellipse`,
|
|
62
|
+
`line`, `polyline`, `polygon`, groups (`g`), `defs` with linear/radial gradients
|
|
63
|
+
and class-based `<style>` rules rasterise; `<text>`, `<use>`, filters, masks and
|
|
64
|
+
clip paths are not rendered (see [Known issues](./known-issues.md)).
|
|
36
65
|
|
|
37
66
|
## Video & audio (M13)
|
|
38
67
|
|
package/doc/gui.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# xbintsc GUI extension (self-hosted HTML/CSS renderer)
|
|
2
2
|
|
|
3
|
-
Status: **
|
|
3
|
+
Status: **M16** — features (M1–M15) are complete: HTML parsing, CSS selector
|
|
4
4
|
matching, the cascade, computed styles and layout (block, inline, Flexbox and
|
|
5
5
|
CSS positioning) are
|
|
6
6
|
in place, and the engine *paints*: it builds a display list of rectangles, images
|
|
@@ -9,7 +9,9 @@ tested and delivered to native TS handlers, `:hover`/`:focus` are matched
|
|
|
9
9
|
dynamically, `<img>` is sized from its intrinsic dimensions and drawn from a
|
|
10
10
|
texture — PNG/JPEG/… bitmaps and SVG files alike, the latter rasterised at the
|
|
11
11
|
drawn size so vector art stays crisp — and CSS transitions animate paint
|
|
12
|
-
properties. **
|
|
12
|
+
properties. **M16** gives inline `<svg>` trees the same treatment: the subtree is
|
|
13
|
+
serialized back into an SVG document and rasterised at the drawn device size, so
|
|
14
|
+
an icon needs no separate file. **M8** adds an interactive
|
|
13
15
|
DOM: element handles with stable identity, mutation (`appendChild`, `textContent`,
|
|
14
16
|
`classList`, `style`, …) and element-level events with capture/bubbling.
|
|
15
17
|
**M9** compiles `<script>` bodies ahead of time (inline and `<script src>`) — see
|
|
@@ -257,6 +259,18 @@ engine were Rust.
|
|
|
257
259
|
[Layout](./gui/layout.md#transforms-m15) and
|
|
258
260
|
[Animation](./gui/animation.md#css-transforms-m15); e2e in
|
|
259
261
|
`tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
|
|
262
|
+
16. **M16 — inline `<svg>`** ✅
|
|
263
|
+
- `<svg>` is a replaced element (`layout.*`): its intrinsic size comes from
|
|
264
|
+
the `width`/`height` attributes, else the `viewBox`, else 300x150, and the
|
|
265
|
+
normal box walk does not descend into its children.
|
|
266
|
+
- `inline_svg.{h,cpp}` serializes the subtree into a standalone SVG document,
|
|
267
|
+
restoring the case-sensitive names the HTML parser lowercased (`viewBox`,
|
|
268
|
+
`preserveAspectRatio`, `linearGradient`, `gradientUnits`, …).
|
|
269
|
+
- Paint carries the markup on its image op (`PaintImage::svg`) and the
|
|
270
|
+
renderer parses/rasterises it at the drawn device size through the same
|
|
271
|
+
NanoSVG cache as an SVG file (`svg.{h,cpp}`); the markup cache is bounded
|
|
272
|
+
and a DOM mutation re-serializes on the next layout. e2e in
|
|
273
|
+
`tests/e2e/gui-inline-svg.test.ts`.
|
|
260
274
|
|
|
261
275
|
## Progress log
|
|
262
276
|
|
|
@@ -355,4 +369,12 @@ engine were Rust.
|
|
|
355
369
|
through the inverse, and `getBoundingClientRect` returns the transformed AABB.
|
|
356
370
|
`examples/gui/pelican-svg` animates its artwork with it; e2e in
|
|
357
371
|
`tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
|
|
372
|
+
- **M16** ✅ inline `<svg>`. `buildBoxTree` treats an `<svg>` element as a
|
|
373
|
+
replaced box (intrinsic size from `width`/`height`, then `viewBox`, then
|
|
374
|
+
300x150) and stops descending, `inline_svg.{h,cpp}` serializes the subtree with
|
|
375
|
+
canonical SVG casing, paint carries the markup on `PaintImage::svg`, and
|
|
376
|
+
`renderer_image.cpp` rasterises it at the drawn device pixel size through the
|
|
377
|
+
NanoSVG cache (`svg.{h,cpp}`, bounded LRU for markup). The `win.inlineSvg`
|
|
378
|
+
diagnostic exposes the serialized document, and `tests/e2e/gui-inline-svg.test.ts`
|
|
379
|
+
pins sizing, hit testing, gradients, CSS box styling and mutation.
|
|
358
380
|
|
package/doc/implemented/async.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
> Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/async.md)
|
|
4
4
|
|
|
5
5
|
Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`,
|
|
6
|
-
`runtime/xt_generator.c`, `runtime/xt_loop.c`, `runtime/
|
|
6
|
+
`runtime/xt_generator.c`, `runtime/xt_loop.c`, `runtime/xt_async_io.c`,
|
|
7
|
+
`runtime/xt_performance.c`.
|
|
7
8
|
|
|
8
9
|
## async / await
|
|
9
10
|
|
|
@@ -11,7 +12,7 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
|
|
|
11
12
|
- The body runs synchronously until the first `await` of a pending promise, then suspends; settlement queues a microtask that resumes the coroutine.
|
|
12
13
|
- An exception that escapes the body rejects that promise instead of throwing synchronously.
|
|
13
14
|
- `await` on a non-promise or settled promise keeps the synchronous fast path and raises on rejection.
|
|
14
|
-
- Each `async` call owns a private stack and swaps its `try` frame stack and GC stack region, so `await` survives `try`/`finally`, loops and closures. A promise settled from a timer, a socket callback or another coroutine resumes the suspended body on the microtask queue.
|
|
15
|
+
- Each `async` call owns a private stack and swaps its `try` frame stack and GC stack region, so `await` survives `try`/`finally`, loops and closures. A promise settled from a timer, a socket callback, an offloaded file operation or another coroutine resumes the suspended body on the microtask queue.
|
|
15
16
|
|
|
16
17
|
## async generators
|
|
17
18
|
|
|
@@ -35,4 +36,16 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
|
|
|
35
36
|
- `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` / `setImmediate` / `clearImmediate` / `queueMicrotask` and `performance.now` (monotonic fractional milliseconds).
|
|
36
37
|
- Timers use numeric ids; `Timeout` / `Immediate` objects are not implemented.
|
|
37
38
|
- A promise settled from a timer can be awaited: the async body suspends on the pending promise and resumes when the timer fires. Sockets and servers (`net`, `http`, `dgram`) deliver their callbacks on the same loop.
|
|
38
|
-
- `fetch`
|
|
39
|
+
- `fetch` and `fs/promises` are reactor-driven rather than blocking — see the next section.
|
|
40
|
+
|
|
41
|
+
## Asynchronous I/O offload
|
|
42
|
+
|
|
43
|
+
- `runtime/xt_async_io.c` owns a small pool of worker threads (four by default, created lazily). Jobs are split in two halves: a `run` function on a worker that may only touch plain C memory, and a `complete` function on the main thread that builds values and settles the promise. The split is what keeps the collector and the value model single-threaded.
|
|
44
|
+
- A completed job wakes `select(2)` through a self-pipe (POSIX) or a loopback UDP socket (Windows); the reactor also polls the pool, so a failed wakeup degrades to a short wait instead of a hang.
|
|
45
|
+
- While a job is queued or running, up to four values per job (its promise, and for `fetch` the headers object) are kept in the job's root slots and scanned by the collector.
|
|
46
|
+
- `fetch` runs each request/redirect hop as a job; `fs/promises` runs every file and descriptor operation as a job. Neither blocks timers, sockets or other `await`s.
|
|
47
|
+
- The event loop stays alive while jobs are outstanding (`xt_loop_has_work`), and exits once the pool is idle again.
|
|
48
|
+
|
|
49
|
+
Known limits of the asynchronous surface (no TLS/streaming `fetch`, inert
|
|
50
|
+
`fs.watch`, four-worker pool) are listed in
|
|
51
|
+
[Runtime deviations](../unimplemented/deviations.md) and the Node pages.
|
package/doc/implemented/fetch.md
CHANGED
|
@@ -2,11 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
> Part of [Implemented syntax and features](../implemented.md) · [简体中文](../zh-CN/implemented/fetch.md)
|
|
4
4
|
|
|
5
|
-
Location: `runtime/xt_fetch.c`.
|
|
5
|
+
Location: `runtime/xt_fetch.c`, `runtime/xt_fetch/async.inc`, `runtime/xt_async_io.c`.
|
|
6
6
|
|
|
7
|
-
- Global `fetch(input, init)` is
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- Global `fetch(input, init)` is an HTTP/1.1 client. The blocking socket exchange
|
|
8
|
+
(`getaddrinfo`, connect, send, receive) runs on the asynchronous I/O worker
|
|
9
|
+
pool, so a request in flight does **not** stall the event loop: timers, sockets
|
|
10
|
+
and other `await`s keep running and the promise settles when the reactor
|
|
11
|
+
dispatches the completed exchange.
|
|
12
|
+
- Redirects are followed on the main thread: each hop is one offloaded
|
|
13
|
+
exchange, up to 20 redirects, and a 301/302/303 switches POST to GET.
|
|
14
|
+
- It accepts `http://` URLs only — `https://` rejects with a `TypeError` because
|
|
15
|
+
no TLS backend is linked.
|
|
16
|
+
- Options: `method`, `headers` (plain object or `Headers`), `body` and `redirect`
|
|
17
|
+
(`follow` / `manual` / `error`).
|
|
18
|
+
- The resolved `Response`-shaped object exposes `ok` / `status` / `statusText` /
|
|
19
|
+
`url` / `headers` plus `text()` / `json()` / `arrayBuffer()` / `bytes()` /
|
|
20
|
+
`clone()`. The body is buffered before the promise settles; streaming bodies
|
|
21
|
+
are not implemented.
|
|
22
|
+
- `Headers` is case-insensitive with `get` / `has` / `set` / `append` /
|
|
23
|
+
`delete` / `keys` / `values` / `entries` / `forEach` / `getSetCookie`.
|
|
12
24
|
- Invalid URLs and network failures reject with a `TypeError`.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
async function main(): Promise<void> {
|
|
28
|
+
const response = await fetch("http://127.0.0.1:8080/status");
|
|
29
|
+
console.log(response.status, await response.text());
|
|
30
|
+
}
|
|
31
|
+
main();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A timer scheduled before the request completes fires while the exchange is still
|
|
35
|
+
in flight:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const pending = fetch("http://127.0.0.1:8080/slow");
|
|
39
|
+
setTimeout(() => console.log("timer"), 10); // runs before the 150ms response
|
|
40
|
+
await pending;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Known limits (only `http://`, no TLS, buffered body) are listed in
|
|
44
|
+
[Runtime deviations](../unimplemented/deviations.md).
|
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
> Part of [Node extension: implemented](../node.md) · [简体中文](../../zh-CN/implemented/node/fs-promises.md)
|
|
4
4
|
|
|
5
|
-
Location: `src/extensions/node/fs-promises/index.ts`, `runtime/ext_node/fs/
|
|
5
|
+
Location: `src/extensions/node/fs-promises/index.ts`, `runtime/ext_node/fs/async_*.c`,
|
|
6
|
+
`runtime/ext_node/fs/promises.c`.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
Every function submits a job to the runtime's asynchronous I/O pool
|
|
9
|
+
(`runtime/xt_async_io.c`): the blocking syscall runs on a worker thread and the
|
|
10
|
+
promise settles on the event loop. `await` therefore yields while a large read,
|
|
11
|
+
a slow network filesystem or a descriptor operation is in progress, and `then` /
|
|
12
|
+
`catch` keep the usual Promise semantics. Failures **reject** with the same
|
|
13
|
+
Node-shaped `Error` (with `code` / `errno` / `syscall` / `path`) that the
|
|
14
|
+
synchronous form throws.
|
|
15
|
+
|
|
16
|
+
`fs.promises` is also reachable from the `fs` module
|
|
17
|
+
(`import { promises as fsp } from "fs"`), and both namespaces expose a
|
|
18
|
+
`constants` object.
|
|
13
19
|
|
|
14
20
|
Promise exports: `readFile`, `writeFile`, `appendFile`, `mkdir`, `readdir`,
|
|
15
21
|
`rm`, `unlink`, `rmdir`, `rename`, `copyFile`, `cp`, `realpath`, `stat`,
|
|
@@ -19,7 +25,9 @@ Promise exports: `readFile`, `writeFile`, `appendFile`, `mkdir`, `readdir`,
|
|
|
19
25
|
|
|
20
26
|
`open(...)` resolves to a **`FileHandle`** with:
|
|
21
27
|
`read`, `write`, `readFile`, `writeFile`, `appendFile`, `close`, `stat`,
|
|
22
|
-
`truncate`, `chmod`, `chown`, `utimes`, `sync`, `datasync`.
|
|
28
|
+
`truncate`, `chmod`, `chown`, `utimes`, `sync`, `datasync`. Those methods are
|
|
29
|
+
offloaded too; `read(buffer, …)` fills the caller's `Buffer` when the worker
|
|
30
|
+
completes.
|
|
23
31
|
|
|
24
32
|
```ts
|
|
25
33
|
import { readFile, writeFile, open } from "fs/promises";
|
|
@@ -34,5 +42,8 @@ async function main(): Promise<void> {
|
|
|
34
42
|
main();
|
|
35
43
|
```
|
|
36
44
|
|
|
45
|
+
Work runs on a small fixed pool (four workers) and completions are dispatched in
|
|
46
|
+
submission order per worker; independent operations can overlap. A job never
|
|
47
|
+
blocks another `await`, a timer or a socket callback.
|
|
37
48
|
|
|
38
49
|
Known deviations are listed in [Unimplemented: `fs/promises`](../../unimplemented/node/fs-promises.md).
|
package/doc/implemented/node.md
CHANGED
|
@@ -19,7 +19,7 @@ work on another. Missing or different behaviour is recorded under
|
|
|
19
19
|
| Module | Page | Notes |
|
|
20
20
|
| --- | --- | --- |
|
|
21
21
|
| `fs` | [fs](./node/fs.md) | synchronous API only |
|
|
22
|
-
| `fs/promises` | [fs/promises](./node/fs-promises.md) |
|
|
22
|
+
| `fs/promises` | [fs/promises](./node/fs-promises.md) | reactor-driven file and `FileHandle` operations on the async I/O pool |
|
|
23
23
|
| `path` | [path](./node/path.md) | `path.<name>(...)` namespace calls |
|
|
24
24
|
| `os` | [os](./node/os.md) | `os.<name>(...)` namespace calls |
|
|
25
25
|
| `process` | [process](./node/process.md) | object: methods and properties |
|
package/doc/implemented.md
CHANGED
|
@@ -37,7 +37,7 @@ small file instead of a single monolithic document.
|
|
|
37
37
|
- [Collections](./implemented/collections.md) — Map, Set, WeakMap, WeakSet, WeakRef, iteration
|
|
38
38
|
- [Typed arrays and binary data](./implemented/typed-arrays.md) — typed arrays, ArrayBuffer, DataView
|
|
39
39
|
- [Async, promises and generators](./implemented/async.md) — async/await, async generators, promises, timers
|
|
40
|
-
- [fetch](./implemented/fetch.md) —
|
|
40
|
+
- [fetch](./implemented/fetch.md) — reactor-driven HTTP/1.1 client and Headers
|
|
41
41
|
|
|
42
42
|
### Modules and tooling
|
|
43
43
|
|
|
@@ -14,7 +14,7 @@ These features **compile and run**, but the result does not fully match ECMAScri
|
|
|
14
14
|
| Array out-of-bounds / sparse | Out-of-bounds access returns `undefined`; assigning `arr.length` truncates / extends, but sparse holes are not tracked distinctly |
|
|
15
15
|
| Memory management | Non-moving mark-sweep collector behind `xt_alloc`: explicit roots, subsystem root providers and a conservative C-stack scan. Single-threaded and stop-the-world; no moving/compaction or generational mode |
|
|
16
16
|
| Function `arity` / argument count | No argument count validation; `fn.length` reports the declared arity but calls are never checked against it |
|
|
17
|
-
| `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises`
|
|
17
|
+
| `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises` and `fetch` offload their blocking work to a four-worker pool and settle on the reactor. Async generators suspend at both `await` and `yield`; `for await...of` awaits one step per iteration |
|
|
18
18
|
| Async generator cleanup | `for await...of` does not call the iterator's `return()` when the loop exits early. `yield*` inside an async generator delegates to synchronous iterables only. `.return()` at a suspended `yield` completes without running `finally` (the same limitation as synchronous generators) |
|
|
19
19
|
| `super` | `super.x` / `super(...)` takes the prototype of `this`'s prototype; single-level inheritance is correct, but depth > 1 may be inaccurate |
|
|
20
20
|
| `Error.stack` | Function-level frames only: each frame is `at name (file:line)` with the function's declaration line (updated to the construction line for the innermost frame); no column numbers and no frames for generator / `async` bodies (they switch stacks) |
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
Implemented:
|
|
6
6
|
|
|
7
7
|
- Core event loop `runtime/xt_loop.c` (a `select(2)` reactor); `net` / `dgram` / `http` are all built on it.
|
|
8
|
-
-
|
|
8
|
+
- Async I/O offload `runtime/xt_async_io.c`: blocking file and HTTP-client work runs on four worker threads and completes on the reactor, so `fs/promises` and `fetch` do not stall timers or sockets.
|
|
9
|
+
- `Promise`, `async` / `await` are available (see the core documents); `fs/promises` returns Promises that settle from the reactor.
|
|
9
10
|
- Timers are available as globals: `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval` run on the core reactor and keep the process alive until no timer remains.
|
|
10
11
|
- A standalone `EventEmitter` is provided by the `events` module (also a global constructor); streams and sockets additionally carry internal emitter methods (`on` / `addListener` / `once` / `off` / `removeListener` / `emit`).
|
|
11
12
|
|
|
12
13
|
Still unimplemented:
|
|
13
14
|
|
|
14
15
|
- Node's `Timeout` object (`setTimeout` returns a numeric id; `setImmediate` and `queueMicrotask` exist as globals).
|
|
15
|
-
-
|
|
16
|
+
- File watching: `fs.watch` / `fs/promises.watch` return inert objects; there is no inotify/kqueue/ReadDirectoryChangesW backend.
|
|
17
|
+
- `Dir.read` / `Dir.close` invoke their callbacks immediately, and callback-style `fs.readFile` / `fs.writeFile` / … are still unsupported (use `fs/promises` or the `*Sync` forms).
|
|
16
18
|
- On streams and sockets the built-in `once` is equivalent to `on` (no "remove after firing once" semantics); the `events` module's `EventEmitter` does implement real `once`.
|
|
17
|
-
- `net`'s `connect` is blocking; HTTP responses require `Connection: close`, with no keep-alive / chunked transfer / pipelining.
|
|
19
|
+
- `net`'s `connect` is still a blocking `connect(2)` performed before the socket joins the reactor; HTTP responses require `Connection: close`, with no keep-alive / chunked transfer / pipelining. `fetch` has no TLS and buffers the whole body before resolving.
|
|
20
|
+
- The offload pool is fixed at four workers; there is no thread-pool sizing option and no `AbortSignal` cancellation for queued jobs.
|