wasm-idle 0.4.0 → 0.4.1
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/README.md +18 -0
- package/dist/clang/app.js +17 -5
- package/dist/clang/index.d.ts +6 -0
- package/dist/clang/index.js +135 -19
- package/dist/index.d.ts +1 -0
- package/dist/playground/assetBridge.d.ts +20 -0
- package/dist/playground/assetBridge.js +192 -0
- package/dist/playground/assets.d.ts +32 -0
- package/dist/playground/assets.js +97 -0
- package/dist/playground/clang.d.ts +6 -1
- package/dist/playground/clang.js +13 -2
- package/dist/playground/index.js +3 -1
- package/dist/playground/java.d.ts +7 -2
- package/dist/playground/java.js +31 -5
- package/dist/playground/javaStdin.js +235 -2
- package/dist/playground/options.d.ts +9 -0
- package/dist/playground/options.js +15 -1
- package/dist/playground/python.d.ts +6 -1
- package/dist/playground/python.js +38 -5
- package/dist/playground/sandbox.d.ts +2 -1
- package/dist/playground/worker/assets.d.ts +12 -0
- package/dist/playground/worker/assets.js +257 -0
- package/dist/playground/worker/clang.js +11 -3
- package/dist/playground/worker/java.js +15 -35
- package/dist/playground/worker/python.js +13 -33
- package/dist/terminal/Terminal.svelte +57 -7
- package/dist/terminal/Terminal.svelte.d.ts +2 -0
- package/dist/terminal/theme.js +2 -2
- package/dist/terminal/types.d.ts +1 -0
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -8,4 +8,22 @@ Refer to src/lib/clang.
|
|
|
8
8
|
|
|
9
9
|
Java uses TeaVM's browser compiler/runtime. TeaVM compiler/runtime/classlib assets are bundled under `static/teavm/` by default, and the asset base URL can be overridden with `PUBLIC_TEAVM_BASE_URL`.
|
|
10
10
|
|
|
11
|
+
`Terminal` and `playground(...).load(...)` support either the legacy shared `path`/`rootUrl` or per-runtime asset config:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import type { PlaygroundRuntimeAssets } from 'wasm-idle';
|
|
15
|
+
|
|
16
|
+
const runtimeAssets: PlaygroundRuntimeAssets = {
|
|
17
|
+
rootUrl: 'https://cdn.example.com/repl',
|
|
18
|
+
python: {
|
|
19
|
+
loader: async ({ asset }) => ({ url: `https://cdn.example.com/repl/pyodide/${asset}` })
|
|
20
|
+
},
|
|
21
|
+
java: {
|
|
22
|
+
baseUrl: 'https://cdn.example.com/repl/teavm/'
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Python custom loaders receive file names under the Pyodide asset root and can serve both core assets and package files. TeaVM custom loaders receive file names under the TeaVM asset root. Compressed TeaVM runtime assets are no longer unpacked inside the library; provide the final file URL or handle decompression in your own loader.
|
|
28
|
+
|
|
11
29
|
Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, and TeaVM.
|
package/dist/clang/app.js
CHANGED
|
@@ -74,7 +74,9 @@ export default class App {
|
|
|
74
74
|
return ESUCCESS;
|
|
75
75
|
session.currentFunctionId = functionId;
|
|
76
76
|
session.currentLine = line;
|
|
77
|
-
const frame = [...session.frames]
|
|
77
|
+
const frame = [...session.frames]
|
|
78
|
+
.reverse()
|
|
79
|
+
.find((candidate) => candidate.functionId === functionId);
|
|
78
80
|
if (frame)
|
|
79
81
|
frame.line = line;
|
|
80
82
|
session.pauseOnEntry = false;
|
|
@@ -93,7 +95,10 @@ export default class App {
|
|
|
93
95
|
: variable.length
|
|
94
96
|
? [variable.length]
|
|
95
97
|
: [];
|
|
96
|
-
if (!Number.isFinite(address) ||
|
|
98
|
+
if (!Number.isFinite(address) ||
|
|
99
|
+
address <= 0 ||
|
|
100
|
+
!dimensions.length ||
|
|
101
|
+
!variable.elementKind) {
|
|
97
102
|
return [{ name: variable.name, value: '?' }];
|
|
98
103
|
}
|
|
99
104
|
const elementStride = variable.elementKind === 'double'
|
|
@@ -115,7 +120,9 @@ export default class App {
|
|
|
115
120
|
}
|
|
116
121
|
if (variable.elementKind === 'char') {
|
|
117
122
|
const charCode = this.mem.read8(offset);
|
|
118
|
-
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
123
|
+
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
124
|
+
? `'${String.fromCharCode(charCode)}'`
|
|
125
|
+
: `${charCode}`);
|
|
119
126
|
continue;
|
|
120
127
|
}
|
|
121
128
|
if (variable.elementKind === 'float') {
|
|
@@ -147,7 +154,9 @@ export default class App {
|
|
|
147
154
|
}
|
|
148
155
|
if (variable.elementKind === 'char') {
|
|
149
156
|
const charCode = this.mem.read8(offset);
|
|
150
|
-
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
157
|
+
values.push(charCode >= 0x20 && charCode <= 0x7e
|
|
158
|
+
? `'${String.fromCharCode(charCode)}'`
|
|
159
|
+
: `${charCode}`);
|
|
151
160
|
continue;
|
|
152
161
|
}
|
|
153
162
|
if (variable.elementKind === 'float') {
|
|
@@ -186,7 +195,10 @@ export default class App {
|
|
|
186
195
|
locals: [...locals, ...globals],
|
|
187
196
|
callStack: [...session.frames]
|
|
188
197
|
.reverse()
|
|
189
|
-
.map((stackFrame) => ({
|
|
198
|
+
.map((stackFrame) => ({
|
|
199
|
+
functionName: stackFrame.functionName,
|
|
200
|
+
line: stackFrame.line
|
|
201
|
+
}))
|
|
190
202
|
});
|
|
191
203
|
const sequence = Atomics.load(buffer, 0);
|
|
192
204
|
while (true) {
|
package/dist/clang/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { DebugVariable, DebugVariableMetadata } from '../playground/options
|
|
|
2
2
|
import App from './app';
|
|
3
3
|
import { MemFS } from './memory';
|
|
4
4
|
import { type Writable } from 'svelte/store';
|
|
5
|
+
type ClangSourceLanguage = 'C' | 'CPP';
|
|
5
6
|
interface APIOption {
|
|
6
7
|
stdin: () => string;
|
|
7
8
|
stdout: (str: string) => void;
|
|
@@ -17,7 +18,12 @@ interface APIOption {
|
|
|
17
18
|
path: string;
|
|
18
19
|
}
|
|
19
20
|
interface DebugRunOptions {
|
|
21
|
+
language?: ClangSourceLanguage;
|
|
20
22
|
args?: string[];
|
|
23
|
+
compileArgs?: string[];
|
|
24
|
+
programArgs?: string[];
|
|
25
|
+
cppVersion?: string;
|
|
26
|
+
cVersion?: string;
|
|
21
27
|
debug?: boolean;
|
|
22
28
|
breakpoints?: number[];
|
|
23
29
|
pauseOnEntry?: boolean;
|
package/dist/clang/index.js
CHANGED
|
@@ -20,7 +20,94 @@ const clangCommonArgs = [
|
|
|
20
20
|
'80',
|
|
21
21
|
'-fcolor-diagnostics'
|
|
22
22
|
];
|
|
23
|
-
const
|
|
23
|
+
const defaultCppStandardArg = '-std=gnu++2a';
|
|
24
|
+
const defaultCStandardArg = '-std=gnu11';
|
|
25
|
+
function normalizeStandardCode(value) {
|
|
26
|
+
return (value || '').trim().toUpperCase().replaceAll(/\s+/g, '');
|
|
27
|
+
}
|
|
28
|
+
function resolveCppStandardArg(version) {
|
|
29
|
+
switch (normalizeStandardCode(version)) {
|
|
30
|
+
case '03':
|
|
31
|
+
case 'CPP03':
|
|
32
|
+
case 'C++03':
|
|
33
|
+
case 'GNU++03':
|
|
34
|
+
case 'GNUC++03':
|
|
35
|
+
return '-std=gnu++03';
|
|
36
|
+
case '11':
|
|
37
|
+
case 'CPP11':
|
|
38
|
+
case 'C++11':
|
|
39
|
+
case 'GNU++11':
|
|
40
|
+
case 'GNUC++11':
|
|
41
|
+
return '-std=gnu++11';
|
|
42
|
+
case '14':
|
|
43
|
+
case 'CPP14':
|
|
44
|
+
case 'C++14':
|
|
45
|
+
case 'GNU++14':
|
|
46
|
+
case 'GNUC++14':
|
|
47
|
+
return '-std=gnu++14';
|
|
48
|
+
case '17':
|
|
49
|
+
case 'CPP17':
|
|
50
|
+
case 'C++17':
|
|
51
|
+
case 'GNU++17':
|
|
52
|
+
case 'GNUC++17':
|
|
53
|
+
return '-std=gnu++17';
|
|
54
|
+
case '20':
|
|
55
|
+
case '23':
|
|
56
|
+
case '26':
|
|
57
|
+
case 'CPP20':
|
|
58
|
+
case 'CPP23':
|
|
59
|
+
case 'CPP26':
|
|
60
|
+
case 'C++20':
|
|
61
|
+
case 'C++23':
|
|
62
|
+
case 'C++26':
|
|
63
|
+
case 'GNU++20':
|
|
64
|
+
case 'GNU++23':
|
|
65
|
+
case 'GNU++26':
|
|
66
|
+
case 'GNUC++20':
|
|
67
|
+
case 'GNUC++23':
|
|
68
|
+
case 'GNUC++26':
|
|
69
|
+
return defaultCppStandardArg;
|
|
70
|
+
default:
|
|
71
|
+
return defaultCppStandardArg;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function resolveCStandardArg(version) {
|
|
75
|
+
switch (normalizeStandardCode(version)) {
|
|
76
|
+
case '99':
|
|
77
|
+
case 'C99':
|
|
78
|
+
case 'GNU99':
|
|
79
|
+
case 'GNUC99':
|
|
80
|
+
return '-std=gnu99';
|
|
81
|
+
case '11':
|
|
82
|
+
case 'C11':
|
|
83
|
+
case 'GNU11':
|
|
84
|
+
case 'GNUC11':
|
|
85
|
+
return '-std=gnu11';
|
|
86
|
+
case '17':
|
|
87
|
+
case '18':
|
|
88
|
+
case 'C17':
|
|
89
|
+
case 'C18':
|
|
90
|
+
case 'GNU17':
|
|
91
|
+
case 'GNU18':
|
|
92
|
+
case 'GNUC17':
|
|
93
|
+
case 'GNUC18':
|
|
94
|
+
return '-std=gnu17';
|
|
95
|
+
default:
|
|
96
|
+
return defaultCStandardArg;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function resolveClangLanguageArgs(language, options) {
|
|
100
|
+
if (language === 'C') {
|
|
101
|
+
return {
|
|
102
|
+
languageArg: 'c',
|
|
103
|
+
standardArg: resolveCStandardArg(options.cVersion)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
languageArg: 'c++',
|
|
108
|
+
standardArg: resolveCppStandardArg(options.cppVersion)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
24
111
|
const toUtf8 = (text) => {
|
|
25
112
|
const surrogate = encodeURIComponent(text);
|
|
26
113
|
let result = '';
|
|
@@ -127,8 +214,11 @@ export default class Clang {
|
|
|
127
214
|
const input = options.input;
|
|
128
215
|
let source = options.code;
|
|
129
216
|
const obj = options.obj;
|
|
217
|
+
const language = options.language === 'C' ? 'C' : 'CPP';
|
|
218
|
+
const compileArgs = options.compileArgs ?? options.args ?? [];
|
|
219
|
+
const { languageArg, standardArg } = resolveClangLanguageArgs(language, options);
|
|
130
220
|
const debug = !!options.debug;
|
|
131
|
-
const opt = debug ? '0' :
|
|
221
|
+
const opt = debug ? '0' : options.opt || '2';
|
|
132
222
|
if (debug) {
|
|
133
223
|
const lines = source.split('\n');
|
|
134
224
|
let braceDepth = 0;
|
|
@@ -231,7 +321,8 @@ export default class Clang {
|
|
|
231
321
|
}
|
|
232
322
|
else {
|
|
233
323
|
analysisLine =
|
|
234
|
-
analysisLine.slice(0, commentStart) +
|
|
324
|
+
analysisLine.slice(0, commentStart) +
|
|
325
|
+
analysisLine.slice(commentEnd + 2);
|
|
235
326
|
}
|
|
236
327
|
}
|
|
237
328
|
const lineComment = analysisLine.indexOf('//');
|
|
@@ -343,7 +434,9 @@ export default class Clang {
|
|
|
343
434
|
let declarationParenDepth = 0;
|
|
344
435
|
let declarationBraceDepth = 0;
|
|
345
436
|
for (const character of declarationMatch[2]) {
|
|
346
|
-
if (character === ',' &&
|
|
437
|
+
if (character === ',' &&
|
|
438
|
+
declarationParenDepth === 0 &&
|
|
439
|
+
declarationBraceDepth === 0) {
|
|
347
440
|
if (declarationBuffer.trim())
|
|
348
441
|
declarations.push(declarationBuffer.trim());
|
|
349
442
|
declarationBuffer = '';
|
|
@@ -491,7 +584,8 @@ export default class Clang {
|
|
|
491
584
|
}
|
|
492
585
|
}
|
|
493
586
|
for (const [name, variable] of globalVariables) {
|
|
494
|
-
if (currentFunctionVariables.has(name) ||
|
|
587
|
+
if (currentFunctionVariables.has(name) ||
|
|
588
|
+
currentFunctionContainers.has(name))
|
|
495
589
|
continue;
|
|
496
590
|
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
497
591
|
if (new RegExp(`(?:^|[^\\w])(?:\\+\\+|--)\\s*${escapedName}\\b`).test(normalized) ||
|
|
@@ -529,7 +623,8 @@ export default class Clang {
|
|
|
529
623
|
for (const [name, variable] of currentFunctionVariables) {
|
|
530
624
|
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
531
625
|
const mutationPattern = new RegExp(`(?:^|[^\\w])(?:\\+\\+|--)\\s*${escapedName}\\b|\\b${escapedName}\\s*(?:[+\\-*/%]?=|\\+\\+|--)`);
|
|
532
|
-
if (!initLooksLikeDeclaration &&
|
|
626
|
+
if (!initLooksLikeDeclaration &&
|
|
627
|
+
mutationPattern.test(initSource)) {
|
|
533
628
|
initHooks.push(`${variable.kind === 'bool' ? '__wasm_idle_debug_value_bool' : '__wasm_idle_debug_value_num'}(${currentFunctionId}, ${variable.slot}, ${name})`);
|
|
534
629
|
}
|
|
535
630
|
if (mutationPattern.test(updateSource)) {
|
|
@@ -648,9 +743,11 @@ export default class Clang {
|
|
|
648
743
|
if (!nameMatch)
|
|
649
744
|
continue;
|
|
650
745
|
const name = nameMatch[1];
|
|
651
|
-
const kind = /\bbool\b/.test(cleaned)
|
|
652
|
-
? '
|
|
653
|
-
:
|
|
746
|
+
const kind = /\bbool\b/.test(cleaned)
|
|
747
|
+
? 'bool'
|
|
748
|
+
: /\b(?:int|float|double|char|short|long)\b/.test(cleaned)
|
|
749
|
+
? 'number'
|
|
750
|
+
: '';
|
|
654
751
|
if (!kind)
|
|
655
752
|
continue;
|
|
656
753
|
const slot = nextVariableSlot++;
|
|
@@ -721,11 +818,11 @@ export default class Clang {
|
|
|
721
818
|
'-O' + opt,
|
|
722
819
|
'-o',
|
|
723
820
|
obj,
|
|
724
|
-
|
|
821
|
+
standardArg,
|
|
725
822
|
'-x',
|
|
726
|
-
|
|
823
|
+
languageArg,
|
|
727
824
|
input,
|
|
728
|
-
...
|
|
825
|
+
...compileArgs
|
|
729
826
|
];
|
|
730
827
|
this.trace(`compile ${input} -> ${obj}`);
|
|
731
828
|
return await this.run(clang, true, 'clang', ...compilerArgs);
|
|
@@ -781,34 +878,53 @@ export default class Clang {
|
|
|
781
878
|
return stillRunning ? app : null;
|
|
782
879
|
}
|
|
783
880
|
async compileLink(code, options = {}) {
|
|
784
|
-
const { args = [], debug = false, breakpoints = [], pauseOnEntry = false, debugBuffer, interruptBuffer } = options;
|
|
785
|
-
const input = `test.cc`, obj = `test.o`, wasm = `test.wasm`;
|
|
881
|
+
const { language = 'CPP', args = [], compileArgs = args, debug = false, breakpoints = [], pauseOnEntry = false, cppVersion, cVersion, debugBuffer, interruptBuffer } = options;
|
|
882
|
+
const input = language === 'C' ? `test.c` : `test.cc`, obj = `test.o`, wasm = `test.wasm`;
|
|
786
883
|
this.beginTrace(debug);
|
|
787
884
|
this.debugBreakpoints = new Set(debug ? breakpoints : []);
|
|
788
885
|
this.debugPauseOnEntry = debug && pauseOnEntry;
|
|
789
886
|
this.debugBuffer = debugBuffer;
|
|
790
887
|
this.debugInterruptBuffer = interruptBuffer;
|
|
791
|
-
const buildKey = JSON.stringify({
|
|
888
|
+
const buildKey = JSON.stringify({
|
|
889
|
+
code,
|
|
890
|
+
language,
|
|
891
|
+
compileArgs,
|
|
892
|
+
cppVersion,
|
|
893
|
+
cVersion,
|
|
894
|
+
debug
|
|
895
|
+
});
|
|
792
896
|
if (this.lastBuildKey === buildKey) {
|
|
793
897
|
this.trace(`reuse ${wasm}`);
|
|
794
898
|
return this.wasm;
|
|
795
899
|
}
|
|
796
|
-
await this.compile({
|
|
900
|
+
await this.compile({
|
|
901
|
+
input,
|
|
902
|
+
code,
|
|
903
|
+
obj,
|
|
904
|
+
language,
|
|
905
|
+
compileArgs,
|
|
906
|
+
cppVersion,
|
|
907
|
+
cVersion,
|
|
908
|
+
debug
|
|
909
|
+
});
|
|
797
910
|
await this.link(obj, wasm, debug);
|
|
798
911
|
this.lastBuildKey = buildKey;
|
|
799
912
|
const wasmBytes = Uint8Array.from(this.memfs.getFileContents(wasm));
|
|
800
913
|
return (this.wasm = await this.hostLogAsync(`Compiling ${wasm}`, WebAssembly.compile(wasmBytes)));
|
|
801
914
|
}
|
|
802
915
|
async compileLinkRun(code, options = {}) {
|
|
803
|
-
const { args = [], debug = false, breakpoints = [], pauseOnEntry = false, debugBuffer, interruptBuffer } = options;
|
|
916
|
+
const { language = 'CPP', args = [], compileArgs = args, programArgs = [], debug = false, breakpoints = [], pauseOnEntry = false, cppVersion, cVersion, debugBuffer, interruptBuffer } = options;
|
|
804
917
|
this.debug = debug;
|
|
805
918
|
return await this.run(await this.compileLink(code, {
|
|
806
|
-
|
|
919
|
+
language,
|
|
920
|
+
compileArgs,
|
|
807
921
|
debug,
|
|
808
922
|
breakpoints,
|
|
809
923
|
pauseOnEntry,
|
|
924
|
+
cppVersion,
|
|
925
|
+
cVersion,
|
|
810
926
|
debugBuffer,
|
|
811
927
|
interruptBuffer
|
|
812
|
-
}), true, `test.wasm
|
|
928
|
+
}), true, `test.wasm`, ...programArgs);
|
|
813
929
|
}
|
|
814
930
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export default Terminal;
|
|
|
5
5
|
export { playground };
|
|
6
6
|
export type { DebugCommand, DebugFrame, DebugSessionEvent, DebugVariable, SandboxExecutionOptions, TerminalControl } from './terminal';
|
|
7
7
|
export type { CompilerDiagnostic } from './playground/options';
|
|
8
|
+
export type { PlaygroundRuntimeAssets, RuntimeAssetConfig, RuntimeAssetLoadRequest, RuntimeAssetLoader, RuntimeAssetLoaderResult } from './playground/assets';
|
|
8
9
|
export type { DebugLanguageAdapter } from './debug';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ResolvedRuntimeAssetConfig, type RuntimeAssetRuntime } from './assets';
|
|
2
|
+
type ProgressLike = {
|
|
3
|
+
set?: (value: number) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare class WorkerAssetBridge {
|
|
6
|
+
private worker;
|
|
7
|
+
private readonly runtime;
|
|
8
|
+
private config;
|
|
9
|
+
private readonly progress;
|
|
10
|
+
constructor(worker: Worker, runtime: RuntimeAssetRuntime, config: ResolvedRuntimeAssetConfig, progress?: ProgressLike);
|
|
11
|
+
matches(config: ResolvedRuntimeAssetConfig): boolean;
|
|
12
|
+
rebind(worker: Worker, config: ResolvedRuntimeAssetConfig, progress?: ProgressLike): void;
|
|
13
|
+
resetProgress(progress?: ProgressLike): void;
|
|
14
|
+
handleMessage(event: MessageEvent<any>): boolean;
|
|
15
|
+
private respond;
|
|
16
|
+
private loadAsset;
|
|
17
|
+
private normalizeLoaderResult;
|
|
18
|
+
private fetchAsset;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { JAVA_RUNTIME_LOAD_ASSETS, PYTHON_RUNTIME_LOAD_ASSETS } from './assets';
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
const transferBuffer = (bytes) => bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength
|
|
4
|
+
? bytes.buffer
|
|
5
|
+
: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
6
|
+
const expectedAssetsForRuntime = (runtime) => new Set(runtime === 'python' ? [...PYTHON_RUNTIME_LOAD_ASSETS] : [...JAVA_RUNTIME_LOAD_ASSETS]);
|
|
7
|
+
class RuntimeLoadProgress {
|
|
8
|
+
fractions = new Map();
|
|
9
|
+
expectedAssets;
|
|
10
|
+
progress;
|
|
11
|
+
constructor(runtime) {
|
|
12
|
+
this.expectedAssets = expectedAssetsForRuntime(runtime);
|
|
13
|
+
this.reset();
|
|
14
|
+
}
|
|
15
|
+
reset(progress) {
|
|
16
|
+
this.progress = progress;
|
|
17
|
+
this.fractions.clear();
|
|
18
|
+
for (const asset of this.expectedAssets)
|
|
19
|
+
this.fractions.set(asset, 0);
|
|
20
|
+
this.emit();
|
|
21
|
+
}
|
|
22
|
+
update(asset, loaded, total) {
|
|
23
|
+
if (!this.expectedAssets.has(asset))
|
|
24
|
+
return;
|
|
25
|
+
const fraction = total && total > 0 ? Math.min(loaded / total, 1) : loaded > 0 ? 1 : 0;
|
|
26
|
+
this.fractions.set(asset, fraction);
|
|
27
|
+
this.emit();
|
|
28
|
+
}
|
|
29
|
+
emit() {
|
|
30
|
+
if (!this.progress)
|
|
31
|
+
return;
|
|
32
|
+
if (!this.fractions.size)
|
|
33
|
+
return this.progress.set?.(0);
|
|
34
|
+
let total = 0;
|
|
35
|
+
for (const fraction of this.fractions.values())
|
|
36
|
+
total += fraction;
|
|
37
|
+
this.progress.set?.(total / this.fractions.size);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class WorkerAssetBridge {
|
|
41
|
+
worker;
|
|
42
|
+
runtime;
|
|
43
|
+
config;
|
|
44
|
+
progress;
|
|
45
|
+
constructor(worker, runtime, config, progress) {
|
|
46
|
+
this.worker = worker;
|
|
47
|
+
this.runtime = runtime;
|
|
48
|
+
this.config = config;
|
|
49
|
+
this.progress = new RuntimeLoadProgress(runtime);
|
|
50
|
+
this.progress.reset(progress);
|
|
51
|
+
}
|
|
52
|
+
matches(config) {
|
|
53
|
+
return (this.config.baseUrl === config.baseUrl &&
|
|
54
|
+
this.config.loader === config.loader &&
|
|
55
|
+
this.config.useAssetBridge === config.useAssetBridge);
|
|
56
|
+
}
|
|
57
|
+
rebind(worker, config, progress) {
|
|
58
|
+
this.worker = worker;
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.progress.reset(progress);
|
|
61
|
+
}
|
|
62
|
+
resetProgress(progress) {
|
|
63
|
+
this.progress.reset(progress);
|
|
64
|
+
}
|
|
65
|
+
handleMessage(event) {
|
|
66
|
+
const assetRequest = event.data?.assetRequest;
|
|
67
|
+
if (assetRequest) {
|
|
68
|
+
void this.respond(assetRequest);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const assetProgress = event.data?.assetProgress;
|
|
72
|
+
if (assetProgress) {
|
|
73
|
+
this.progress.update(assetProgress.asset, assetProgress.loaded, assetProgress.total);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
async respond(request) {
|
|
79
|
+
try {
|
|
80
|
+
const loaded = await this.loadAsset(request.asset);
|
|
81
|
+
const buffer = transferBuffer(loaded.bytes);
|
|
82
|
+
this.worker.postMessage({
|
|
83
|
+
assetResponse: {
|
|
84
|
+
id: request.id,
|
|
85
|
+
ok: true,
|
|
86
|
+
bytes: buffer,
|
|
87
|
+
mimeType: loaded.mimeType
|
|
88
|
+
}
|
|
89
|
+
}, [buffer]);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.worker.postMessage({
|
|
93
|
+
assetResponse: {
|
|
94
|
+
id: request.id,
|
|
95
|
+
ok: false,
|
|
96
|
+
error: error instanceof Error ? error.message : String(error)
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async loadAsset(asset) {
|
|
102
|
+
const reportProgress = (loaded, total) => this.progress.update(asset, loaded, total);
|
|
103
|
+
if (this.config.loader) {
|
|
104
|
+
const loaded = await this.normalizeLoaderResult(await this.config.loader({
|
|
105
|
+
runtime: this.runtime,
|
|
106
|
+
asset,
|
|
107
|
+
reportProgress
|
|
108
|
+
}), asset);
|
|
109
|
+
if (loaded)
|
|
110
|
+
return loaded;
|
|
111
|
+
}
|
|
112
|
+
return await this.fetchAsset(new URL(asset, this.config.baseUrl).href, asset);
|
|
113
|
+
}
|
|
114
|
+
async normalizeLoaderResult(result, asset) {
|
|
115
|
+
if (!result)
|
|
116
|
+
return null;
|
|
117
|
+
if (typeof result === 'string' || result instanceof URL) {
|
|
118
|
+
return await this.fetchAsset(String(result), asset);
|
|
119
|
+
}
|
|
120
|
+
if (result instanceof ArrayBuffer) {
|
|
121
|
+
const bytes = new Uint8Array(result);
|
|
122
|
+
this.progress.update(asset, bytes.byteLength, bytes.byteLength);
|
|
123
|
+
return { bytes };
|
|
124
|
+
}
|
|
125
|
+
if (result instanceof Uint8Array) {
|
|
126
|
+
this.progress.update(asset, result.byteLength, result.byteLength);
|
|
127
|
+
return { bytes: result };
|
|
128
|
+
}
|
|
129
|
+
if (result instanceof Blob) {
|
|
130
|
+
const bytes = new Uint8Array(await result.arrayBuffer());
|
|
131
|
+
this.progress.update(asset, bytes.byteLength, bytes.byteLength);
|
|
132
|
+
return { bytes, mimeType: result.type || undefined };
|
|
133
|
+
}
|
|
134
|
+
if ('url' in result && result.url) {
|
|
135
|
+
return await this.fetchAsset(String(result.url), asset);
|
|
136
|
+
}
|
|
137
|
+
if ('data' in result) {
|
|
138
|
+
if (typeof result.data === 'string') {
|
|
139
|
+
const bytes = encoder.encode(result.data);
|
|
140
|
+
this.progress.update(asset, bytes.byteLength, bytes.byteLength);
|
|
141
|
+
return { bytes, mimeType: result.mimeType };
|
|
142
|
+
}
|
|
143
|
+
if (result.data instanceof ArrayBuffer) {
|
|
144
|
+
const bytes = new Uint8Array(result.data);
|
|
145
|
+
this.progress.update(asset, bytes.byteLength, bytes.byteLength);
|
|
146
|
+
return { bytes, mimeType: result.mimeType };
|
|
147
|
+
}
|
|
148
|
+
if (result.data instanceof Uint8Array) {
|
|
149
|
+
this.progress.update(asset, result.data.byteLength, result.data.byteLength);
|
|
150
|
+
return { bytes: result.data, mimeType: result.mimeType };
|
|
151
|
+
}
|
|
152
|
+
const bytes = new Uint8Array(await result.data.arrayBuffer());
|
|
153
|
+
this.progress.update(asset, bytes.byteLength, bytes.byteLength);
|
|
154
|
+
return { bytes, mimeType: result.mimeType || result.data.type || undefined };
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
async fetchAsset(url, asset) {
|
|
159
|
+
const response = await fetch(url);
|
|
160
|
+
if (!response.ok)
|
|
161
|
+
throw new Error(`Failed to load ${asset}: ${response.status}`);
|
|
162
|
+
const contentLength = Number(response.headers.get('content-length') || 0) || undefined;
|
|
163
|
+
const mimeType = response.headers.get('content-type') || undefined;
|
|
164
|
+
if (!response.body) {
|
|
165
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
166
|
+
this.progress.update(asset, bytes.byteLength, contentLength ?? bytes.byteLength);
|
|
167
|
+
return { bytes, mimeType };
|
|
168
|
+
}
|
|
169
|
+
const reader = response.body.getReader();
|
|
170
|
+
let receivedLength = 0;
|
|
171
|
+
const chunks = [];
|
|
172
|
+
while (true) {
|
|
173
|
+
const { done, value } = await reader.read();
|
|
174
|
+
if (done)
|
|
175
|
+
break;
|
|
176
|
+
if (!value)
|
|
177
|
+
continue;
|
|
178
|
+
const chunk = Uint8Array.from(value);
|
|
179
|
+
chunks.push(chunk);
|
|
180
|
+
receivedLength += chunk.byteLength;
|
|
181
|
+
this.progress.update(asset, receivedLength, contentLength);
|
|
182
|
+
}
|
|
183
|
+
const bytes = new Uint8Array(receivedLength);
|
|
184
|
+
let position = 0;
|
|
185
|
+
for (const chunk of chunks) {
|
|
186
|
+
bytes.set(chunk, position);
|
|
187
|
+
position += chunk.byteLength;
|
|
188
|
+
}
|
|
189
|
+
this.progress.update(asset, receivedLength, contentLength ?? receivedLength);
|
|
190
|
+
return { bytes, mimeType };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type RuntimeAssetRuntime = 'python' | 'java';
|
|
2
|
+
export interface RuntimeAssetLoadRequest {
|
|
3
|
+
runtime: RuntimeAssetRuntime;
|
|
4
|
+
asset: string;
|
|
5
|
+
reportProgress: (loaded: number, total?: number) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface RuntimeAssetDataResult {
|
|
8
|
+
data: string | ArrayBuffer | Uint8Array | Blob;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RuntimeAssetUrlResult {
|
|
12
|
+
url: string | URL;
|
|
13
|
+
}
|
|
14
|
+
export type RuntimeAssetLoaderResult = RuntimeAssetDataResult | RuntimeAssetUrlResult | string | URL | ArrayBuffer | Uint8Array | Blob | null | undefined;
|
|
15
|
+
export type RuntimeAssetLoader = (request: RuntimeAssetLoadRequest) => RuntimeAssetLoaderResult | Promise<RuntimeAssetLoaderResult>;
|
|
16
|
+
export interface RuntimeAssetConfig {
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
loader?: RuntimeAssetLoader;
|
|
19
|
+
}
|
|
20
|
+
export interface PlaygroundRuntimeAssets {
|
|
21
|
+
rootUrl?: string;
|
|
22
|
+
python?: RuntimeAssetConfig;
|
|
23
|
+
java?: RuntimeAssetConfig;
|
|
24
|
+
}
|
|
25
|
+
export interface ResolvedRuntimeAssetConfig {
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
loader?: RuntimeAssetLoader;
|
|
28
|
+
useAssetBridge: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare const PYTHON_RUNTIME_LOAD_ASSETS: readonly ["pyodide.asm.js", "pyodide-lock.json", "pyodide.asm.wasm", "python_stdlib.zip"];
|
|
31
|
+
export declare const JAVA_RUNTIME_LOAD_ASSETS: readonly ["compiler.wasm-runtime.js", "compiler.wasm", "compile-classlib-teavm.bin", "runtime-classlib-teavm.bin"];
|
|
32
|
+
export declare function resolveRuntimeAssetConfig(runtime: RuntimeAssetRuntime, options: string | PlaygroundRuntimeAssets | undefined, currentUrl?: string): ResolvedRuntimeAssetConfig;
|