@effindomv2/fui-as 0.1.1 → 0.1.3
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/browser/src/common-harness/managed-harness.ts +113 -0
- package/browser/src/host-events.ts +22 -1
- package/browser/src/host-services.ts +290 -10
- package/browser/src/worker-bootstrap.ts +27 -0
- package/package.json +1 -1
- package/scripts/build-demo-as.sh +36 -6
- package/scripts/build.sh +66 -6
- package/scripts/framework-host-services.ts +40 -0
- package/scripts/generate-host-events.ts +79 -6
- package/scripts/generate-host-services.ts +132 -15
- package/src/FuiPrimitives.ts +11 -1
- package/src/core/ffi.ts +8 -15
- package/src/core/generated/FrameworkHostServices.ts +36 -0
- package/src/host-services/runtime.ts +74 -1
|
@@ -255,6 +255,19 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
255
255
|
return textBridge.writeAppUtf8(ptr, capacity, text, context);
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
function writeAppBytes(ptr: number, capacity: number, bytes: Uint8Array, context: string): number {
|
|
259
|
+
if (capacity < 0) {
|
|
260
|
+
throw new Error(`${context} has invalid buffer capacity ${String(capacity)}.`);
|
|
261
|
+
}
|
|
262
|
+
if (bytes.length > capacity) {
|
|
263
|
+
throw new Error(`${context} returned ${String(bytes.length)} bytes but the shared result buffer only holds ${String(capacity)}.`);
|
|
264
|
+
}
|
|
265
|
+
if (bytes.length > 0) {
|
|
266
|
+
new Uint8Array(getCurrentMemory().buffer, ptr, bytes.length).set(bytes);
|
|
267
|
+
}
|
|
268
|
+
return bytes.length;
|
|
269
|
+
}
|
|
270
|
+
|
|
258
271
|
function withUiUtf8(
|
|
259
272
|
text: string,
|
|
260
273
|
callback: (ptr: WasmHandleLike | number, len: number) => void,
|
|
@@ -480,6 +493,71 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
480
493
|
method: NormalizedHostEventMethod,
|
|
481
494
|
args: readonly unknown[],
|
|
482
495
|
): Array<unknown> {
|
|
496
|
+
function alignOffset(value: number, alignment: number): number {
|
|
497
|
+
return alignment <= 1 ? value : (value + alignment - 1) & ~(alignment - 1);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function encodeTypedArrayArg(
|
|
501
|
+
type: "bytes" | "i32_array" | "u32_array" | "i64_array" | "u64_array" | "f64_array",
|
|
502
|
+
arg: unknown,
|
|
503
|
+
context: string,
|
|
504
|
+
): { bytes: Uint8Array; elementCount: number; alignment: number } {
|
|
505
|
+
if (type === "bytes") {
|
|
506
|
+
if (!(arg instanceof Uint8Array)) {
|
|
507
|
+
throw new Error(`${context} must be a Uint8Array.`);
|
|
508
|
+
}
|
|
509
|
+
return { bytes: arg, elementCount: arg.length, alignment: 1 };
|
|
510
|
+
}
|
|
511
|
+
if (type === "i32_array") {
|
|
512
|
+
if (!(arg instanceof Int32Array)) {
|
|
513
|
+
throw new Error(`${context} must be an Int32Array.`);
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
|
|
517
|
+
elementCount: arg.length,
|
|
518
|
+
alignment: 4,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
if (type === "u32_array") {
|
|
522
|
+
if (!(arg instanceof Uint32Array)) {
|
|
523
|
+
throw new Error(`${context} must be a Uint32Array.`);
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
|
|
527
|
+
elementCount: arg.length,
|
|
528
|
+
alignment: 4,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (type === "i64_array") {
|
|
532
|
+
if (!(arg instanceof BigInt64Array)) {
|
|
533
|
+
throw new Error(`${context} must be a BigInt64Array.`);
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
|
|
537
|
+
elementCount: arg.length,
|
|
538
|
+
alignment: 8,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (type === "u64_array") {
|
|
542
|
+
if (!(arg instanceof BigUint64Array)) {
|
|
543
|
+
throw new Error(`${context} must be a BigUint64Array.`);
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
|
|
547
|
+
elementCount: arg.length,
|
|
548
|
+
alignment: 8,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (!(arg instanceof Float64Array)) {
|
|
552
|
+
throw new Error(`${context} must be a Float64Array.`);
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
|
|
556
|
+
elementCount: arg.length,
|
|
557
|
+
alignment: 8,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
483
561
|
if (args.length != method.args.length) {
|
|
484
562
|
throw new Error(`Host event ${method.serviceName}.${method.methodName} expected ${String(method.args.length)} args but received ${String(args.length)}.`);
|
|
485
563
|
}
|
|
@@ -507,6 +585,22 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
507
585
|
}
|
|
508
586
|
continue;
|
|
509
587
|
}
|
|
588
|
+
if (type === "bytes" || type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
|
|
589
|
+
const payload = encodeTypedArrayArg(type, arg, context);
|
|
590
|
+
if (payload.bytes.length > 0) {
|
|
591
|
+
const alignedOffset = alignOffset(byteOffset, payload.alignment);
|
|
592
|
+
if (session.textBufferPtr === 0 || alignedOffset + payload.bytes.length > session.textBufferSize) {
|
|
593
|
+
throw new Error(`${context} exceeds the shared AssemblyScript text buffer.`);
|
|
594
|
+
}
|
|
595
|
+
const memory = new Uint8Array(session.memory.buffer, session.textBufferPtr + alignedOffset, payload.bytes.length);
|
|
596
|
+
memory.set(payload.bytes);
|
|
597
|
+
callArgs.push(session.textBufferPtr + alignedOffset, payload.elementCount);
|
|
598
|
+
byteOffset = alignedOffset + payload.bytes.length;
|
|
599
|
+
} else {
|
|
600
|
+
callArgs.push(0, 0);
|
|
601
|
+
}
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
510
604
|
if (type === 'bool') {
|
|
511
605
|
if (typeof arg !== 'boolean') {
|
|
512
606
|
throw new Error(`${context} must be a boolean.`);
|
|
@@ -514,6 +608,19 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
514
608
|
callArgs.push(arg ? 1 : 0);
|
|
515
609
|
continue;
|
|
516
610
|
}
|
|
611
|
+
if (type === "i64" || type === "u64") {
|
|
612
|
+
if (typeof arg !== "bigint") {
|
|
613
|
+
throw new Error(`${context} must be a bigint.`);
|
|
614
|
+
}
|
|
615
|
+
if (type === "i64" && (arg < -9223372036854775808n || arg > 9223372036854775807n)) {
|
|
616
|
+
throw new Error(`${context} must be a signed 64-bit integer.`);
|
|
617
|
+
}
|
|
618
|
+
if (type === "u64" && (arg < 0n || arg > 18446744073709551615n)) {
|
|
619
|
+
throw new Error(`${context} must be an unsigned 64-bit integer.`);
|
|
620
|
+
}
|
|
621
|
+
callArgs.push(arg);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
517
624
|
if (typeof arg !== 'number' || Number.isNaN(arg)) {
|
|
518
625
|
throw new Error(`${context} must be a number.`);
|
|
519
626
|
}
|
|
@@ -521,6 +628,10 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
521
628
|
if (!Number.isInteger(arg) || arg < -2147483648 || arg > 2147483647) {
|
|
522
629
|
throw new Error(`${context} must be a signed 32-bit integer.`);
|
|
523
630
|
}
|
|
631
|
+
} else if (type === "u32") {
|
|
632
|
+
if (!Number.isInteger(arg) || arg < 0 || arg > 4294967295) {
|
|
633
|
+
throw new Error(`${context} must be an unsigned 32-bit integer.`);
|
|
634
|
+
}
|
|
524
635
|
}
|
|
525
636
|
callArgs.push(arg);
|
|
526
637
|
}
|
|
@@ -703,6 +814,8 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
703
814
|
const hostServiceImports = createHostServiceImportModule(hostServices, {
|
|
704
815
|
readString: readAppUtf8,
|
|
705
816
|
writeString: writeAppUtf8,
|
|
817
|
+
readBytes: readAppBytes,
|
|
818
|
+
writeBytes: writeAppBytes,
|
|
706
819
|
});
|
|
707
820
|
return {
|
|
708
821
|
effindom_v2_ui: createUiImportModule({
|
|
@@ -3,6 +3,13 @@ import type { HostServiceTypeName } from "./host-services";
|
|
|
3
3
|
type HostEventTypeValue<T extends HostServiceTypeName> =
|
|
4
4
|
T extends "string" ? string :
|
|
5
5
|
T extends "bool" ? boolean :
|
|
6
|
+
T extends "bytes" ? Uint8Array :
|
|
7
|
+
T extends "i32_array" ? Int32Array :
|
|
8
|
+
T extends "u32_array" ? Uint32Array :
|
|
9
|
+
T extends "i64_array" ? BigInt64Array :
|
|
10
|
+
T extends "u64_array" ? BigUint64Array :
|
|
11
|
+
T extends "f64_array" ? Float64Array :
|
|
12
|
+
T extends "i64" | "u64" ? bigint :
|
|
6
13
|
T extends "void" ? void :
|
|
7
14
|
number;
|
|
8
15
|
|
|
@@ -59,7 +66,21 @@ function buildExportName(eventName: string): string {
|
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
function validateEventType(type: string, context: string): asserts type is HostServiceTypeName {
|
|
62
|
-
if (
|
|
69
|
+
if (
|
|
70
|
+
type === "string" ||
|
|
71
|
+
type === "bool" ||
|
|
72
|
+
type === "i32" ||
|
|
73
|
+
type === "u32" ||
|
|
74
|
+
type === "i64" ||
|
|
75
|
+
type === "u64" ||
|
|
76
|
+
type === "f64" ||
|
|
77
|
+
type === "bytes" ||
|
|
78
|
+
type === "i32_array" ||
|
|
79
|
+
type === "u32_array" ||
|
|
80
|
+
type === "i64_array" ||
|
|
81
|
+
type === "u64_array" ||
|
|
82
|
+
type === "f64_array"
|
|
83
|
+
) {
|
|
63
84
|
return;
|
|
64
85
|
}
|
|
65
86
|
throw new Error(`${context} uses unsupported host-event type "${type}".`);
|
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
export type HostServiceTypeName =
|
|
1
|
+
export type HostServiceTypeName =
|
|
2
|
+
| "string"
|
|
3
|
+
| "bool"
|
|
4
|
+
| "i32"
|
|
5
|
+
| "u32"
|
|
6
|
+
| "i64"
|
|
7
|
+
| "u64"
|
|
8
|
+
| "f64"
|
|
9
|
+
| "bytes"
|
|
10
|
+
| "i32_array"
|
|
11
|
+
| "u32_array"
|
|
12
|
+
| "i64_array"
|
|
13
|
+
| "u64_array"
|
|
14
|
+
| "f64_array"
|
|
15
|
+
| "void";
|
|
2
16
|
|
|
3
17
|
type HostServiceTypeValue<T extends HostServiceTypeName> =
|
|
4
18
|
T extends "string" ? string :
|
|
5
19
|
T extends "bool" ? boolean :
|
|
20
|
+
T extends "bytes" ? Uint8Array :
|
|
21
|
+
T extends "i32_array" ? Int32Array :
|
|
22
|
+
T extends "u32_array" ? Uint32Array :
|
|
23
|
+
T extends "i64_array" ? BigInt64Array :
|
|
24
|
+
T extends "u64_array" ? BigUint64Array :
|
|
25
|
+
T extends "f64_array" ? Float64Array :
|
|
26
|
+
T extends "i64" | "u64" ? bigint :
|
|
6
27
|
T extends "void" ? void :
|
|
7
28
|
number;
|
|
8
29
|
|
|
@@ -14,6 +35,7 @@ export interface HostServiceMethodDefinition<
|
|
|
14
35
|
TArgs extends readonly HostServiceTypeName[] = readonly HostServiceTypeName[],
|
|
15
36
|
TResult extends HostServiceTypeName = HostServiceTypeName,
|
|
16
37
|
> {
|
|
38
|
+
readonly importName?: string;
|
|
17
39
|
readonly args: TArgs;
|
|
18
40
|
readonly returns: TResult;
|
|
19
41
|
readonly implementation: (...args: HostServiceArgsValues<TArgs>) => HostServiceTypeValue<TResult>;
|
|
@@ -33,6 +55,8 @@ export interface NormalizedHostServiceMethod {
|
|
|
33
55
|
export interface HostServiceImportIo {
|
|
34
56
|
readString(ptr: number, len: number): string;
|
|
35
57
|
writeString(ptr: number, capacity: number, text: string, context: string): number;
|
|
58
|
+
readBytes(ptr: number, len: number): Uint8Array;
|
|
59
|
+
writeBytes(ptr: number, capacity: number, bytes: Uint8Array, context: string): number;
|
|
36
60
|
}
|
|
37
61
|
|
|
38
62
|
const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -63,7 +87,22 @@ function buildImportName(serviceName: string, methodName: string): string {
|
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
function validateServiceType(type: string, context: string): asserts type is HostServiceTypeName {
|
|
66
|
-
if (
|
|
90
|
+
if (
|
|
91
|
+
type === "string" ||
|
|
92
|
+
type === "bool" ||
|
|
93
|
+
type === "i32" ||
|
|
94
|
+
type === "u32" ||
|
|
95
|
+
type === "i64" ||
|
|
96
|
+
type === "u64" ||
|
|
97
|
+
type === "f64" ||
|
|
98
|
+
type === "bytes" ||
|
|
99
|
+
type === "i32_array" ||
|
|
100
|
+
type === "u32_array" ||
|
|
101
|
+
type === "i64_array" ||
|
|
102
|
+
type === "u64_array" ||
|
|
103
|
+
type === "f64_array" ||
|
|
104
|
+
type === "void"
|
|
105
|
+
) {
|
|
67
106
|
return;
|
|
68
107
|
}
|
|
69
108
|
throw new Error(`${context} uses unsupported host-service type "${type}".`);
|
|
@@ -79,7 +118,8 @@ export function listHostServiceMethods(services: HostServicesDefinition | undefi
|
|
|
79
118
|
assertIdentifier(serviceName, "Host service");
|
|
80
119
|
for (const [methodName, definition] of Object.entries(serviceMethods)) {
|
|
81
120
|
assertIdentifier(methodName, `Host service ${serviceName} method`);
|
|
82
|
-
const importName = buildImportName(serviceName, methodName);
|
|
121
|
+
const importName = definition.importName ?? buildImportName(serviceName, methodName);
|
|
122
|
+
assertIdentifier(importName, `Host service ${serviceName}.${methodName} import`);
|
|
83
123
|
if (seenImports.has(importName)) {
|
|
84
124
|
throw new Error(`Duplicate host-service import name "${importName}".`);
|
|
85
125
|
}
|
|
@@ -126,6 +166,48 @@ function expectString(value: unknown, context: string): string {
|
|
|
126
166
|
return value;
|
|
127
167
|
}
|
|
128
168
|
|
|
169
|
+
function expectBytes(value: unknown, context: string): Uint8Array {
|
|
170
|
+
if (!(value instanceof Uint8Array)) {
|
|
171
|
+
throw new Error(`${context} must be a Uint8Array.`);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function expectInt32Array(value: unknown, context: string): Int32Array {
|
|
177
|
+
if (!(value instanceof Int32Array)) {
|
|
178
|
+
throw new Error(`${context} must be an Int32Array.`);
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function expectFloat64Array(value: unknown, context: string): Float64Array {
|
|
184
|
+
if (!(value instanceof Float64Array)) {
|
|
185
|
+
throw new Error(`${context} must be a Float64Array.`);
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function expectBigInt64Array(value: unknown, context: string): BigInt64Array {
|
|
191
|
+
if (!(value instanceof BigInt64Array)) {
|
|
192
|
+
throw new Error(`${context} must be a BigInt64Array.`);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function expectBigUint64Array(value: unknown, context: string): BigUint64Array {
|
|
198
|
+
if (!(value instanceof BigUint64Array)) {
|
|
199
|
+
throw new Error(`${context} must be a BigUint64Array.`);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function expectUint32Array(value: unknown, context: string): Uint32Array {
|
|
205
|
+
if (!(value instanceof Uint32Array)) {
|
|
206
|
+
throw new Error(`${context} must be a Uint32Array.`);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
129
211
|
function expectI32(value: unknown, context: string): number {
|
|
130
212
|
const numberValue = expectNumber(value, context);
|
|
131
213
|
if (!Number.isInteger(numberValue) || numberValue < -2147483648 || numberValue > 2147483647) {
|
|
@@ -134,6 +216,109 @@ function expectI32(value: unknown, context: string): number {
|
|
|
134
216
|
return numberValue;
|
|
135
217
|
}
|
|
136
218
|
|
|
219
|
+
function expectU32(value: unknown, context: string): number {
|
|
220
|
+
const numberValue = expectNumber(value, context);
|
|
221
|
+
if (!Number.isInteger(numberValue) || numberValue < 0 || numberValue > 4294967295) {
|
|
222
|
+
throw new Error(`${context} must be an unsigned 32-bit integer.`);
|
|
223
|
+
}
|
|
224
|
+
return numberValue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function expectI64(value: unknown, context: string): bigint {
|
|
228
|
+
if (typeof value !== "bigint") {
|
|
229
|
+
throw new Error(`${context} must be a bigint.`);
|
|
230
|
+
}
|
|
231
|
+
if (value < -9223372036854775808n || value > 9223372036854775807n) {
|
|
232
|
+
throw new Error(`${context} must be a signed 64-bit integer.`);
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function expectU64(value: unknown, context: string): bigint {
|
|
238
|
+
if (typeof value !== "bigint") {
|
|
239
|
+
throw new Error(`${context} must be a bigint.`);
|
|
240
|
+
}
|
|
241
|
+
if (value < 0n || value > 18446744073709551615n) {
|
|
242
|
+
throw new Error(`${context} must be an unsigned 64-bit integer.`);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function expectLength(value: unknown, context: string): number {
|
|
248
|
+
const length = expectNumber(value, context);
|
|
249
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
250
|
+
throw new Error(`${context} must be a non-negative integer.`);
|
|
251
|
+
}
|
|
252
|
+
return length;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function bytesToI32Array(bytes: Uint8Array, context: string): Int32Array {
|
|
256
|
+
if ((bytes.byteLength & 3) !== 0) {
|
|
257
|
+
throw new Error(`${context} payload length must be divisible by 4.`);
|
|
258
|
+
}
|
|
259
|
+
const values = new Int32Array(bytes.byteLength >>> 2);
|
|
260
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
261
|
+
return values;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function bytesToU32Array(bytes: Uint8Array, context: string): Uint32Array {
|
|
265
|
+
if ((bytes.byteLength & 3) !== 0) {
|
|
266
|
+
throw new Error(`${context} payload length must be divisible by 4.`);
|
|
267
|
+
}
|
|
268
|
+
const values = new Uint32Array(bytes.byteLength >>> 2);
|
|
269
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
270
|
+
return values;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function bytesToF64Array(bytes: Uint8Array, context: string): Float64Array {
|
|
274
|
+
if ((bytes.byteLength & 7) !== 0) {
|
|
275
|
+
throw new Error(`${context} payload length must be divisible by 8.`);
|
|
276
|
+
}
|
|
277
|
+
const values = new Float64Array(bytes.byteLength >>> 3);
|
|
278
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
279
|
+
return values;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function bytesToI64Array(bytes: Uint8Array, context: string): BigInt64Array {
|
|
283
|
+
if ((bytes.byteLength & 7) !== 0) {
|
|
284
|
+
throw new Error(`${context} payload length must be divisible by 8.`);
|
|
285
|
+
}
|
|
286
|
+
const values = new BigInt64Array(bytes.byteLength >>> 3);
|
|
287
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
288
|
+
return values;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function bytesToU64Array(bytes: Uint8Array, context: string): BigUint64Array {
|
|
292
|
+
if ((bytes.byteLength & 7) !== 0) {
|
|
293
|
+
throw new Error(`${context} payload length must be divisible by 8.`);
|
|
294
|
+
}
|
|
295
|
+
const values = new BigUint64Array(bytes.byteLength >>> 3);
|
|
296
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
297
|
+
return values;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function typedArrayBytes(
|
|
301
|
+
value: Uint8Array | Int32Array | Uint32Array | BigInt64Array | BigUint64Array | Float64Array,
|
|
302
|
+
): Uint8Array {
|
|
303
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function consumedRawArgCount(method: NormalizedHostServiceMethod): number {
|
|
307
|
+
let count = 0;
|
|
308
|
+
method.args.forEach((type) => {
|
|
309
|
+
count += type === "string" ||
|
|
310
|
+
type === "bytes" ||
|
|
311
|
+
type === "i32_array" ||
|
|
312
|
+
type === "u32_array" ||
|
|
313
|
+
type === "i64_array" ||
|
|
314
|
+
type === "u64_array" ||
|
|
315
|
+
type === "f64_array"
|
|
316
|
+
? 2
|
|
317
|
+
: 1;
|
|
318
|
+
});
|
|
319
|
+
return count;
|
|
320
|
+
}
|
|
321
|
+
|
|
137
322
|
function decodeHostServiceArgs(
|
|
138
323
|
method: NormalizedHostServiceMethod,
|
|
139
324
|
rawArgs: readonly unknown[],
|
|
@@ -150,11 +335,64 @@ function decodeHostServiceArgs(
|
|
|
150
335
|
index += 2;
|
|
151
336
|
return;
|
|
152
337
|
}
|
|
338
|
+
if (type === "bytes") {
|
|
339
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
340
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
341
|
+
decodedArgs.push(len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len));
|
|
342
|
+
index += 2;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (type === "i32_array") {
|
|
346
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
347
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
348
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 2);
|
|
349
|
+
decodedArgs.push(bytesToI32Array(payload, context));
|
|
350
|
+
index += 2;
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (type === "u32_array") {
|
|
354
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
355
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
356
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 2);
|
|
357
|
+
decodedArgs.push(bytesToU32Array(payload, context));
|
|
358
|
+
index += 2;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (type === "f64_array") {
|
|
362
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
363
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
364
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 3);
|
|
365
|
+
decodedArgs.push(bytesToF64Array(payload, context));
|
|
366
|
+
index += 2;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (type === "i64_array") {
|
|
370
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
371
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
372
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 3);
|
|
373
|
+
decodedArgs.push(bytesToI64Array(payload, context));
|
|
374
|
+
index += 2;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (type === "u64_array") {
|
|
378
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
379
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
380
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 3);
|
|
381
|
+
decodedArgs.push(bytesToU64Array(payload, context));
|
|
382
|
+
index += 2;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
153
385
|
const rawValue = rawArgs[index];
|
|
154
386
|
if (type === "bool") {
|
|
155
387
|
decodedArgs.push(expectNumber(rawValue, context) !== 0);
|
|
156
388
|
} else if (type === "i32") {
|
|
157
389
|
decodedArgs.push(expectI32(rawValue, context));
|
|
390
|
+
} else if (type === "u32") {
|
|
391
|
+
decodedArgs.push(expectU32(rawValue, context));
|
|
392
|
+
} else if (type === "i64") {
|
|
393
|
+
decodedArgs.push(expectI64(rawValue, context));
|
|
394
|
+
} else if (type === "u64") {
|
|
395
|
+
decodedArgs.push(expectU64(rawValue, context));
|
|
158
396
|
} else if (type === "f64") {
|
|
159
397
|
decodedArgs.push(expectNumber(rawValue, context));
|
|
160
398
|
} else {
|
|
@@ -168,10 +406,10 @@ function decodeHostServiceArgs(
|
|
|
168
406
|
export function createHostServiceImportModule(
|
|
169
407
|
services: HostServicesDefinition | undefined,
|
|
170
408
|
io: HostServiceImportIo,
|
|
171
|
-
): Record<string, (...rawArgs: Array<unknown>) => number | void> {
|
|
172
|
-
const module: Record<string, (...rawArgs: Array<unknown>) => number | void> = {};
|
|
409
|
+
): Record<string, (...rawArgs: Array<unknown>) => number | bigint | void> {
|
|
410
|
+
const module: Record<string, (...rawArgs: Array<unknown>) => number | bigint | void> = {};
|
|
173
411
|
for (const method of listHostServiceMethods(services)) {
|
|
174
|
-
module[method.importName] = (...rawArgs: Array<unknown>): number | void => {
|
|
412
|
+
module[method.importName] = (...rawArgs: Array<unknown>): number | bigint | void => {
|
|
175
413
|
const decodedArgs = decodeHostServiceArgs(method, rawArgs, io);
|
|
176
414
|
const result = method.implementation(...decodedArgs);
|
|
177
415
|
const resultContext = `Host service ${method.serviceName}.${method.methodName} result`;
|
|
@@ -179,20 +417,62 @@ export function createHostServiceImportModule(
|
|
|
179
417
|
return;
|
|
180
418
|
}
|
|
181
419
|
if (method.returns === "string") {
|
|
182
|
-
|
|
183
|
-
method.args.forEach((type) => {
|
|
184
|
-
outputIndex += type === "string" ? 2 : 1;
|
|
185
|
-
});
|
|
420
|
+
const outputIndex = consumedRawArgCount(method);
|
|
186
421
|
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
187
422
|
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
188
423
|
return io.writeString(ptr, capacity, expectString(result, resultContext), resultContext);
|
|
189
424
|
}
|
|
425
|
+
if (method.returns === "bytes") {
|
|
426
|
+
const outputIndex = consumedRawArgCount(method);
|
|
427
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
428
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
429
|
+
return io.writeBytes(ptr, capacity, expectBytes(result, resultContext), resultContext);
|
|
430
|
+
}
|
|
431
|
+
if (method.returns === "i32_array") {
|
|
432
|
+
const outputIndex = consumedRawArgCount(method);
|
|
433
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
434
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
435
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectInt32Array(result, resultContext)), resultContext);
|
|
436
|
+
}
|
|
437
|
+
if (method.returns === "u32_array") {
|
|
438
|
+
const outputIndex = consumedRawArgCount(method);
|
|
439
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
440
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
441
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectUint32Array(result, resultContext)), resultContext);
|
|
442
|
+
}
|
|
443
|
+
if (method.returns === "f64_array") {
|
|
444
|
+
const outputIndex = consumedRawArgCount(method);
|
|
445
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
446
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
447
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectFloat64Array(result, resultContext)), resultContext);
|
|
448
|
+
}
|
|
449
|
+
if (method.returns === "i64_array") {
|
|
450
|
+
const outputIndex = consumedRawArgCount(method);
|
|
451
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
452
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
453
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectBigInt64Array(result, resultContext)), resultContext);
|
|
454
|
+
}
|
|
455
|
+
if (method.returns === "u64_array") {
|
|
456
|
+
const outputIndex = consumedRawArgCount(method);
|
|
457
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
458
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
459
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectBigUint64Array(result, resultContext)), resultContext);
|
|
460
|
+
}
|
|
190
461
|
if (method.returns === "bool") {
|
|
191
462
|
return expectBoolean(result, resultContext) ? 1 : 0;
|
|
192
463
|
}
|
|
193
464
|
if (method.returns === "i32") {
|
|
194
465
|
return expectI32(result, resultContext);
|
|
195
466
|
}
|
|
467
|
+
if (method.returns === "u32") {
|
|
468
|
+
return expectU32(result, resultContext);
|
|
469
|
+
}
|
|
470
|
+
if (method.returns === "i64") {
|
|
471
|
+
return expectI64(result, resultContext);
|
|
472
|
+
}
|
|
473
|
+
if (method.returns === "u64") {
|
|
474
|
+
return expectU64(result, resultContext);
|
|
475
|
+
}
|
|
196
476
|
if (method.returns === "f64") {
|
|
197
477
|
return expectNumber(result, resultContext);
|
|
198
478
|
}
|
|
@@ -106,6 +106,31 @@ function writeUtf8(memory: WebAssembly.Memory | null, ptr: number, capacity: num
|
|
|
106
106
|
return encoded.length;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
function readBytes(memory: WebAssembly.Memory | null, ptr: number, len: number): Uint8Array {
|
|
110
|
+
if (memory === null || len <= 0) {
|
|
111
|
+
return new Uint8Array(0);
|
|
112
|
+
}
|
|
113
|
+
const bytes = new Uint8Array(len);
|
|
114
|
+
bytes.set(new Uint8Array(memory.buffer, ptr, len));
|
|
115
|
+
return bytes;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeBytes(memory: WebAssembly.Memory | null, ptr: number, capacity: number, bytes: Uint8Array, context: string): number {
|
|
119
|
+
if (memory === null) {
|
|
120
|
+
throw new Error(`${context} requires worker memory.`);
|
|
121
|
+
}
|
|
122
|
+
if (capacity < 0) {
|
|
123
|
+
throw new Error(`${context} has invalid worker host-service buffer capacity.`);
|
|
124
|
+
}
|
|
125
|
+
if (bytes.length > capacity) {
|
|
126
|
+
throw new Error(`${context} exceeds the worker host-service result buffer.`);
|
|
127
|
+
}
|
|
128
|
+
if (bytes.length > 0) {
|
|
129
|
+
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
|
|
130
|
+
}
|
|
131
|
+
return bytes.length;
|
|
132
|
+
}
|
|
133
|
+
|
|
109
134
|
function encodeTextPartsPayload(values: readonly string[]): Uint8Array {
|
|
110
135
|
const encodedValues = values.map((value) => encoder.encode(value));
|
|
111
136
|
let totalBytes = 4;
|
|
@@ -277,6 +302,8 @@ async function startWorker(message: WorkerBootstrapStartMessage): Promise<void>
|
|
|
277
302
|
fui_host_service: createHostServiceImportModule(hostServices, {
|
|
278
303
|
readString: (ptr, len) => readUtf8(memory, ptr, len),
|
|
279
304
|
writeString: (ptr, capacity, text, context) => writeUtf8(memory, ptr, capacity, text, context),
|
|
305
|
+
readBytes: (ptr, len) => readBytes(memory, ptr, len),
|
|
306
|
+
writeBytes: (ptr, capacity, bytes, context) => writeBytes(memory, ptr, capacity, bytes, context),
|
|
280
307
|
}),
|
|
281
308
|
fui_worker_host: {
|
|
282
309
|
fui_worker_input_length(): number {
|
package/package.json
CHANGED