@crowdedkingdoms/crowdyjs 8.21.1 → 9.0.0
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/MIGRATION.md +44 -0
- package/README.md +74 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/live-coding/assets/browser-authoring-index.json +1 -0
- package/dist/live-coding/assets/manifest.json +19 -0
- package/dist/live-coding/assets/tree-sitter-rust.wasm +0 -0
- package/dist/live-coding/assets/web-tree-sitter.wasm +0 -0
- package/dist/live-coding/browser-authoring-index.generated.d.ts +2 -0
- package/dist/live-coding/browser-authoring-index.generated.d.ts.map +1 -0
- package/dist/live-coding/browser-authoring-index.generated.js +5127 -0
- package/dist/live-coding/ide.d.ts +14 -7
- package/dist/live-coding/ide.d.ts.map +1 -1
- package/dist/live-coding/ide.js +296 -181
- package/dist/live-coding/index.d.ts +8 -0
- package/dist/live-coding/index.d.ts.map +1 -0
- package/dist/live-coding/index.js +7 -0
- package/dist/live-coding/lsp-protocol.d.ts +98 -0
- package/dist/live-coding/lsp-protocol.d.ts.map +1 -0
- package/dist/live-coding/lsp-protocol.js +70 -0
- package/dist/live-coding/monaco-services.d.ts +22 -0
- package/dist/live-coding/monaco-services.d.ts.map +1 -0
- package/dist/live-coding/monaco-services.js +69 -0
- package/dist/live-coding/platform-index.d.ts +29 -0
- package/dist/live-coding/platform-index.d.ts.map +1 -0
- package/dist/live-coding/platform-index.js +281 -0
- package/dist/live-coding/rust-analysis.d.ts +24 -0
- package/dist/live-coding/rust-analysis.d.ts.map +1 -0
- package/dist/live-coding/rust-analysis.js +312 -0
- package/dist/live-coding/rust-lsp-server.d.ts +43 -0
- package/dist/live-coding/rust-lsp-server.d.ts.map +1 -0
- package/dist/live-coding/rust-lsp-server.js +455 -0
- package/dist/live-coding/rust-lsp.worker.d.ts +2 -0
- package/dist/live-coding/rust-lsp.worker.d.ts.map +1 -0
- package/dist/live-coding/rust-lsp.worker.js +26 -0
- package/dist/live-coding/vfs.d.ts +41 -0
- package/dist/live-coding/vfs.d.ts.map +1 -0
- package/dist/live-coding/vfs.js +174 -0
- package/dist/live-coding/worker-transport.d.ts +60 -0
- package/dist/live-coding/worker-transport.d.ts.map +1 -0
- package/dist/live-coding/worker-transport.js +204 -0
- package/dist/player-runtime/glue-runtime.d.ts +13 -4
- package/dist/player-runtime/glue-runtime.d.ts.map +1 -1
- package/dist/player-runtime/glue-runtime.js +52 -8
- package/dist/player-runtime/glue-sab.d.ts +8 -5
- package/dist/player-runtime/glue-sab.d.ts.map +1 -1
- package/dist/player-runtime/glue-sab.js +36 -10
- package/dist/player-runtime/player-code-broker.d.ts +33 -4
- package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
- package/dist/player-runtime/player-code-broker.js +332 -51
- package/dist/player-runtime/player-glue-worker.d.ts +3 -2
- package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
- package/dist/player-runtime/player-glue-worker.js +68 -32
- package/package.json +18 -6
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
export const DEFAULT_VFS_LIMITS = {
|
|
2
|
+
maxFiles: 32,
|
|
3
|
+
maxFileBytes: 256 * 1024,
|
|
4
|
+
maxWorkspaceBytes: 1024 * 1024,
|
|
5
|
+
};
|
|
6
|
+
export class VfsLimitError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = 'VFS_LIMIT';
|
|
10
|
+
this.name = 'VfsLimitError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class VirtualFileSystem {
|
|
14
|
+
constructor(workspaceUri = 'file:///player-mod', limits = {}) {
|
|
15
|
+
this.files = new Map();
|
|
16
|
+
this.encoder = new TextEncoder();
|
|
17
|
+
this.totalBytes = 0;
|
|
18
|
+
this.workspaceUri = workspaceUri.replace(/\/+$/, '');
|
|
19
|
+
const root = new URL(this.workspaceUri);
|
|
20
|
+
if (root.protocol !== 'file:' ||
|
|
21
|
+
root.search ||
|
|
22
|
+
root.hash ||
|
|
23
|
+
!root.pathname.startsWith('/') ||
|
|
24
|
+
root.pathname === '/') {
|
|
25
|
+
throw new Error('workspaceUri must be a non-root file URI');
|
|
26
|
+
}
|
|
27
|
+
this.limits = { ...DEFAULT_VFS_LIMITS, ...limits };
|
|
28
|
+
this.assertLimits();
|
|
29
|
+
}
|
|
30
|
+
get size() {
|
|
31
|
+
return this.files.size;
|
|
32
|
+
}
|
|
33
|
+
get bytes() {
|
|
34
|
+
return this.totalBytes;
|
|
35
|
+
}
|
|
36
|
+
open(item) {
|
|
37
|
+
const path = this.pathForUri(item.uri);
|
|
38
|
+
const previous = this.files.get(item.uri);
|
|
39
|
+
if (!previous && this.files.size >= this.limits.maxFiles) {
|
|
40
|
+
throw new VfsLimitError(`Workspace file limit is ${this.limits.maxFiles}`);
|
|
41
|
+
}
|
|
42
|
+
if (previous && item.version <= previous.version) {
|
|
43
|
+
return previous;
|
|
44
|
+
}
|
|
45
|
+
const next = this.makeDocument(item, path);
|
|
46
|
+
this.ensureFits(next.bytes, previous?.bytes ?? 0);
|
|
47
|
+
if (previous)
|
|
48
|
+
this.totalBytes -= previous.bytes;
|
|
49
|
+
this.files.set(item.uri, next);
|
|
50
|
+
this.totalBytes += next.bytes;
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
change(uri, version, changes) {
|
|
54
|
+
const previous = this.require(uri);
|
|
55
|
+
if (version <= previous.version) {
|
|
56
|
+
return { applied: false, document: previous };
|
|
57
|
+
}
|
|
58
|
+
let text = previous.text;
|
|
59
|
+
for (const change of changes) {
|
|
60
|
+
if (!change.range) {
|
|
61
|
+
text = change.text;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const start = offsetAt(text, change.range.start);
|
|
65
|
+
const end = offsetAt(text, change.range.end);
|
|
66
|
+
if (end < start)
|
|
67
|
+
throw new Error('Invalid content change range');
|
|
68
|
+
text = text.slice(0, start) + change.text + text.slice(end);
|
|
69
|
+
}
|
|
70
|
+
const next = this.makeDocument({
|
|
71
|
+
uri,
|
|
72
|
+
version,
|
|
73
|
+
languageId: previous.languageId,
|
|
74
|
+
text,
|
|
75
|
+
}, previous.path);
|
|
76
|
+
this.ensureFits(next.bytes, previous.bytes);
|
|
77
|
+
this.files.set(uri, next);
|
|
78
|
+
this.totalBytes += next.bytes - previous.bytes;
|
|
79
|
+
return { applied: true, document: next };
|
|
80
|
+
}
|
|
81
|
+
close(uri) {
|
|
82
|
+
const previous = this.files.get(uri);
|
|
83
|
+
if (!previous)
|
|
84
|
+
return false;
|
|
85
|
+
this.files.delete(uri);
|
|
86
|
+
this.totalBytes -= previous.bytes;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
get(uri) {
|
|
90
|
+
return this.files.get(uri);
|
|
91
|
+
}
|
|
92
|
+
require(uri) {
|
|
93
|
+
const document = this.files.get(uri);
|
|
94
|
+
if (!document)
|
|
95
|
+
throw new Error(`Document is not open: ${uri}`);
|
|
96
|
+
return document;
|
|
97
|
+
}
|
|
98
|
+
documents() {
|
|
99
|
+
return [...this.files.values()];
|
|
100
|
+
}
|
|
101
|
+
clear() {
|
|
102
|
+
this.files.clear();
|
|
103
|
+
this.totalBytes = 0;
|
|
104
|
+
}
|
|
105
|
+
pathForUri(uri) {
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = new URL(uri);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
throw new Error(`Invalid document URI: ${uri}`);
|
|
112
|
+
}
|
|
113
|
+
if (parsed.protocol !== 'file:' ||
|
|
114
|
+
parsed.search ||
|
|
115
|
+
parsed.hash ||
|
|
116
|
+
!uri.startsWith(`${this.workspaceUri}/`)) {
|
|
117
|
+
throw new Error(`Document is outside ${this.workspaceUri}`);
|
|
118
|
+
}
|
|
119
|
+
const relative = decodeURIComponent(parsed.pathname.slice(new URL(this.workspaceUri).pathname.length + 1));
|
|
120
|
+
if (relative.length === 0 ||
|
|
121
|
+
relative.startsWith('/') ||
|
|
122
|
+
relative.split('/').some((part) => part === '' || part === '..' || part === '.')) {
|
|
123
|
+
throw new Error(`Unsafe document path: ${relative}`);
|
|
124
|
+
}
|
|
125
|
+
return relative;
|
|
126
|
+
}
|
|
127
|
+
makeDocument(item, path) {
|
|
128
|
+
if (!Number.isSafeInteger(item.version) || item.version < 0) {
|
|
129
|
+
throw new Error('Document version must be a non-negative safe integer');
|
|
130
|
+
}
|
|
131
|
+
const bytes = this.encoder.encode(item.text).byteLength;
|
|
132
|
+
if (bytes > this.limits.maxFileBytes) {
|
|
133
|
+
throw new VfsLimitError(`${path} is ${bytes} bytes; file limit is ${this.limits.maxFileBytes}`);
|
|
134
|
+
}
|
|
135
|
+
return Object.freeze({ ...item, path, bytes });
|
|
136
|
+
}
|
|
137
|
+
ensureFits(nextBytes, replacedBytes) {
|
|
138
|
+
const total = this.totalBytes - replacedBytes + nextBytes;
|
|
139
|
+
if (total > this.limits.maxWorkspaceBytes) {
|
|
140
|
+
throw new VfsLimitError(`Workspace is ${total} bytes; limit is ${this.limits.maxWorkspaceBytes}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
assertLimits() {
|
|
144
|
+
for (const [name, value] of Object.entries(this.limits)) {
|
|
145
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
146
|
+
throw new Error(`${name} must be a positive safe integer`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (this.limits.maxFileBytes > this.limits.maxWorkspaceBytes) {
|
|
150
|
+
throw new Error('maxFileBytes cannot exceed maxWorkspaceBytes');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function offsetAt(text, position) {
|
|
155
|
+
if (!Number.isSafeInteger(position.line) ||
|
|
156
|
+
!Number.isSafeInteger(position.character) ||
|
|
157
|
+
position.line < 0 ||
|
|
158
|
+
position.character < 0) {
|
|
159
|
+
throw new Error('Invalid document position');
|
|
160
|
+
}
|
|
161
|
+
let offset = 0;
|
|
162
|
+
for (let line = 0; line < position.line; line++) {
|
|
163
|
+
const newline = text.indexOf('\n', offset);
|
|
164
|
+
if (newline < 0)
|
|
165
|
+
throw new Error('Document position is outside the text');
|
|
166
|
+
offset = newline + 1;
|
|
167
|
+
}
|
|
168
|
+
const newline = text.indexOf('\n', offset);
|
|
169
|
+
const lineEnd = newline < 0 ? text.length : newline;
|
|
170
|
+
const target = offset + position.character;
|
|
171
|
+
if (target > lineEnd)
|
|
172
|
+
throw new Error('Document position is outside the text');
|
|
173
|
+
return target;
|
|
174
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AbstractMessageReader, AbstractMessageWriter, type DataCallback, type Disposable, type Message } from 'vscode-jsonrpc';
|
|
2
|
+
import { type JsonRpcMessage } from './lsp-protocol.js';
|
|
3
|
+
export type { Disposable };
|
|
4
|
+
export interface LanguageWorkerLike {
|
|
5
|
+
postMessage(message: unknown): void;
|
|
6
|
+
addEventListener(type: 'message' | 'error' | 'messageerror', listener: EventListener): void;
|
|
7
|
+
removeEventListener(type: 'message' | 'error' | 'messageerror', listener: EventListener): void;
|
|
8
|
+
terminate(): void;
|
|
9
|
+
}
|
|
10
|
+
/** Standard vscode-jsonrpc MessageReader over structured-clone worker messages. */
|
|
11
|
+
export declare class WorkerMessageReader extends AbstractMessageReader {
|
|
12
|
+
private readonly worker;
|
|
13
|
+
private callback;
|
|
14
|
+
private listening;
|
|
15
|
+
constructor(worker: LanguageWorkerLike);
|
|
16
|
+
listen(callback: DataCallback): Disposable;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
private readonly onMessage;
|
|
19
|
+
private readonly onMessageError;
|
|
20
|
+
private readonly onWorkerError;
|
|
21
|
+
}
|
|
22
|
+
/** Standard vscode-jsonrpc MessageWriter over structured-clone worker messages. */
|
|
23
|
+
export declare class WorkerMessageWriter extends AbstractMessageWriter {
|
|
24
|
+
private readonly worker;
|
|
25
|
+
private disposed;
|
|
26
|
+
private errorCount;
|
|
27
|
+
constructor(worker: LanguageWorkerLike);
|
|
28
|
+
write(message: Message): Promise<void>;
|
|
29
|
+
end(): void;
|
|
30
|
+
dispose(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface WorkerLanguageClientOptions {
|
|
33
|
+
requestTimeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* LSP 3.17 JSON-RPC client over a real vscode-jsonrpc MessageConnection.
|
|
37
|
+
* Monaco providers adapt editor calls to this connection; transport framing,
|
|
38
|
+
* request ids, cancellation, errors, and notifications are standard JSON-RPC.
|
|
39
|
+
*/
|
|
40
|
+
export declare class WorkerLanguageClient {
|
|
41
|
+
private readonly worker;
|
|
42
|
+
private readonly options;
|
|
43
|
+
private readonly reader;
|
|
44
|
+
private readonly writer;
|
|
45
|
+
private readonly connection;
|
|
46
|
+
private readonly transportErrorHandlers;
|
|
47
|
+
private readonly readerErrorDisposable;
|
|
48
|
+
private disposed;
|
|
49
|
+
private terminateTimer;
|
|
50
|
+
constructor(worker: LanguageWorkerLike, options?: WorkerLanguageClientOptions);
|
|
51
|
+
initialize(params: unknown): Promise<unknown>;
|
|
52
|
+
request(method: string, params?: unknown): Promise<unknown>;
|
|
53
|
+
notify(method: string, params?: unknown): Promise<void>;
|
|
54
|
+
onNotification(method: string, callback: (params: unknown) => void): Disposable;
|
|
55
|
+
shutdown(): void;
|
|
56
|
+
dispose(): void;
|
|
57
|
+
}
|
|
58
|
+
export declare function createDefaultRustLanguageWorker(): Worker;
|
|
59
|
+
export declare function isWorkerLspMessage(value: unknown): value is JsonRpcMessage;
|
|
60
|
+
//# sourceMappingURL=worker-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-transport.d.ts","sourceRoot":"","sources":["../../src/live-coding/worker-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EAErB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,OAAO,EACb,MAAM,gBAAgB,CAAC;AASxB,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,gBAAgB,CACd,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,cAAc,EAC1C,QAAQ,EAAE,aAAa,GACtB,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,cAAc,EAC1C,QAAQ,EAAE,aAAa,GACtB,IAAI,CAAC;IACR,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,mFAAmF;AACnF,qBAAa,mBAAoB,SAAQ,qBAAqB;IAIhD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,SAAS,CAAS;gBAEG,MAAM,EAAE,kBAAkB;IAIvD,MAAM,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU;IAUjC,OAAO,IAAI,IAAI;IAWxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAOxB;IAEF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE7B;IAEF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAM5B;CACH;AAED,mFAAmF;AACnF,qBAAa,mBAAoB,SAAQ,qBAAqB;IAIhD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAK;gBAEM,MAAM,EAAE,kBAAkB;IAIvD,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtC,GAAG,IAAI,IAAI;IAIF,OAAO,IAAI,IAAI;CAIzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,qBAAa,oBAAoB;IAU7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqC;IAC5E,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAa;IACnD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAA8C;gBAGjD,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,2BAAgC;IAWtD,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAMnD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAsD3D,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAClC,UAAU;IAIb,QAAQ,IAAI,IAAI;IAehB,OAAO,IAAI,IAAI;CAYhB;AAED,wBAAgB,+BAA+B,IAAI,MAAM,CAKxD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAE1E"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { AbstractMessageReader, AbstractMessageWriter, CancellationTokenSource, } from 'vscode-jsonrpc';
|
|
2
|
+
import { ExitNotification, InitializeRequest, InitializedNotification, ShutdownRequest, createProtocolConnection, } from 'vscode-languageserver-protocol';
|
|
3
|
+
import { decodeJsonRpcMessage, } from './lsp-protocol.js';
|
|
4
|
+
/** Standard vscode-jsonrpc MessageReader over structured-clone worker messages. */
|
|
5
|
+
export class WorkerMessageReader extends AbstractMessageReader {
|
|
6
|
+
constructor(worker) {
|
|
7
|
+
super();
|
|
8
|
+
this.worker = worker;
|
|
9
|
+
this.callback = null;
|
|
10
|
+
this.listening = false;
|
|
11
|
+
this.onMessage = (event) => {
|
|
12
|
+
const decoded = decodeJsonRpcMessage(event.data);
|
|
13
|
+
if (!decoded.ok) {
|
|
14
|
+
this.fireError(new Error(decoded.message));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
this.callback?.(decoded.message);
|
|
18
|
+
};
|
|
19
|
+
this.onMessageError = () => {
|
|
20
|
+
this.fireError(new Error('Language worker sent an unreadable message'));
|
|
21
|
+
};
|
|
22
|
+
this.onWorkerError = (event) => {
|
|
23
|
+
const message = 'message' in event && typeof event.message === 'string'
|
|
24
|
+
? event.message
|
|
25
|
+
: 'Language worker failed';
|
|
26
|
+
this.fireError(new Error(message));
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
listen(callback) {
|
|
30
|
+
if (this.listening)
|
|
31
|
+
throw new Error('WorkerMessageReader is already listening');
|
|
32
|
+
this.listening = true;
|
|
33
|
+
this.callback = callback;
|
|
34
|
+
this.worker.addEventListener('message', this.onMessage);
|
|
35
|
+
this.worker.addEventListener('messageerror', this.onMessageError);
|
|
36
|
+
this.worker.addEventListener('error', this.onWorkerError);
|
|
37
|
+
return { dispose: () => this.dispose() };
|
|
38
|
+
}
|
|
39
|
+
dispose() {
|
|
40
|
+
if (this.listening) {
|
|
41
|
+
this.worker.removeEventListener('message', this.onMessage);
|
|
42
|
+
this.worker.removeEventListener('messageerror', this.onMessageError);
|
|
43
|
+
this.worker.removeEventListener('error', this.onWorkerError);
|
|
44
|
+
}
|
|
45
|
+
this.listening = false;
|
|
46
|
+
this.callback = null;
|
|
47
|
+
super.dispose();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Standard vscode-jsonrpc MessageWriter over structured-clone worker messages. */
|
|
51
|
+
export class WorkerMessageWriter extends AbstractMessageWriter {
|
|
52
|
+
constructor(worker) {
|
|
53
|
+
super();
|
|
54
|
+
this.worker = worker;
|
|
55
|
+
this.disposed = false;
|
|
56
|
+
this.errorCount = 0;
|
|
57
|
+
}
|
|
58
|
+
write(message) {
|
|
59
|
+
if (this.disposed)
|
|
60
|
+
return Promise.reject(new Error('Worker writer is disposed'));
|
|
61
|
+
try {
|
|
62
|
+
this.worker.postMessage(message);
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.errorCount++;
|
|
67
|
+
this.fireError(error, message, this.errorCount);
|
|
68
|
+
return Promise.reject(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
end() {
|
|
72
|
+
this.dispose();
|
|
73
|
+
}
|
|
74
|
+
dispose() {
|
|
75
|
+
this.disposed = true;
|
|
76
|
+
super.dispose();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* LSP 3.17 JSON-RPC client over a real vscode-jsonrpc MessageConnection.
|
|
81
|
+
* Monaco providers adapt editor calls to this connection; transport framing,
|
|
82
|
+
* request ids, cancellation, errors, and notifications are standard JSON-RPC.
|
|
83
|
+
*/
|
|
84
|
+
export class WorkerLanguageClient {
|
|
85
|
+
constructor(worker, options = {}) {
|
|
86
|
+
this.worker = worker;
|
|
87
|
+
this.options = options;
|
|
88
|
+
this.transportErrorHandlers = new Set();
|
|
89
|
+
this.disposed = false;
|
|
90
|
+
this.terminateTimer = null;
|
|
91
|
+
this.reader = new WorkerMessageReader(worker);
|
|
92
|
+
this.writer = new WorkerMessageWriter(worker);
|
|
93
|
+
this.readerErrorDisposable = this.reader.onError((error) => {
|
|
94
|
+
for (const handler of this.transportErrorHandlers)
|
|
95
|
+
handler(error);
|
|
96
|
+
});
|
|
97
|
+
this.connection = createProtocolConnection(this.reader, this.writer);
|
|
98
|
+
this.connection.listen();
|
|
99
|
+
}
|
|
100
|
+
async initialize(params) {
|
|
101
|
+
const result = await this.request(InitializeRequest.type.method, params);
|
|
102
|
+
await this.notify(InitializedNotification.type.method, {});
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
request(method, params) {
|
|
106
|
+
if (this.disposed)
|
|
107
|
+
return Promise.reject(new Error('Language client is disposed'));
|
|
108
|
+
const cancellation = new CancellationTokenSource();
|
|
109
|
+
const timeoutMs = this.options.requestTimeoutMs ?? 3000;
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
let settled = false;
|
|
112
|
+
const onTransportError = (error) => {
|
|
113
|
+
if (settled)
|
|
114
|
+
return;
|
|
115
|
+
settled = true;
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
cancellation.cancel();
|
|
118
|
+
cancellation.dispose();
|
|
119
|
+
this.transportErrorHandlers.delete(onTransportError);
|
|
120
|
+
reject(error);
|
|
121
|
+
};
|
|
122
|
+
const timer = setTimeout(() => {
|
|
123
|
+
if (settled)
|
|
124
|
+
return;
|
|
125
|
+
settled = true;
|
|
126
|
+
cancellation.cancel();
|
|
127
|
+
cancellation.dispose();
|
|
128
|
+
this.transportErrorHandlers.delete(onTransportError);
|
|
129
|
+
reject(new Error(`Language request timed out: ${method}`));
|
|
130
|
+
}, timeoutMs);
|
|
131
|
+
this.transportErrorHandlers.add(onTransportError);
|
|
132
|
+
const request = params === undefined
|
|
133
|
+
? this.connection.sendRequest(method, cancellation.token)
|
|
134
|
+
: this.connection.sendRequest(method, params, cancellation.token);
|
|
135
|
+
void request
|
|
136
|
+
.then((value) => {
|
|
137
|
+
if (settled)
|
|
138
|
+
return;
|
|
139
|
+
settled = true;
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
cancellation.dispose();
|
|
142
|
+
this.transportErrorHandlers.delete(onTransportError);
|
|
143
|
+
resolve(value);
|
|
144
|
+
}, (error) => {
|
|
145
|
+
if (settled)
|
|
146
|
+
return;
|
|
147
|
+
settled = true;
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
cancellation.dispose();
|
|
150
|
+
this.transportErrorHandlers.delete(onTransportError);
|
|
151
|
+
reject(error instanceof Error
|
|
152
|
+
? error
|
|
153
|
+
: new Error('Language request failed'));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
notify(method, params) {
|
|
158
|
+
if (this.disposed)
|
|
159
|
+
return Promise.reject(new Error('Language client is disposed'));
|
|
160
|
+
return this.connection.sendNotification(method, params);
|
|
161
|
+
}
|
|
162
|
+
onNotification(method, callback) {
|
|
163
|
+
return this.connection.onNotification(method, callback);
|
|
164
|
+
}
|
|
165
|
+
shutdown() {
|
|
166
|
+
if (this.disposed || this.terminateTimer)
|
|
167
|
+
return;
|
|
168
|
+
const finish = () => {
|
|
169
|
+
if (this.disposed)
|
|
170
|
+
return;
|
|
171
|
+
void this.connection
|
|
172
|
+
.sendNotification(ExitNotification.type.method)
|
|
173
|
+
.catch(() => { })
|
|
174
|
+
.finally(() => this.dispose());
|
|
175
|
+
};
|
|
176
|
+
void this.request(ShutdownRequest.type.method)
|
|
177
|
+
.catch(() => { })
|
|
178
|
+
.finally(finish);
|
|
179
|
+
this.terminateTimer = setTimeout(finish, this.options.requestTimeoutMs ?? 3000);
|
|
180
|
+
}
|
|
181
|
+
dispose() {
|
|
182
|
+
if (this.disposed)
|
|
183
|
+
return;
|
|
184
|
+
this.disposed = true;
|
|
185
|
+
if (this.terminateTimer)
|
|
186
|
+
clearTimeout(this.terminateTimer);
|
|
187
|
+
this.terminateTimer = null;
|
|
188
|
+
this.transportErrorHandlers.clear();
|
|
189
|
+
this.readerErrorDisposable.dispose();
|
|
190
|
+
this.connection.dispose();
|
|
191
|
+
this.reader.dispose();
|
|
192
|
+
this.writer.dispose();
|
|
193
|
+
this.worker.terminate();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export function createDefaultRustLanguageWorker() {
|
|
197
|
+
return new Worker(new URL('./rust-lsp.worker.js', import.meta.url), {
|
|
198
|
+
type: 'module',
|
|
199
|
+
name: 'crowdy-rust-lsp',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
export function isWorkerLspMessage(value) {
|
|
203
|
+
return decodeJsonRpcMessage(value).ok;
|
|
204
|
+
}
|
|
@@ -25,12 +25,18 @@ export interface GlueInitMessage {
|
|
|
25
25
|
type: 'init';
|
|
26
26
|
artifact: ArrayBuffer;
|
|
27
27
|
authority: 'player';
|
|
28
|
+
/** Server-authored budget loaded into an injected mutable `ck_fuel` global. */
|
|
28
29
|
fuelPerDispatch?: string;
|
|
30
|
+
/** Legacy metadata; the hard watchdog is owned by the page-side broker. */
|
|
29
31
|
watchdogMs?: number;
|
|
32
|
+
hostCallTimeoutMs?: number;
|
|
30
33
|
/** Local client tick cadence in ms (0/undefined => no self-tick). */
|
|
31
34
|
tickIntervalMs?: number;
|
|
32
35
|
}
|
|
33
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Parse the server-authored budget used to refill an instrumented artifact's
|
|
38
|
+
* mutable `ck_fuel` global before every guest dispatch.
|
|
39
|
+
*/
|
|
34
40
|
export declare function parseFuelBudget(raw: string | undefined): bigint | null;
|
|
35
41
|
/** A dispatch outcome the worker reports back to the broker. */
|
|
36
42
|
export type GlueDispatchResult = {
|
|
@@ -41,9 +47,9 @@ export type GlueDispatchResult = {
|
|
|
41
47
|
detail?: string;
|
|
42
48
|
};
|
|
43
49
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
50
|
+
* Classify a completed guest dispatch by elapsed wall time. This cannot
|
|
51
|
+
* interrupt synchronous WASM; the page-side PlayerCodeBroker owns the hard
|
|
52
|
+
* dispatch watchdog and terminates a worker whose dispatch never returns.
|
|
47
53
|
*/
|
|
48
54
|
export declare function runWithWatchdog(dispatch: () => unknown, watchdogMs: number, now?: () => number): Promise<GlueDispatchResult>;
|
|
49
55
|
/** The minimal guest-instance surface the runtime drives (a real WebAssembly.Instance satisfies it). */
|
|
@@ -51,6 +57,7 @@ export interface GuestExports {
|
|
|
51
57
|
memory: {
|
|
52
58
|
buffer: ArrayBuffer;
|
|
53
59
|
};
|
|
60
|
+
ck_fuel?: WebAssembly.Global;
|
|
54
61
|
ck_alloc(len: number): number;
|
|
55
62
|
ck_free?(ptr: number, len: number): void;
|
|
56
63
|
init?(): void;
|
|
@@ -66,6 +73,7 @@ export interface GlueRuntimeOptions {
|
|
|
66
73
|
/** Deterministic-enough randomness for the guest `random_get` (defaults to crypto). */
|
|
67
74
|
randomFill?: (buf: Uint8Array) => void;
|
|
68
75
|
now?: () => number;
|
|
76
|
+
fuelPerDispatch?: bigint | null;
|
|
69
77
|
}
|
|
70
78
|
/**
|
|
71
79
|
* Drives one untrusted guest module: builds the `ck` + wasi import table,
|
|
@@ -79,6 +87,7 @@ export declare class GlueRuntime {
|
|
|
79
87
|
private exports;
|
|
80
88
|
private stateBlob;
|
|
81
89
|
constructor(options: GlueRuntimeOptions);
|
|
90
|
+
private resetFuel;
|
|
82
91
|
/** The import object handed to `WebAssembly.instantiate`. Guest sees only these. */
|
|
83
92
|
buildImports(getExports: () => GuestExports | null): WebAssembly.Imports;
|
|
84
93
|
instantiate(artifact: ArrayBuffer): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glue-runtime.d.ts","sourceRoot":"","sources":["../../src/player-runtime/glue-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,iVAoBtB,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED
|
|
1
|
+
{"version":3,"file":"glue-runtime.d.ts","sourceRoot":"","sources":["../../src/player-runtime/glue-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,iVAoBtB,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC;IACpB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,OAAO,EACvB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,MAAyB,GACnC,OAAO,CAAC,kBAAkB,CAAC,CAe7B;AAED,wGAAwG;AACxG,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC;IAChC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,IAAI,CAAC,IAAI,IAAI,CAAC;IACd,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1D,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IACjC,4FAA4F;IAC5F,YAAY,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,UAAU,CAAC;IACnD,gEAAgE;IAChE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAkCD;;;;;;GAMG;AACH,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAiC;gBAErB,OAAO,EAAE,kBAAkB;IAExD,OAAO,CAAC,SAAS;IASjB,oFAAoF;IACpF,YAAY,CAAC,UAAU,EAAE,MAAM,YAAY,GAAG,IAAI,GAAG,WAAW,CAAC,OAAO;IA4FlE,WAAW,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD,gEAAgE;IAChE,IAAI,IAAI,IAAI;IAKZ,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxB,sFAAsF;IACtF,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU;CAuBxC"}
|
|
@@ -41,7 +41,10 @@ export const GLUE_HOST_FUNCTIONS = [
|
|
|
41
41
|
'overlay_draw',
|
|
42
42
|
'grid_permission_check',
|
|
43
43
|
];
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Parse the server-authored budget used to refill an instrumented artifact's
|
|
46
|
+
* mutable `ck_fuel` global before every guest dispatch.
|
|
47
|
+
*/
|
|
45
48
|
export function parseFuelBudget(raw) {
|
|
46
49
|
if (raw == null)
|
|
47
50
|
return null;
|
|
@@ -54,9 +57,9 @@ export function parseFuelBudget(raw) {
|
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
+
* Classify a completed guest dispatch by elapsed wall time. This cannot
|
|
61
|
+
* interrupt synchronous WASM; the page-side PlayerCodeBroker owns the hard
|
|
62
|
+
* dispatch watchdog and terminates a worker whose dispatch never returns.
|
|
60
63
|
*/
|
|
61
64
|
export async function runWithWatchdog(dispatch, watchdogMs, now = () => Date.now()) {
|
|
62
65
|
const start = now();
|
|
@@ -76,6 +79,16 @@ export async function runWithWatchdog(dispatch, watchdogMs, now = () => Date.now
|
|
|
76
79
|
return { ok: true };
|
|
77
80
|
}
|
|
78
81
|
const textDecoder = new TextDecoder();
|
|
82
|
+
function assertMemoryRange(buffer, ptr, len, operation) {
|
|
83
|
+
if (!Number.isSafeInteger(ptr) ||
|
|
84
|
+
!Number.isSafeInteger(len) ||
|
|
85
|
+
ptr < 0 ||
|
|
86
|
+
len < 0 ||
|
|
87
|
+
ptr > buffer.byteLength ||
|
|
88
|
+
len > buffer.byteLength - ptr) {
|
|
89
|
+
throw new RangeError(`${operation} is outside guest memory`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
79
92
|
function defaultRandomFill(buf) {
|
|
80
93
|
const c = globalThis.crypto;
|
|
81
94
|
if (c?.getRandomValues) {
|
|
@@ -102,6 +115,15 @@ export class GlueRuntime {
|
|
|
102
115
|
this.exports = null;
|
|
103
116
|
this.stateBlob = new Uint8Array(0);
|
|
104
117
|
}
|
|
118
|
+
resetFuel() {
|
|
119
|
+
const fuel = this.exports?.ck_fuel;
|
|
120
|
+
if (!fuel)
|
|
121
|
+
return;
|
|
122
|
+
if (this.options.fuelPerDispatch == null) {
|
|
123
|
+
throw new Error('instrumented artifact is missing a fuel budget');
|
|
124
|
+
}
|
|
125
|
+
fuel.value = this.options.fuelPerDispatch;
|
|
126
|
+
}
|
|
105
127
|
/** The import object handed to `WebAssembly.instantiate`. Guest sees only these. */
|
|
106
128
|
buildImports(getExports) {
|
|
107
129
|
const mem = () => {
|
|
@@ -114,17 +136,21 @@ export class GlueRuntime {
|
|
|
114
136
|
const ex = getExports();
|
|
115
137
|
if (!ex)
|
|
116
138
|
throw new Error('guest not instantiated');
|
|
139
|
+
const buffer = ex.memory.buffer;
|
|
140
|
+
assertMemoryRange(buffer, ptr, len, 'guest memory read');
|
|
117
141
|
// Copy into a fresh ArrayBuffer-backed view — the guest buffer may
|
|
118
142
|
// detach/grow between calls (and may be a SharedArrayBuffer).
|
|
119
143
|
const out = new Uint8Array(len);
|
|
120
|
-
out.set(new Uint8Array(
|
|
144
|
+
out.set(new Uint8Array(buffer, ptr, len));
|
|
121
145
|
return out;
|
|
122
146
|
};
|
|
123
147
|
const writeAt = (ptr, src) => {
|
|
124
148
|
const ex = getExports();
|
|
125
149
|
if (!ex)
|
|
126
150
|
throw new Error('guest not instantiated');
|
|
127
|
-
|
|
151
|
+
const buffer = ex.memory.buffer;
|
|
152
|
+
assertMemoryRange(buffer, ptr, src.length, 'guest memory write');
|
|
153
|
+
new Uint8Array(buffer, ptr, src.length).set(src);
|
|
128
154
|
};
|
|
129
155
|
const now = this.options.now ?? (() => Date.now());
|
|
130
156
|
const randomFill = this.options.randomFill ?? defaultRandomFill;
|
|
@@ -153,6 +179,9 @@ export class GlueRuntime {
|
|
|
153
179
|
if (!ex)
|
|
154
180
|
throw new Error('guest not instantiated');
|
|
155
181
|
const outPtr = ex.ck_alloc(respBytes.length);
|
|
182
|
+
if (respBytes.length > 0 && outPtr === 0) {
|
|
183
|
+
throw new RangeError('ck_alloc returned a null reply pointer');
|
|
184
|
+
}
|
|
156
185
|
writeAt(outPtr, respBytes);
|
|
157
186
|
// Packed (ptr << 32 | len); the guest reads then ck_frees it.
|
|
158
187
|
return (BigInt(outPtr) << 32n) | BigInt(respBytes.length >>> 0);
|
|
@@ -160,9 +189,13 @@ export class GlueRuntime {
|
|
|
160
189
|
};
|
|
161
190
|
const wasi = {
|
|
162
191
|
random_get: (ptr, len) => {
|
|
163
|
-
const
|
|
192
|
+
const ex = getExports();
|
|
193
|
+
if (!ex)
|
|
194
|
+
throw new Error('guest not instantiated');
|
|
195
|
+
const buffer = ex.memory.buffer;
|
|
196
|
+
assertMemoryRange(buffer, ptr, len, 'random_get write');
|
|
197
|
+
const buf = new Uint8Array(buffer, ptr, len);
|
|
164
198
|
randomFill(buf);
|
|
165
|
-
writeAt(ptr, buf);
|
|
166
199
|
return 0;
|
|
167
200
|
},
|
|
168
201
|
// A player artifact may pull in a few benign wasi stubs; keep them inert.
|
|
@@ -196,10 +229,12 @@ export class GlueRuntime {
|
|
|
196
229
|
}
|
|
197
230
|
/** Run the module's `init` export (once, after instantiate). */
|
|
198
231
|
init() {
|
|
232
|
+
this.resetFuel();
|
|
199
233
|
this.exports?.init?.();
|
|
200
234
|
}
|
|
201
235
|
/** Run one `tick(dt_ms)`. Throws propagate to the caller's watchdog wrapper. */
|
|
202
236
|
tick(dtMs) {
|
|
237
|
+
this.resetFuel();
|
|
203
238
|
this.exports?.tick?.(dtMs);
|
|
204
239
|
}
|
|
205
240
|
/** Invoke the module with an opaque payload; returns the reply bytes (copied out). */
|
|
@@ -208,13 +243,22 @@ export class GlueRuntime {
|
|
|
208
243
|
if (!ex || typeof ex.handle_invoke !== 'function')
|
|
209
244
|
return new Uint8Array(0);
|
|
210
245
|
const ptr = ex.ck_alloc(payload.length);
|
|
246
|
+
if (payload.length > 0 && ptr === 0) {
|
|
247
|
+
throw new RangeError('ck_alloc returned a null invoke pointer');
|
|
248
|
+
}
|
|
249
|
+
assertMemoryRange(ex.memory.buffer, ptr, payload.length, 'invoke request write');
|
|
211
250
|
new Uint8Array(ex.memory.buffer, ptr, payload.length).set(payload);
|
|
251
|
+
this.resetFuel();
|
|
212
252
|
const packed = BigInt(ex.handle_invoke(ptr, payload.length));
|
|
213
253
|
ex.ck_free?.(ptr, payload.length);
|
|
214
254
|
const outPtr = Number(packed >> 32n);
|
|
215
255
|
const outLen = Number(packed & 0xffffffffn);
|
|
216
256
|
if (outLen === 0)
|
|
217
257
|
return new Uint8Array(0);
|
|
258
|
+
if (outPtr === 0) {
|
|
259
|
+
throw new RangeError('guest returned a null invoke reply pointer');
|
|
260
|
+
}
|
|
261
|
+
assertMemoryRange(ex.memory.buffer, outPtr, outLen, 'invoke reply read');
|
|
218
262
|
const out = new Uint8Array(ex.memory.buffer, outPtr, outLen).slice();
|
|
219
263
|
ex.ck_free?.(outPtr, outLen);
|
|
220
264
|
return out;
|