@effindomv2/fui-as 0.1.2 → 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 +49 -2
- package/browser/src/host-events.ts +10 -0
- package/browser/src/host-services.ts +167 -6
- package/package.json +1 -1
- package/scripts/build-demo-as.sh +13 -6
- package/scripts/build.sh +13 -6
- package/scripts/framework-host-services.ts +40 -0
- package/scripts/generate-host-events.ts +48 -2
- package/scripts/generate-host-services.ts +70 -11
- package/src/FuiPrimitives.ts +3 -0
- package/src/core/ffi.ts +8 -15
- package/src/core/generated/FrameworkHostServices.ts +36 -0
- package/src/host-services/runtime.ts +36 -0
|
@@ -498,7 +498,7 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
function encodeTypedArrayArg(
|
|
501
|
-
type: "bytes" | "i32_array" | "f64_array",
|
|
501
|
+
type: "bytes" | "i32_array" | "u32_array" | "i64_array" | "u64_array" | "f64_array",
|
|
502
502
|
arg: unknown,
|
|
503
503
|
context: string,
|
|
504
504
|
): { bytes: Uint8Array; elementCount: number; alignment: number } {
|
|
@@ -518,6 +518,36 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
518
518
|
alignment: 4,
|
|
519
519
|
};
|
|
520
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
|
+
}
|
|
521
551
|
if (!(arg instanceof Float64Array)) {
|
|
522
552
|
throw new Error(`${context} must be a Float64Array.`);
|
|
523
553
|
}
|
|
@@ -555,7 +585,7 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
555
585
|
}
|
|
556
586
|
continue;
|
|
557
587
|
}
|
|
558
|
-
if (type === "bytes" || type === "i32_array" || type === "f64_array") {
|
|
588
|
+
if (type === "bytes" || type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
|
|
559
589
|
const payload = encodeTypedArrayArg(type, arg, context);
|
|
560
590
|
if (payload.bytes.length > 0) {
|
|
561
591
|
const alignedOffset = alignOffset(byteOffset, payload.alignment);
|
|
@@ -578,6 +608,19 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
578
608
|
callArgs.push(arg ? 1 : 0);
|
|
579
609
|
continue;
|
|
580
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
|
+
}
|
|
581
624
|
if (typeof arg !== 'number' || Number.isNaN(arg)) {
|
|
582
625
|
throw new Error(`${context} must be a number.`);
|
|
583
626
|
}
|
|
@@ -585,6 +628,10 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
585
628
|
if (!Number.isInteger(arg) || arg < -2147483648 || arg > 2147483647) {
|
|
586
629
|
throw new Error(`${context} must be a signed 32-bit integer.`);
|
|
587
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
|
+
}
|
|
588
635
|
}
|
|
589
636
|
callArgs.push(arg);
|
|
590
637
|
}
|
|
@@ -5,7 +5,11 @@ type HostEventTypeValue<T extends HostServiceTypeName> =
|
|
|
5
5
|
T extends "bool" ? boolean :
|
|
6
6
|
T extends "bytes" ? Uint8Array :
|
|
7
7
|
T extends "i32_array" ? Int32Array :
|
|
8
|
+
T extends "u32_array" ? Uint32Array :
|
|
9
|
+
T extends "i64_array" ? BigInt64Array :
|
|
10
|
+
T extends "u64_array" ? BigUint64Array :
|
|
8
11
|
T extends "f64_array" ? Float64Array :
|
|
12
|
+
T extends "i64" | "u64" ? bigint :
|
|
9
13
|
T extends "void" ? void :
|
|
10
14
|
number;
|
|
11
15
|
|
|
@@ -66,9 +70,15 @@ function validateEventType(type: string, context: string): asserts type is HostS
|
|
|
66
70
|
type === "string" ||
|
|
67
71
|
type === "bool" ||
|
|
68
72
|
type === "i32" ||
|
|
73
|
+
type === "u32" ||
|
|
74
|
+
type === "i64" ||
|
|
75
|
+
type === "u64" ||
|
|
69
76
|
type === "f64" ||
|
|
70
77
|
type === "bytes" ||
|
|
71
78
|
type === "i32_array" ||
|
|
79
|
+
type === "u32_array" ||
|
|
80
|
+
type === "i64_array" ||
|
|
81
|
+
type === "u64_array" ||
|
|
72
82
|
type === "f64_array"
|
|
73
83
|
) {
|
|
74
84
|
return;
|
|
@@ -2,9 +2,15 @@ export type HostServiceTypeName =
|
|
|
2
2
|
| "string"
|
|
3
3
|
| "bool"
|
|
4
4
|
| "i32"
|
|
5
|
+
| "u32"
|
|
6
|
+
| "i64"
|
|
7
|
+
| "u64"
|
|
5
8
|
| "f64"
|
|
6
9
|
| "bytes"
|
|
7
10
|
| "i32_array"
|
|
11
|
+
| "u32_array"
|
|
12
|
+
| "i64_array"
|
|
13
|
+
| "u64_array"
|
|
8
14
|
| "f64_array"
|
|
9
15
|
| "void";
|
|
10
16
|
|
|
@@ -13,7 +19,11 @@ type HostServiceTypeValue<T extends HostServiceTypeName> =
|
|
|
13
19
|
T extends "bool" ? boolean :
|
|
14
20
|
T extends "bytes" ? Uint8Array :
|
|
15
21
|
T extends "i32_array" ? Int32Array :
|
|
22
|
+
T extends "u32_array" ? Uint32Array :
|
|
23
|
+
T extends "i64_array" ? BigInt64Array :
|
|
24
|
+
T extends "u64_array" ? BigUint64Array :
|
|
16
25
|
T extends "f64_array" ? Float64Array :
|
|
26
|
+
T extends "i64" | "u64" ? bigint :
|
|
17
27
|
T extends "void" ? void :
|
|
18
28
|
number;
|
|
19
29
|
|
|
@@ -25,6 +35,7 @@ export interface HostServiceMethodDefinition<
|
|
|
25
35
|
TArgs extends readonly HostServiceTypeName[] = readonly HostServiceTypeName[],
|
|
26
36
|
TResult extends HostServiceTypeName = HostServiceTypeName,
|
|
27
37
|
> {
|
|
38
|
+
readonly importName?: string;
|
|
28
39
|
readonly args: TArgs;
|
|
29
40
|
readonly returns: TResult;
|
|
30
41
|
readonly implementation: (...args: HostServiceArgsValues<TArgs>) => HostServiceTypeValue<TResult>;
|
|
@@ -80,9 +91,15 @@ function validateServiceType(type: string, context: string): asserts type is Hos
|
|
|
80
91
|
type === "string" ||
|
|
81
92
|
type === "bool" ||
|
|
82
93
|
type === "i32" ||
|
|
94
|
+
type === "u32" ||
|
|
95
|
+
type === "i64" ||
|
|
96
|
+
type === "u64" ||
|
|
83
97
|
type === "f64" ||
|
|
84
98
|
type === "bytes" ||
|
|
85
99
|
type === "i32_array" ||
|
|
100
|
+
type === "u32_array" ||
|
|
101
|
+
type === "i64_array" ||
|
|
102
|
+
type === "u64_array" ||
|
|
86
103
|
type === "f64_array" ||
|
|
87
104
|
type === "void"
|
|
88
105
|
) {
|
|
@@ -101,7 +118,8 @@ export function listHostServiceMethods(services: HostServicesDefinition | undefi
|
|
|
101
118
|
assertIdentifier(serviceName, "Host service");
|
|
102
119
|
for (const [methodName, definition] of Object.entries(serviceMethods)) {
|
|
103
120
|
assertIdentifier(methodName, `Host service ${serviceName} method`);
|
|
104
|
-
const importName = buildImportName(serviceName, methodName);
|
|
121
|
+
const importName = definition.importName ?? buildImportName(serviceName, methodName);
|
|
122
|
+
assertIdentifier(importName, `Host service ${serviceName}.${methodName} import`);
|
|
105
123
|
if (seenImports.has(importName)) {
|
|
106
124
|
throw new Error(`Duplicate host-service import name "${importName}".`);
|
|
107
125
|
}
|
|
@@ -169,6 +187,27 @@ function expectFloat64Array(value: unknown, context: string): Float64Array {
|
|
|
169
187
|
return value;
|
|
170
188
|
}
|
|
171
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
|
+
|
|
172
211
|
function expectI32(value: unknown, context: string): number {
|
|
173
212
|
const numberValue = expectNumber(value, context);
|
|
174
213
|
if (!Number.isInteger(numberValue) || numberValue < -2147483648 || numberValue > 2147483647) {
|
|
@@ -177,6 +216,34 @@ function expectI32(value: unknown, context: string): number {
|
|
|
177
216
|
return numberValue;
|
|
178
217
|
}
|
|
179
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
|
+
|
|
180
247
|
function expectLength(value: unknown, context: string): number {
|
|
181
248
|
const length = expectNumber(value, context);
|
|
182
249
|
if (!Number.isInteger(length) || length < 0) {
|
|
@@ -194,6 +261,15 @@ function bytesToI32Array(bytes: Uint8Array, context: string): Int32Array {
|
|
|
194
261
|
return values;
|
|
195
262
|
}
|
|
196
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
|
+
|
|
197
273
|
function bytesToF64Array(bytes: Uint8Array, context: string): Float64Array {
|
|
198
274
|
if ((bytes.byteLength & 7) !== 0) {
|
|
199
275
|
throw new Error(`${context} payload length must be divisible by 8.`);
|
|
@@ -203,14 +279,42 @@ function bytesToF64Array(bytes: Uint8Array, context: string): Float64Array {
|
|
|
203
279
|
return values;
|
|
204
280
|
}
|
|
205
281
|
|
|
206
|
-
function
|
|
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 {
|
|
207
303
|
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
208
304
|
}
|
|
209
305
|
|
|
210
306
|
function consumedRawArgCount(method: NormalizedHostServiceMethod): number {
|
|
211
307
|
let count = 0;
|
|
212
308
|
method.args.forEach((type) => {
|
|
213
|
-
count += type === "string" ||
|
|
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;
|
|
214
318
|
});
|
|
215
319
|
return count;
|
|
216
320
|
}
|
|
@@ -246,6 +350,14 @@ function decodeHostServiceArgs(
|
|
|
246
350
|
index += 2;
|
|
247
351
|
return;
|
|
248
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
|
+
}
|
|
249
361
|
if (type === "f64_array") {
|
|
250
362
|
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
251
363
|
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
@@ -254,11 +366,33 @@ function decodeHostServiceArgs(
|
|
|
254
366
|
index += 2;
|
|
255
367
|
return;
|
|
256
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
|
+
}
|
|
257
385
|
const rawValue = rawArgs[index];
|
|
258
386
|
if (type === "bool") {
|
|
259
387
|
decodedArgs.push(expectNumber(rawValue, context) !== 0);
|
|
260
388
|
} else if (type === "i32") {
|
|
261
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));
|
|
262
396
|
} else if (type === "f64") {
|
|
263
397
|
decodedArgs.push(expectNumber(rawValue, context));
|
|
264
398
|
} else {
|
|
@@ -272,10 +406,10 @@ function decodeHostServiceArgs(
|
|
|
272
406
|
export function createHostServiceImportModule(
|
|
273
407
|
services: HostServicesDefinition | undefined,
|
|
274
408
|
io: HostServiceImportIo,
|
|
275
|
-
): Record<string, (...rawArgs: Array<unknown>) => number | void> {
|
|
276
|
-
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> = {};
|
|
277
411
|
for (const method of listHostServiceMethods(services)) {
|
|
278
|
-
module[method.importName] = (...rawArgs: Array<unknown>): number | void => {
|
|
412
|
+
module[method.importName] = (...rawArgs: Array<unknown>): number | bigint | void => {
|
|
279
413
|
const decodedArgs = decodeHostServiceArgs(method, rawArgs, io);
|
|
280
414
|
const result = method.implementation(...decodedArgs);
|
|
281
415
|
const resultContext = `Host service ${method.serviceName}.${method.methodName} result`;
|
|
@@ -300,18 +434,45 @@ export function createHostServiceImportModule(
|
|
|
300
434
|
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
301
435
|
return io.writeBytes(ptr, capacity, typedArrayBytes(expectInt32Array(result, resultContext)), resultContext);
|
|
302
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
|
+
}
|
|
303
443
|
if (method.returns === "f64_array") {
|
|
304
444
|
const outputIndex = consumedRawArgCount(method);
|
|
305
445
|
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
306
446
|
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
307
447
|
return io.writeBytes(ptr, capacity, typedArrayBytes(expectFloat64Array(result, resultContext)), resultContext);
|
|
308
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
|
+
}
|
|
309
461
|
if (method.returns === "bool") {
|
|
310
462
|
return expectBoolean(result, resultContext) ? 1 : 0;
|
|
311
463
|
}
|
|
312
464
|
if (method.returns === "i32") {
|
|
313
465
|
return expectI32(result, resultContext);
|
|
314
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
|
+
}
|
|
315
476
|
if (method.returns === "f64") {
|
|
316
477
|
return expectNumber(result, resultContext);
|
|
317
478
|
}
|
package/package.json
CHANGED
package/scripts/build-demo-as.sh
CHANGED
|
@@ -11,7 +11,7 @@ HOST_SERVICE_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-services.mjs"
|
|
|
11
11
|
HOST_EVENT_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-events.mjs"
|
|
12
12
|
BUILD_TARGET="${1:-all}"
|
|
13
13
|
|
|
14
|
-
mkdir -p "${PACKAGE_DIR}/build" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/generated"
|
|
14
|
+
mkdir -p "${PACKAGE_DIR}/build" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/host/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/host/generated"
|
|
15
15
|
cd "${PACKAGE_DIR}"
|
|
16
16
|
|
|
17
17
|
build_demo_app() {
|
|
@@ -30,6 +30,7 @@ generate_host_services() {
|
|
|
30
30
|
local export_name="$2"
|
|
31
31
|
local output_file="$3"
|
|
32
32
|
local primitives_import="${4:-}"
|
|
33
|
+
local host_import_module="${5:-}"
|
|
33
34
|
|
|
34
35
|
npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
|
|
35
36
|
--bundle \
|
|
@@ -39,8 +40,13 @@ generate_host_services() {
|
|
|
39
40
|
--packages=external \
|
|
40
41
|
--outfile="${HOST_SERVICE_GENERATOR_BUILD}"
|
|
41
42
|
|
|
42
|
-
if [ -n "${primitives_import}" ]; then
|
|
43
|
+
if [ -n "${primitives_import}" ] && [ -n "${host_import_module}" ]; then
|
|
44
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" \
|
|
45
|
+
"${definition_file}" "${export_name}" "${output_file}" "${primitives_import}" "${host_import_module}"
|
|
46
|
+
elif [ -n "${primitives_import}" ]; then
|
|
43
47
|
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
|
|
48
|
+
elif [ -n "${host_import_module}" ]; then
|
|
49
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "" "${host_import_module}"
|
|
44
50
|
else
|
|
45
51
|
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
|
|
46
52
|
fi
|
|
@@ -70,10 +76,11 @@ generate_host_events() {
|
|
|
70
76
|
generate_host_services "demo/src/host-services.ts" "demoHostServices" "demo/src/generated/HostServices.ts"
|
|
71
77
|
generate_host_events "demo/src/host-events.ts" "demoHostEvents" "demo/src/generated/HostEvents.ts"
|
|
72
78
|
generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "demo/src/generated/WorkerHostServices.ts"
|
|
73
|
-
generate_host_services "
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
generate_host_services "scripts/framework-host-services.ts" "frameworkHostServices" "src/core/generated/FrameworkHostServices.ts" "" "fui_host"
|
|
80
|
+
generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
|
|
81
|
+
generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
|
|
82
|
+
generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
|
|
83
|
+
generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
|
|
77
84
|
|
|
78
85
|
case "${BUILD_TARGET}" in
|
|
79
86
|
all)
|
package/scripts/build.sh
CHANGED
|
@@ -24,7 +24,7 @@ RUNTIME_CONFIG_FILE="effindom-runtime-config.js"
|
|
|
24
24
|
DEFAULT_MANIFEST_PATH="./runtime/dist/effindom.v2.manifest.json"
|
|
25
25
|
|
|
26
26
|
rm -rf "${OUT_DIR}"
|
|
27
|
-
mkdir -p "${PACKAGE_DIR}/build" "${OUT_DIR}" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${WORKER_BUILD_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/generated"
|
|
27
|
+
mkdir -p "${PACKAGE_DIR}/build" "${OUT_DIR}" "${DEMO_OUT_DIR}" "${MVC_OUT_DIR}" "${HELLO_OUT_DIR}" "${WORKER_BUILD_DIR}" "${PACKAGE_DIR}/templates/demo-mvc/src/host/generated" "${PACKAGE_DIR}/templates/demo-hello-world/src/host/generated"
|
|
28
28
|
|
|
29
29
|
cd "${PACKAGE_DIR}"
|
|
30
30
|
|
|
@@ -66,6 +66,7 @@ generate_host_services() {
|
|
|
66
66
|
local export_name="$2"
|
|
67
67
|
local output_file="$3"
|
|
68
68
|
local primitives_import="${4:-}"
|
|
69
|
+
local host_import_module="${5:-}"
|
|
69
70
|
|
|
70
71
|
npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
|
|
71
72
|
--bundle \
|
|
@@ -75,8 +76,13 @@ generate_host_services() {
|
|
|
75
76
|
--packages=external \
|
|
76
77
|
--outfile="${HOST_SERVICE_GENERATOR_BUILD}"
|
|
77
78
|
|
|
78
|
-
if [ -n "${primitives_import}" ]; then
|
|
79
|
+
if [ -n "${primitives_import}" ] && [ -n "${host_import_module}" ]; then
|
|
80
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" \
|
|
81
|
+
"${definition_file}" "${export_name}" "${output_file}" "${primitives_import}" "${host_import_module}"
|
|
82
|
+
elif [ -n "${primitives_import}" ]; then
|
|
79
83
|
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
|
|
84
|
+
elif [ -n "${host_import_module}" ]; then
|
|
85
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "" "${host_import_module}"
|
|
80
86
|
else
|
|
81
87
|
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
|
|
82
88
|
fi
|
|
@@ -246,10 +252,11 @@ copy_worker_assets() {
|
|
|
246
252
|
generate_host_services "demo/src/host-services.ts" "demoHostServices" "demo/src/generated/HostServices.ts"
|
|
247
253
|
generate_host_events "demo/src/host-events.ts" "demoHostEvents" "demo/src/generated/HostEvents.ts"
|
|
248
254
|
generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "demo/src/generated/WorkerHostServices.ts"
|
|
249
|
-
generate_host_services "
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
generate_host_services "scripts/framework-host-services.ts" "frameworkHostServices" "src/core/generated/FrameworkHostServices.ts" "" "fui_host"
|
|
256
|
+
generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
|
|
257
|
+
generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
|
|
258
|
+
generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/host/generated/HostServices.ts" "../../fui/FuiPrimitives"
|
|
259
|
+
generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/host/generated/HostEvents.ts" "../../fui/FuiPrimitives"
|
|
253
260
|
|
|
254
261
|
build_app "tests/fixtures/smoke/app.ts" "${OUT_DIR}/app.wasm"
|
|
255
262
|
build_app "demo/src/dashboard.ts" "${DEMO_OUT_DIR}/demo.wasm"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineHostServices, hostService } from "../browser/src/host-services";
|
|
2
|
+
|
|
3
|
+
function unsupported(): never {
|
|
4
|
+
throw new Error("Framework host service definitions are generator metadata only.");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const frameworkHostServices = defineHostServices({
|
|
8
|
+
frameworkHost: {
|
|
9
|
+
nowMs: hostService({
|
|
10
|
+
importName: "fui_now_ms",
|
|
11
|
+
args: [] as const,
|
|
12
|
+
returns: "f64",
|
|
13
|
+
implementation: () => unsupported(),
|
|
14
|
+
}),
|
|
15
|
+
isDarkMode: hostService({
|
|
16
|
+
importName: "fui_is_dark_mode",
|
|
17
|
+
args: [] as const,
|
|
18
|
+
returns: "bool",
|
|
19
|
+
implementation: () => unsupported(),
|
|
20
|
+
}),
|
|
21
|
+
accentColor: hostService({
|
|
22
|
+
importName: "fui_get_accent_color",
|
|
23
|
+
args: [] as const,
|
|
24
|
+
returns: "u32",
|
|
25
|
+
implementation: () => unsupported(),
|
|
26
|
+
}),
|
|
27
|
+
platformFamily: hostService({
|
|
28
|
+
importName: "fui_get_platform_family",
|
|
29
|
+
args: [] as const,
|
|
30
|
+
returns: "u32",
|
|
31
|
+
implementation: () => unsupported(),
|
|
32
|
+
}),
|
|
33
|
+
isCoarsePointer: hostService({
|
|
34
|
+
importName: "fui_is_coarse_pointer",
|
|
35
|
+
args: [] as const,
|
|
36
|
+
returns: "bool",
|
|
37
|
+
implementation: () => unsupported(),
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -22,6 +22,11 @@ function relativeImport(fromFile: string, targetFile: string): string {
|
|
|
22
22
|
return toPosix(relative);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function sourcePathForHeader(sourceModulePath: string): string {
|
|
26
|
+
const relative = path.relative(process.cwd(), sourceModulePath);
|
|
27
|
+
return toPosix(relative.startsWith(".") ? relative : `./${relative}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
function asTypeName(type: HostServiceTypeName): string {
|
|
26
31
|
switch (type) {
|
|
27
32
|
case "string":
|
|
@@ -30,12 +35,24 @@ function asTypeName(type: HostServiceTypeName): string {
|
|
|
30
35
|
return "bool";
|
|
31
36
|
case "i32":
|
|
32
37
|
return "i32";
|
|
38
|
+
case "u32":
|
|
39
|
+
return "u32";
|
|
40
|
+
case "i64":
|
|
41
|
+
return "i64";
|
|
42
|
+
case "u64":
|
|
43
|
+
return "u64";
|
|
33
44
|
case "f64":
|
|
34
45
|
return "f64";
|
|
35
46
|
case "bytes":
|
|
36
47
|
return "Uint8Array";
|
|
37
48
|
case "i32_array":
|
|
38
49
|
return "Int32Array";
|
|
50
|
+
case "u32_array":
|
|
51
|
+
return "Uint32Array";
|
|
52
|
+
case "i64_array":
|
|
53
|
+
return "Int64Array";
|
|
54
|
+
case "u64_array":
|
|
55
|
+
return "Uint64Array";
|
|
39
56
|
case "f64_array":
|
|
40
57
|
return "Float64Array";
|
|
41
58
|
case "void":
|
|
@@ -59,7 +76,15 @@ function callbackTypeFor(method: NormalizedHostEventMethod): string {
|
|
|
59
76
|
function emitExportArgs(method: NormalizedHostEventMethod): string {
|
|
60
77
|
const parts: Array<string> = [];
|
|
61
78
|
method.args.forEach((type, index) => {
|
|
62
|
-
if (
|
|
79
|
+
if (
|
|
80
|
+
type === "string" ||
|
|
81
|
+
type === "bytes" ||
|
|
82
|
+
type === "i32_array" ||
|
|
83
|
+
type === "u32_array" ||
|
|
84
|
+
type === "i64_array" ||
|
|
85
|
+
type === "u64_array" ||
|
|
86
|
+
type === "f64_array"
|
|
87
|
+
) {
|
|
63
88
|
parts.push(`arg${String(index)}Ptr: usize`, `arg${String(index)}Len: u32`);
|
|
64
89
|
return;
|
|
65
90
|
}
|
|
@@ -91,6 +116,27 @@ function emitDecodedArgs(method: NormalizedHostEventMethod): Array<string> {
|
|
|
91
116
|
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
92
117
|
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
|
|
93
118
|
lines.push(" }");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (type === "u32_array") {
|
|
122
|
+
lines.push(` const arg${String(index)} = new Uint32Array(<i32>arg${String(index)}Len);`);
|
|
123
|
+
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
124
|
+
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 2);`);
|
|
125
|
+
lines.push(" }");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (type === "i64_array") {
|
|
129
|
+
lines.push(` const arg${String(index)} = new Int64Array(<i32>arg${String(index)}Len);`);
|
|
130
|
+
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
131
|
+
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
|
|
132
|
+
lines.push(" }");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (type === "u64_array") {
|
|
136
|
+
lines.push(` const arg${String(index)} = new Uint64Array(<i32>arg${String(index)}Len);`);
|
|
137
|
+
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
138
|
+
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
|
|
139
|
+
lines.push(" }");
|
|
94
140
|
}
|
|
95
141
|
return;
|
|
96
142
|
}
|
|
@@ -171,7 +217,7 @@ function emitBindingsFile(
|
|
|
171
217
|
): string {
|
|
172
218
|
const callbackImport = primitivesImportOverride ?? relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
|
|
173
219
|
const blocks: Array<string> = [
|
|
174
|
-
`// Generated by scripts/generate-host-events.ts from ${
|
|
220
|
+
`// Generated by scripts/generate-host-events.ts from ${sourcePathForHeader(sourceModulePath)}#${exportName}.`,
|
|
175
221
|
`import { Callback0, Callback1, Callback2 } from "${callbackImport}";`,
|
|
176
222
|
"",
|
|
177
223
|
];
|
|
@@ -21,6 +21,11 @@ function relativeImport(fromFile: string, targetFile: string): string {
|
|
|
21
21
|
return toPosix(relative);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function sourcePathForHeader(sourceModulePath: string): string {
|
|
25
|
+
const relative = path.relative(process.cwd(), sourceModulePath);
|
|
26
|
+
return toPosix(relative.startsWith(".") ? relative : `./${relative}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
function asTypeName(type: HostServiceTypeName): string {
|
|
25
30
|
switch (type) {
|
|
26
31
|
case "string":
|
|
@@ -29,12 +34,24 @@ function asTypeName(type: HostServiceTypeName): string {
|
|
|
29
34
|
return "bool";
|
|
30
35
|
case "i32":
|
|
31
36
|
return "i32";
|
|
37
|
+
case "u32":
|
|
38
|
+
return "u32";
|
|
39
|
+
case "i64":
|
|
40
|
+
return "i64";
|
|
41
|
+
case "u64":
|
|
42
|
+
return "u64";
|
|
32
43
|
case "f64":
|
|
33
44
|
return "f64";
|
|
34
45
|
case "bytes":
|
|
35
46
|
return "Uint8Array";
|
|
36
47
|
case "i32_array":
|
|
37
48
|
return "Int32Array";
|
|
49
|
+
case "u32_array":
|
|
50
|
+
return "Uint32Array";
|
|
51
|
+
case "i64_array":
|
|
52
|
+
return "Int64Array";
|
|
53
|
+
case "u64_array":
|
|
54
|
+
return "Uint64Array";
|
|
38
55
|
case "f64_array":
|
|
39
56
|
return "Float64Array";
|
|
40
57
|
case "void":
|
|
@@ -43,14 +60,31 @@ function asTypeName(type: HostServiceTypeName): string {
|
|
|
43
60
|
}
|
|
44
61
|
|
|
45
62
|
function isPointerLengthType(type: HostServiceTypeName): boolean {
|
|
46
|
-
return type === "string" ||
|
|
63
|
+
return type === "string" ||
|
|
64
|
+
type === "bytes" ||
|
|
65
|
+
type === "i32_array" ||
|
|
66
|
+
type === "u32_array" ||
|
|
67
|
+
type === "i64_array" ||
|
|
68
|
+
type === "u64_array" ||
|
|
69
|
+
type === "f64_array";
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
function returnsBufferType(type: HostServiceTypeName): boolean {
|
|
50
|
-
return type === "string" ||
|
|
73
|
+
return type === "string" ||
|
|
74
|
+
type === "bytes" ||
|
|
75
|
+
type === "i32_array" ||
|
|
76
|
+
type === "u32_array" ||
|
|
77
|
+
type === "i64_array" ||
|
|
78
|
+
type === "u64_array" ||
|
|
79
|
+
type === "f64_array";
|
|
51
80
|
}
|
|
52
81
|
|
|
53
|
-
function emitExternalSignature(
|
|
82
|
+
function emitExternalSignature(
|
|
83
|
+
importName: string,
|
|
84
|
+
args: readonly HostServiceTypeName[],
|
|
85
|
+
returns: HostServiceTypeName,
|
|
86
|
+
moduleName: string,
|
|
87
|
+
): string {
|
|
54
88
|
const signatureParts: Array<string> = [];
|
|
55
89
|
args.forEach((type, index) => {
|
|
56
90
|
if (isPointerLengthType(type)) {
|
|
@@ -64,7 +98,7 @@ function emitExternalSignature(importName: string, args: readonly HostServiceTyp
|
|
|
64
98
|
}
|
|
65
99
|
const returnType = returnsBufferType(returns) ? "u32" : asTypeName(returns);
|
|
66
100
|
return [
|
|
67
|
-
`@external("
|
|
101
|
+
`@external("${moduleName}", "${importName}")`,
|
|
68
102
|
`declare function __host_${importName}(${signatureParts.join(", ")}): ${returnType};`,
|
|
69
103
|
].join("\n");
|
|
70
104
|
}
|
|
@@ -77,7 +111,7 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
|
|
|
77
111
|
lines.push(` const arg${String(index)}Bytes = Uint8Array.wrap(String.UTF8.encode(arg${String(index)}, false));`);
|
|
78
112
|
return;
|
|
79
113
|
}
|
|
80
|
-
if (type === "bytes" || type === "i32_array" || type === "f64_array") {
|
|
114
|
+
if (type === "bytes" || type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
|
|
81
115
|
lines.push(` const arg${String(index)}Bytes = arg${String(index)};`);
|
|
82
116
|
}
|
|
83
117
|
});
|
|
@@ -93,7 +127,7 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
|
|
|
93
127
|
callArgs.push(`<u32>arg${String(index)}Bytes.length`);
|
|
94
128
|
return;
|
|
95
129
|
}
|
|
96
|
-
if (type === "i32_array" || type === "f64_array") {
|
|
130
|
+
if (type === "i32_array" || type === "u32_array" || type === "i64_array" || type === "u64_array" || type === "f64_array") {
|
|
97
131
|
callArgs.push(`arg${String(index)}Bytes.length > 0 ? arg${String(index)}Bytes.dataStart : 0`);
|
|
98
132
|
callArgs.push(`<u32>arg${String(index)}Bytes.length`);
|
|
99
133
|
return;
|
|
@@ -111,6 +145,12 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
|
|
|
111
145
|
lines.push(` return decodeHostServiceBytesResult(resultPtr, resultLen, "${importName}");`);
|
|
112
146
|
} else if (returns === "i32_array") {
|
|
113
147
|
lines.push(` return decodeHostServiceI32ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
148
|
+
} else if (returns === "u32_array") {
|
|
149
|
+
lines.push(` return decodeHostServiceU32ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
150
|
+
} else if (returns === "i64_array") {
|
|
151
|
+
lines.push(` return decodeHostServiceI64ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
152
|
+
} else if (returns === "u64_array") {
|
|
153
|
+
lines.push(` return decodeHostServiceU64ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
114
154
|
} else {
|
|
115
155
|
lines.push(` return decodeHostServiceF64ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
116
156
|
}
|
|
@@ -155,10 +195,11 @@ function emitBindingsFile(
|
|
|
155
195
|
outputPath: string,
|
|
156
196
|
methods: ReturnType<typeof listHostServiceMethods>,
|
|
157
197
|
primitivesImportOverride: string | undefined,
|
|
198
|
+
moduleName: string,
|
|
158
199
|
): string {
|
|
159
200
|
const runtimeImport = primitivesImportOverride ?? relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
|
|
160
201
|
const blocks: Array<string> = [
|
|
161
|
-
`// Generated by scripts/generate-host-services.ts from ${
|
|
202
|
+
`// Generated by scripts/generate-host-services.ts from ${sourcePathForHeader(sourceModulePath)}#${exportName}.`,
|
|
162
203
|
];
|
|
163
204
|
if (methods.some((method) => returnsBufferType(method.returns))) {
|
|
164
205
|
const helpers = [
|
|
@@ -178,6 +219,15 @@ function emitBindingsFile(
|
|
|
178
219
|
if (type === "i32_array") {
|
|
179
220
|
return "decodeHostServiceI32ArrayResult";
|
|
180
221
|
}
|
|
222
|
+
if (type === "u32_array") {
|
|
223
|
+
return "decodeHostServiceU32ArrayResult";
|
|
224
|
+
}
|
|
225
|
+
if (type === "i64_array") {
|
|
226
|
+
return "decodeHostServiceI64ArrayResult";
|
|
227
|
+
}
|
|
228
|
+
if (type === "u64_array") {
|
|
229
|
+
return "decodeHostServiceU64ArrayResult";
|
|
230
|
+
}
|
|
181
231
|
return "decodeHostServiceF64ArrayResult";
|
|
182
232
|
}),
|
|
183
233
|
),
|
|
@@ -188,7 +238,7 @@ function emitBindingsFile(
|
|
|
188
238
|
blocks.push("");
|
|
189
239
|
}
|
|
190
240
|
methods.forEach((method, index) => {
|
|
191
|
-
blocks.push(emitExternalSignature(method.importName, method.args, method.returns));
|
|
241
|
+
blocks.push(emitExternalSignature(method.importName, method.args, method.returns, moduleName));
|
|
192
242
|
blocks.push("");
|
|
193
243
|
blocks.push(emitWrapper(method.importName, method.args, method.returns));
|
|
194
244
|
if (index + 1 < methods.length) {
|
|
@@ -199,15 +249,24 @@ function emitBindingsFile(
|
|
|
199
249
|
}
|
|
200
250
|
|
|
201
251
|
async function main(): Promise<void> {
|
|
202
|
-
const [moduleArg, exportName, outputArg, primitivesImportArg] = process.argv.slice(2);
|
|
252
|
+
const [moduleArg, exportName, outputArg, primitivesImportArg, hostModuleArg] = process.argv.slice(2);
|
|
203
253
|
if (moduleArg === undefined || exportName === undefined || outputArg === undefined) {
|
|
204
|
-
throw new Error(
|
|
254
|
+
throw new Error(
|
|
255
|
+
"Usage: generate-host-services <module-path> <export-name> <output-path> [primitives-import] [host-import-module]",
|
|
256
|
+
);
|
|
205
257
|
}
|
|
206
258
|
const modulePath = path.resolve(process.cwd(), moduleArg);
|
|
207
259
|
const outputPath = path.resolve(process.cwd(), outputArg);
|
|
208
260
|
const registry = await loadHostServices(modulePath, exportName);
|
|
209
261
|
const methods = listHostServiceMethods(registry as never);
|
|
210
|
-
const content = emitBindingsFile(
|
|
262
|
+
const content = emitBindingsFile(
|
|
263
|
+
modulePath,
|
|
264
|
+
exportName,
|
|
265
|
+
outputPath,
|
|
266
|
+
methods,
|
|
267
|
+
primitivesImportArg,
|
|
268
|
+
hostModuleArg ?? "fui_host_service",
|
|
269
|
+
);
|
|
211
270
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
212
271
|
await fs.writeFile(outputPath, content, "utf8");
|
|
213
272
|
}
|
package/src/FuiPrimitives.ts
CHANGED
|
@@ -15,8 +15,11 @@ export { clearCurrentSelection, tryGetBounds } from "./bindings/ui";
|
|
|
15
15
|
export {
|
|
16
16
|
decodeHostServiceBytesResult,
|
|
17
17
|
decodeHostServiceF64ArrayResult,
|
|
18
|
+
decodeHostServiceI64ArrayResult,
|
|
18
19
|
decodeHostServiceI32ArrayResult,
|
|
19
20
|
decodeHostServiceStringResult,
|
|
21
|
+
decodeHostServiceU64ArrayResult,
|
|
22
|
+
decodeHostServiceU32ArrayResult,
|
|
20
23
|
hostServiceResultBufferPtr,
|
|
21
24
|
hostServiceResultBufferSize,
|
|
22
25
|
} from "./host-services/runtime";
|
package/src/core/ffi.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
fui_get_accent_color,
|
|
3
|
+
fui_get_platform_family,
|
|
4
|
+
fui_is_coarse_pointer,
|
|
5
|
+
fui_is_dark_mode,
|
|
6
|
+
fui_now_ms,
|
|
7
|
+
} from "./generated/FrameworkHostServices";
|
|
8
|
+
|
|
1
9
|
export enum HandleValue {
|
|
2
10
|
Invalid = 0,
|
|
3
11
|
}
|
|
@@ -569,24 +577,9 @@ export declare function fui_start_timer(timerId: u32, delayMs: i32): void;
|
|
|
569
577
|
@external("fui_host", "fui_cancel_timer")
|
|
570
578
|
export declare function fui_cancel_timer(timerId: u32): void;
|
|
571
579
|
|
|
572
|
-
@external("fui_host", "fui_now_ms")
|
|
573
|
-
export declare function fui_now_ms(): f64;
|
|
574
|
-
|
|
575
580
|
@external("fui_host", "fui_set_cursor")
|
|
576
581
|
export declare function fui_set_cursor(style: u32): void;
|
|
577
582
|
|
|
578
|
-
@external("fui_host", "fui_is_dark_mode")
|
|
579
|
-
export declare function fui_is_dark_mode(): bool;
|
|
580
|
-
|
|
581
|
-
@external("fui_host", "fui_get_accent_color")
|
|
582
|
-
export declare function fui_get_accent_color(): u32;
|
|
583
|
-
|
|
584
|
-
@external("fui_host", "fui_get_platform_family")
|
|
585
|
-
export declare function fui_get_platform_family(): u32;
|
|
586
|
-
|
|
587
|
-
@external("fui_host", "fui_is_coarse_pointer")
|
|
588
|
-
export declare function fui_is_coarse_pointer(): bool;
|
|
589
|
-
|
|
590
583
|
@external("fui_host", "fui_show_url_preview")
|
|
591
584
|
export declare function fui_show_url_preview(ptr: usize, len: u32): void;
|
|
592
585
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Generated by scripts/generate-host-services.ts from ./scripts/framework-host-services.ts#frameworkHostServices.
|
|
2
|
+
|
|
3
|
+
@external("fui_host", "fui_get_accent_color")
|
|
4
|
+
declare function __host_fui_get_accent_color(): u32;
|
|
5
|
+
|
|
6
|
+
export function fui_get_accent_color(): u32 {
|
|
7
|
+
return __host_fui_get_accent_color();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@external("fui_host", "fui_get_platform_family")
|
|
11
|
+
declare function __host_fui_get_platform_family(): u32;
|
|
12
|
+
|
|
13
|
+
export function fui_get_platform_family(): u32 {
|
|
14
|
+
return __host_fui_get_platform_family();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@external("fui_host", "fui_is_coarse_pointer")
|
|
18
|
+
declare function __host_fui_is_coarse_pointer(): bool;
|
|
19
|
+
|
|
20
|
+
export function fui_is_coarse_pointer(): bool {
|
|
21
|
+
return __host_fui_is_coarse_pointer();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@external("fui_host", "fui_is_dark_mode")
|
|
25
|
+
declare function __host_fui_is_dark_mode(): bool;
|
|
26
|
+
|
|
27
|
+
export function fui_is_dark_mode(): bool {
|
|
28
|
+
return __host_fui_is_dark_mode();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@external("fui_host", "fui_now_ms")
|
|
32
|
+
declare function __host_fui_now_ms(): f64;
|
|
33
|
+
|
|
34
|
+
export function fui_now_ms(): f64 {
|
|
35
|
+
return __host_fui_now_ms();
|
|
36
|
+
}
|
|
@@ -49,6 +49,42 @@ export function decodeHostServiceI32ArrayResult(resultPtr: usize, resultLen: u32
|
|
|
49
49
|
return values;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export function decodeHostServiceU32ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Uint32Array {
|
|
53
|
+
assertResultByteLength(resultLen, importName);
|
|
54
|
+
if ((resultLen & 3) != 0) {
|
|
55
|
+
throw new Error("Host service " + importName + " returned misaligned Uint32Array byte length.");
|
|
56
|
+
}
|
|
57
|
+
const values = new Uint32Array(<i32>(resultLen >> 2));
|
|
58
|
+
if (resultLen > 0) {
|
|
59
|
+
memory.copy(values.dataStart, resultPtr, <usize>resultLen);
|
|
60
|
+
}
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function decodeHostServiceI64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Int64Array {
|
|
65
|
+
assertResultByteLength(resultLen, importName);
|
|
66
|
+
if ((resultLen & 7) != 0) {
|
|
67
|
+
throw new Error("Host service " + importName + " returned misaligned Int64Array byte length.");
|
|
68
|
+
}
|
|
69
|
+
const values = new Int64Array(<i32>(resultLen >> 3));
|
|
70
|
+
if (resultLen > 0) {
|
|
71
|
+
memory.copy(values.dataStart, resultPtr, <usize>resultLen);
|
|
72
|
+
}
|
|
73
|
+
return values;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function decodeHostServiceU64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Uint64Array {
|
|
77
|
+
assertResultByteLength(resultLen, importName);
|
|
78
|
+
if ((resultLen & 7) != 0) {
|
|
79
|
+
throw new Error("Host service " + importName + " returned misaligned Uint64Array byte length.");
|
|
80
|
+
}
|
|
81
|
+
const values = new Uint64Array(<i32>(resultLen >> 3));
|
|
82
|
+
if (resultLen > 0) {
|
|
83
|
+
memory.copy(values.dataStart, resultPtr, <usize>resultLen);
|
|
84
|
+
}
|
|
85
|
+
return values;
|
|
86
|
+
}
|
|
87
|
+
|
|
52
88
|
export function decodeHostServiceF64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Float64Array {
|
|
53
89
|
assertResultByteLength(resultLen, importName);
|
|
54
90
|
if ((resultLen & 7) != 0) {
|