@effindomv2/fui-as 0.1.1 → 0.1.2
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 +66 -0
- package/browser/src/host-events.ts +12 -1
- package/browser/src/host-services.ts +125 -6
- package/browser/src/worker-bootstrap.ts +27 -0
- package/package.json +1 -1
- package/scripts/build-demo-as.sh +29 -6
- package/scripts/build.sh +59 -6
- package/scripts/generate-host-events.ts +32 -5
- package/scripts/generate-host-services.ts +69 -11
- package/src/FuiPrimitives.ts +8 -1
- package/src/host-services/runtime.ts +38 -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,41 @@ 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" | "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 (!(arg instanceof Float64Array)) {
|
|
522
|
+
throw new Error(`${context} must be a Float64Array.`);
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
bytes: new Uint8Array(arg.buffer, arg.byteOffset, arg.byteLength),
|
|
526
|
+
elementCount: arg.length,
|
|
527
|
+
alignment: 8,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
483
531
|
if (args.length != method.args.length) {
|
|
484
532
|
throw new Error(`Host event ${method.serviceName}.${method.methodName} expected ${String(method.args.length)} args but received ${String(args.length)}.`);
|
|
485
533
|
}
|
|
@@ -507,6 +555,22 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
507
555
|
}
|
|
508
556
|
continue;
|
|
509
557
|
}
|
|
558
|
+
if (type === "bytes" || type === "i32_array" || type === "f64_array") {
|
|
559
|
+
const payload = encodeTypedArrayArg(type, arg, context);
|
|
560
|
+
if (payload.bytes.length > 0) {
|
|
561
|
+
const alignedOffset = alignOffset(byteOffset, payload.alignment);
|
|
562
|
+
if (session.textBufferPtr === 0 || alignedOffset + payload.bytes.length > session.textBufferSize) {
|
|
563
|
+
throw new Error(`${context} exceeds the shared AssemblyScript text buffer.`);
|
|
564
|
+
}
|
|
565
|
+
const memory = new Uint8Array(session.memory.buffer, session.textBufferPtr + alignedOffset, payload.bytes.length);
|
|
566
|
+
memory.set(payload.bytes);
|
|
567
|
+
callArgs.push(session.textBufferPtr + alignedOffset, payload.elementCount);
|
|
568
|
+
byteOffset = alignedOffset + payload.bytes.length;
|
|
569
|
+
} else {
|
|
570
|
+
callArgs.push(0, 0);
|
|
571
|
+
}
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
510
574
|
if (type === 'bool') {
|
|
511
575
|
if (typeof arg !== 'boolean') {
|
|
512
576
|
throw new Error(`${context} must be a boolean.`);
|
|
@@ -703,6 +767,8 @@ export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
|
703
767
|
const hostServiceImports = createHostServiceImportModule(hostServices, {
|
|
704
768
|
readString: readAppUtf8,
|
|
705
769
|
writeString: writeAppUtf8,
|
|
770
|
+
readBytes: readAppBytes,
|
|
771
|
+
writeBytes: writeAppBytes,
|
|
706
772
|
});
|
|
707
773
|
return {
|
|
708
774
|
effindom_v2_ui: createUiImportModule({
|
|
@@ -3,6 +3,9 @@ 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 "f64_array" ? Float64Array :
|
|
6
9
|
T extends "void" ? void :
|
|
7
10
|
number;
|
|
8
11
|
|
|
@@ -59,7 +62,15 @@ function buildExportName(eventName: string): string {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
function validateEventType(type: string, context: string): asserts type is HostServiceTypeName {
|
|
62
|
-
if (
|
|
65
|
+
if (
|
|
66
|
+
type === "string" ||
|
|
67
|
+
type === "bool" ||
|
|
68
|
+
type === "i32" ||
|
|
69
|
+
type === "f64" ||
|
|
70
|
+
type === "bytes" ||
|
|
71
|
+
type === "i32_array" ||
|
|
72
|
+
type === "f64_array"
|
|
73
|
+
) {
|
|
63
74
|
return;
|
|
64
75
|
}
|
|
65
76
|
throw new Error(`${context} uses unsupported host-event type "${type}".`);
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
export type HostServiceTypeName =
|
|
1
|
+
export type HostServiceTypeName =
|
|
2
|
+
| "string"
|
|
3
|
+
| "bool"
|
|
4
|
+
| "i32"
|
|
5
|
+
| "f64"
|
|
6
|
+
| "bytes"
|
|
7
|
+
| "i32_array"
|
|
8
|
+
| "f64_array"
|
|
9
|
+
| "void";
|
|
2
10
|
|
|
3
11
|
type HostServiceTypeValue<T extends HostServiceTypeName> =
|
|
4
12
|
T extends "string" ? string :
|
|
5
13
|
T extends "bool" ? boolean :
|
|
14
|
+
T extends "bytes" ? Uint8Array :
|
|
15
|
+
T extends "i32_array" ? Int32Array :
|
|
16
|
+
T extends "f64_array" ? Float64Array :
|
|
6
17
|
T extends "void" ? void :
|
|
7
18
|
number;
|
|
8
19
|
|
|
@@ -33,6 +44,8 @@ export interface NormalizedHostServiceMethod {
|
|
|
33
44
|
export interface HostServiceImportIo {
|
|
34
45
|
readString(ptr: number, len: number): string;
|
|
35
46
|
writeString(ptr: number, capacity: number, text: string, context: string): number;
|
|
47
|
+
readBytes(ptr: number, len: number): Uint8Array;
|
|
48
|
+
writeBytes(ptr: number, capacity: number, bytes: Uint8Array, context: string): number;
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -63,7 +76,16 @@ function buildImportName(serviceName: string, methodName: string): string {
|
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
function validateServiceType(type: string, context: string): asserts type is HostServiceTypeName {
|
|
66
|
-
if (
|
|
79
|
+
if (
|
|
80
|
+
type === "string" ||
|
|
81
|
+
type === "bool" ||
|
|
82
|
+
type === "i32" ||
|
|
83
|
+
type === "f64" ||
|
|
84
|
+
type === "bytes" ||
|
|
85
|
+
type === "i32_array" ||
|
|
86
|
+
type === "f64_array" ||
|
|
87
|
+
type === "void"
|
|
88
|
+
) {
|
|
67
89
|
return;
|
|
68
90
|
}
|
|
69
91
|
throw new Error(`${context} uses unsupported host-service type "${type}".`);
|
|
@@ -126,6 +148,27 @@ function expectString(value: unknown, context: string): string {
|
|
|
126
148
|
return value;
|
|
127
149
|
}
|
|
128
150
|
|
|
151
|
+
function expectBytes(value: unknown, context: string): Uint8Array {
|
|
152
|
+
if (!(value instanceof Uint8Array)) {
|
|
153
|
+
throw new Error(`${context} must be a Uint8Array.`);
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function expectInt32Array(value: unknown, context: string): Int32Array {
|
|
159
|
+
if (!(value instanceof Int32Array)) {
|
|
160
|
+
throw new Error(`${context} must be an Int32Array.`);
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function expectFloat64Array(value: unknown, context: string): Float64Array {
|
|
166
|
+
if (!(value instanceof Float64Array)) {
|
|
167
|
+
throw new Error(`${context} must be a Float64Array.`);
|
|
168
|
+
}
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
|
|
129
172
|
function expectI32(value: unknown, context: string): number {
|
|
130
173
|
const numberValue = expectNumber(value, context);
|
|
131
174
|
if (!Number.isInteger(numberValue) || numberValue < -2147483648 || numberValue > 2147483647) {
|
|
@@ -134,6 +177,44 @@ function expectI32(value: unknown, context: string): number {
|
|
|
134
177
|
return numberValue;
|
|
135
178
|
}
|
|
136
179
|
|
|
180
|
+
function expectLength(value: unknown, context: string): number {
|
|
181
|
+
const length = expectNumber(value, context);
|
|
182
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
183
|
+
throw new Error(`${context} must be a non-negative integer.`);
|
|
184
|
+
}
|
|
185
|
+
return length;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function bytesToI32Array(bytes: Uint8Array, context: string): Int32Array {
|
|
189
|
+
if ((bytes.byteLength & 3) !== 0) {
|
|
190
|
+
throw new Error(`${context} payload length must be divisible by 4.`);
|
|
191
|
+
}
|
|
192
|
+
const values = new Int32Array(bytes.byteLength >>> 2);
|
|
193
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
194
|
+
return values;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function bytesToF64Array(bytes: Uint8Array, context: string): Float64Array {
|
|
198
|
+
if ((bytes.byteLength & 7) !== 0) {
|
|
199
|
+
throw new Error(`${context} payload length must be divisible by 8.`);
|
|
200
|
+
}
|
|
201
|
+
const values = new Float64Array(bytes.byteLength >>> 3);
|
|
202
|
+
new Uint8Array(values.buffer).set(bytes);
|
|
203
|
+
return values;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function typedArrayBytes(value: Uint8Array | Int32Array | Float64Array): Uint8Array {
|
|
207
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function consumedRawArgCount(method: NormalizedHostServiceMethod): number {
|
|
211
|
+
let count = 0;
|
|
212
|
+
method.args.forEach((type) => {
|
|
213
|
+
count += type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array" ? 2 : 1;
|
|
214
|
+
});
|
|
215
|
+
return count;
|
|
216
|
+
}
|
|
217
|
+
|
|
137
218
|
function decodeHostServiceArgs(
|
|
138
219
|
method: NormalizedHostServiceMethod,
|
|
139
220
|
rawArgs: readonly unknown[],
|
|
@@ -150,6 +231,29 @@ function decodeHostServiceArgs(
|
|
|
150
231
|
index += 2;
|
|
151
232
|
return;
|
|
152
233
|
}
|
|
234
|
+
if (type === "bytes") {
|
|
235
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
236
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
237
|
+
decodedArgs.push(len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len));
|
|
238
|
+
index += 2;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (type === "i32_array") {
|
|
242
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
243
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
244
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 2);
|
|
245
|
+
decodedArgs.push(bytesToI32Array(payload, context));
|
|
246
|
+
index += 2;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (type === "f64_array") {
|
|
250
|
+
const ptr = expectNumber(rawArgs[index], `${context} ptr`);
|
|
251
|
+
const len = expectLength(rawArgs[index + 1], `${context} len`);
|
|
252
|
+
const payload = len <= 0 ? new Uint8Array(0) : io.readBytes(ptr, len << 3);
|
|
253
|
+
decodedArgs.push(bytesToF64Array(payload, context));
|
|
254
|
+
index += 2;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
153
257
|
const rawValue = rawArgs[index];
|
|
154
258
|
if (type === "bool") {
|
|
155
259
|
decodedArgs.push(expectNumber(rawValue, context) !== 0);
|
|
@@ -179,14 +283,29 @@ export function createHostServiceImportModule(
|
|
|
179
283
|
return;
|
|
180
284
|
}
|
|
181
285
|
if (method.returns === "string") {
|
|
182
|
-
|
|
183
|
-
method.args.forEach((type) => {
|
|
184
|
-
outputIndex += type === "string" ? 2 : 1;
|
|
185
|
-
});
|
|
286
|
+
const outputIndex = consumedRawArgCount(method);
|
|
186
287
|
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
187
288
|
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
188
289
|
return io.writeString(ptr, capacity, expectString(result, resultContext), resultContext);
|
|
189
290
|
}
|
|
291
|
+
if (method.returns === "bytes") {
|
|
292
|
+
const outputIndex = consumedRawArgCount(method);
|
|
293
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
294
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
295
|
+
return io.writeBytes(ptr, capacity, expectBytes(result, resultContext), resultContext);
|
|
296
|
+
}
|
|
297
|
+
if (method.returns === "i32_array") {
|
|
298
|
+
const outputIndex = consumedRawArgCount(method);
|
|
299
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
300
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
301
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectInt32Array(result, resultContext)), resultContext);
|
|
302
|
+
}
|
|
303
|
+
if (method.returns === "f64_array") {
|
|
304
|
+
const outputIndex = consumedRawArgCount(method);
|
|
305
|
+
const ptr = expectNumber(rawArgs[outputIndex], `${resultContext} ptr`);
|
|
306
|
+
const capacity = expectNumber(rawArgs[outputIndex + 1], `${resultContext} capacity`);
|
|
307
|
+
return io.writeBytes(ptr, capacity, typedArrayBytes(expectFloat64Array(result, resultContext)), resultContext);
|
|
308
|
+
}
|
|
190
309
|
if (method.returns === "bool") {
|
|
191
310
|
return expectBoolean(result, resultContext) ? 1 : 0;
|
|
192
311
|
}
|
|
@@ -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
package/scripts/build-demo-as.sh
CHANGED
|
@@ -5,12 +5,13 @@ set -euo pipefail
|
|
|
5
5
|
PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
6
|
REPO_ROOT="$(cd "${PACKAGE_DIR}/../.." && pwd)"
|
|
7
7
|
DEMO_OUT_DIR="${REPO_ROOT}/public/v2/fui-as/demo"
|
|
8
|
+
MVC_OUT_DIR="${REPO_ROOT}/public/v2/fui-as/demo-mvc"
|
|
8
9
|
HELLO_OUT_DIR="${REPO_ROOT}/public/v2/fui-as/demo-hello-world"
|
|
9
10
|
HOST_SERVICE_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-services.mjs"
|
|
10
11
|
HOST_EVENT_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-events.mjs"
|
|
11
12
|
BUILD_TARGET="${1:-all}"
|
|
12
13
|
|
|
13
|
-
mkdir -p "${PACKAGE_DIR}/build" "${DEMO_OUT_DIR}" "${HELLO_OUT_DIR}"
|
|
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
15
|
cd "${PACKAGE_DIR}"
|
|
15
16
|
|
|
16
17
|
build_demo_app() {
|
|
@@ -28,6 +29,7 @@ generate_host_services() {
|
|
|
28
29
|
local definition_file="$1"
|
|
29
30
|
local export_name="$2"
|
|
30
31
|
local output_file="$3"
|
|
32
|
+
local primitives_import="${4:-}"
|
|
31
33
|
|
|
32
34
|
npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
|
|
33
35
|
--bundle \
|
|
@@ -37,13 +39,18 @@ generate_host_services() {
|
|
|
37
39
|
--packages=external \
|
|
38
40
|
--outfile="${HOST_SERVICE_GENERATOR_BUILD}"
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
if [ -n "${primitives_import}" ]; then
|
|
43
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
|
|
44
|
+
else
|
|
45
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
|
|
46
|
+
fi
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
generate_host_events() {
|
|
44
50
|
local definition_file="$1"
|
|
45
51
|
local export_name="$2"
|
|
46
52
|
local output_file="$3"
|
|
53
|
+
local primitives_import="${4:-}"
|
|
47
54
|
|
|
48
55
|
npx esbuild "${PACKAGE_DIR}/scripts/generate-host-events.ts" \
|
|
49
56
|
--bundle \
|
|
@@ -53,12 +60,20 @@ generate_host_events() {
|
|
|
53
60
|
--packages=external \
|
|
54
61
|
--outfile="${HOST_EVENT_GENERATOR_BUILD}"
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
if [ -n "${primitives_import}" ]; then
|
|
64
|
+
node "${HOST_EVENT_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
|
|
65
|
+
else
|
|
66
|
+
node "${HOST_EVENT_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
|
|
67
|
+
fi
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
generate_host_services "demo/src/host-services.ts" "demoHostServices" "demo/src/generated/HostServices.ts"
|
|
60
71
|
generate_host_events "demo/src/host-events.ts" "demoHostEvents" "demo/src/generated/HostEvents.ts"
|
|
61
72
|
generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "demo/src/generated/WorkerHostServices.ts"
|
|
73
|
+
generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/generated/HostServices.ts" "../FuiPrimitives"
|
|
74
|
+
generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/generated/HostEvents.ts" "../FuiPrimitives"
|
|
75
|
+
generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/generated/HostServices.ts" "../FuiPrimitives"
|
|
76
|
+
generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/generated/HostEvents.ts" "../FuiPrimitives"
|
|
62
77
|
|
|
63
78
|
case "${BUILD_TARGET}" in
|
|
64
79
|
all)
|
|
@@ -66,7 +81,9 @@ case "${BUILD_TARGET}" in
|
|
|
66
81
|
build_demo_app "demo/src/routes/demo_home.ts" "${DEMO_OUT_DIR}/home.wasm"
|
|
67
82
|
build_demo_app "demo/src/routes/demo_advanced_controls.ts" "${DEMO_OUT_DIR}/advanced-controls.wasm"
|
|
68
83
|
build_demo_app "demo/src/routes/templated-controls.ts" "${DEMO_OUT_DIR}/templated-controls.wasm"
|
|
69
|
-
build_demo_app "demo-
|
|
84
|
+
build_demo_app "templates/demo-mvc/src/routes/mvc_home.ts" "${MVC_OUT_DIR}/mvc-home.wasm"
|
|
85
|
+
build_demo_app "templates/demo-mvc/src/routes/mvc_settings.ts" "${MVC_OUT_DIR}/mvc-settings.wasm"
|
|
86
|
+
build_demo_app "templates/demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
|
|
70
87
|
;;
|
|
71
88
|
dashboard)
|
|
72
89
|
build_demo_app "demo/src/dashboard.ts" "${DEMO_OUT_DIR}/demo.wasm"
|
|
@@ -80,12 +97,18 @@ case "${BUILD_TARGET}" in
|
|
|
80
97
|
templated-controls|templated)
|
|
81
98
|
build_demo_app "demo/src/routes/templated-controls.ts" "${DEMO_OUT_DIR}/templated-controls.wasm"
|
|
82
99
|
;;
|
|
100
|
+
mvc-home|mvc-home-page)
|
|
101
|
+
build_demo_app "templates/demo-mvc/src/routes/mvc_home.ts" "${MVC_OUT_DIR}/mvc-home.wasm"
|
|
102
|
+
;;
|
|
103
|
+
mvc-settings|mvc-settings-page)
|
|
104
|
+
build_demo_app "templates/demo-mvc/src/routes/mvc_settings.ts" "${MVC_OUT_DIR}/mvc-settings.wasm"
|
|
105
|
+
;;
|
|
83
106
|
hello-world|hello)
|
|
84
|
-
build_demo_app "demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
|
|
107
|
+
build_demo_app "templates/demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
|
|
85
108
|
;;
|
|
86
109
|
*)
|
|
87
110
|
echo "Unknown build target: ${BUILD_TARGET}" >&2
|
|
88
|
-
echo "Usage: bash scripts/build-demo-as.sh [all|dashboard|home|advanced-controls|templated-controls|hello-world]" >&2
|
|
111
|
+
echo "Usage: bash scripts/build-demo-as.sh [all|dashboard|home|advanced-controls|templated-controls|mvc-home|mvc-settings|hello-world]" >&2
|
|
89
112
|
exit 1
|
|
90
113
|
;;
|
|
91
114
|
esac
|
package/scripts/build.sh
CHANGED
|
@@ -8,6 +8,7 @@ BROWSER_SRC_DIR="${PACKAGE_DIR}/browser/src"
|
|
|
8
8
|
SMOKE_FIXTURE_DIR="${PACKAGE_DIR}/tests/fixtures/smoke"
|
|
9
9
|
OUT_DIR="${REPO_ROOT}/public/v2/fui-as"
|
|
10
10
|
DEMO_OUT_DIR="${OUT_DIR}/demo"
|
|
11
|
+
MVC_OUT_DIR="${OUT_DIR}/demo-mvc"
|
|
11
12
|
HELLO_OUT_DIR="${OUT_DIR}/demo-hello-world"
|
|
12
13
|
WORKER_BUILD_DIR="${PACKAGE_DIR}/build/workers"
|
|
13
14
|
WORKER_BOOTSTRAP_BUILD="${PACKAGE_DIR}/build/worker-bootstrap.js"
|
|
@@ -23,7 +24,7 @@ RUNTIME_CONFIG_FILE="effindom-runtime-config.js"
|
|
|
23
24
|
DEFAULT_MANIFEST_PATH="./runtime/dist/effindom.v2.manifest.json"
|
|
24
25
|
|
|
25
26
|
rm -rf "${OUT_DIR}"
|
|
26
|
-
mkdir -p "${PACKAGE_DIR}/build" "${OUT_DIR}" "${DEMO_OUT_DIR}" "${HELLO_OUT_DIR}" "${WORKER_BUILD_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
28
|
|
|
28
29
|
cd "${PACKAGE_DIR}"
|
|
29
30
|
|
|
@@ -64,6 +65,7 @@ generate_host_services() {
|
|
|
64
65
|
local definition_file="$1"
|
|
65
66
|
local export_name="$2"
|
|
66
67
|
local output_file="$3"
|
|
68
|
+
local primitives_import="${4:-}"
|
|
67
69
|
|
|
68
70
|
npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
|
|
69
71
|
--bundle \
|
|
@@ -73,13 +75,18 @@ generate_host_services() {
|
|
|
73
75
|
--packages=external \
|
|
74
76
|
--outfile="${HOST_SERVICE_GENERATOR_BUILD}"
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
if [ -n "${primitives_import}" ]; then
|
|
79
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
|
|
80
|
+
else
|
|
81
|
+
node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
|
|
82
|
+
fi
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
generate_host_events() {
|
|
80
86
|
local definition_file="$1"
|
|
81
87
|
local export_name="$2"
|
|
82
88
|
local output_file="$3"
|
|
89
|
+
local primitives_import="${4:-}"
|
|
83
90
|
|
|
84
91
|
npx esbuild "${PACKAGE_DIR}/scripts/generate-host-events.ts" \
|
|
85
92
|
--bundle \
|
|
@@ -89,7 +96,11 @@ generate_host_events() {
|
|
|
89
96
|
--packages=external \
|
|
90
97
|
--outfile="${HOST_EVENT_GENERATOR_BUILD}"
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
if [ -n "${primitives_import}" ]; then
|
|
100
|
+
node "${HOST_EVENT_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${primitives_import}"
|
|
101
|
+
else
|
|
102
|
+
node "${HOST_EVENT_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
|
|
103
|
+
fi
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
find_worker_entries() {
|
|
@@ -162,6 +173,10 @@ copy_runtime_assets() {
|
|
|
162
173
|
cp "${RUNTIME_DIST_DIR}/icu-asset.json" "${destination}/runtime/dist/icu-asset.json"
|
|
163
174
|
fi
|
|
164
175
|
cp -R "${RUNTIME_DIST_DIR}/runtime" "${destination}/runtime/dist/runtime"
|
|
176
|
+
if [ -d "${RUNTIME_DIST_DIR}/fonts" ]; then
|
|
177
|
+
mkdir -p "${destination}/runtime"
|
|
178
|
+
cp -R "${RUNTIME_DIST_DIR}/fonts" "${destination}/runtime/fonts"
|
|
179
|
+
fi
|
|
165
180
|
}
|
|
166
181
|
|
|
167
182
|
build_workers() {
|
|
@@ -231,13 +246,19 @@ copy_worker_assets() {
|
|
|
231
246
|
generate_host_services "demo/src/host-services.ts" "demoHostServices" "demo/src/generated/HostServices.ts"
|
|
232
247
|
generate_host_events "demo/src/host-events.ts" "demoHostEvents" "demo/src/generated/HostEvents.ts"
|
|
233
248
|
generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "demo/src/generated/WorkerHostServices.ts"
|
|
249
|
+
generate_host_services "templates/demo-hello-world/src/host/host-services.ts" "appHostServices" "templates/demo-hello-world/src/generated/HostServices.ts" "../FuiPrimitives"
|
|
250
|
+
generate_host_events "templates/demo-hello-world/src/host/host-events.ts" "appHostEvents" "templates/demo-hello-world/src/generated/HostEvents.ts" "../FuiPrimitives"
|
|
251
|
+
generate_host_services "templates/demo-mvc/src/host/host-services.ts" "appHostServices" "templates/demo-mvc/src/generated/HostServices.ts" "../FuiPrimitives"
|
|
252
|
+
generate_host_events "templates/demo-mvc/src/host/host-events.ts" "appHostEvents" "templates/demo-mvc/src/generated/HostEvents.ts" "../FuiPrimitives"
|
|
234
253
|
|
|
235
254
|
build_app "tests/fixtures/smoke/app.ts" "${OUT_DIR}/app.wasm"
|
|
236
255
|
build_app "demo/src/dashboard.ts" "${DEMO_OUT_DIR}/demo.wasm"
|
|
237
256
|
build_app "demo/src/routes/demo_home.ts" "${DEMO_OUT_DIR}/home.wasm"
|
|
238
257
|
build_app "demo/src/routes/demo_advanced_controls.ts" "${DEMO_OUT_DIR}/advanced-controls.wasm"
|
|
239
258
|
build_app "demo/src/routes/templated-controls.ts" "${DEMO_OUT_DIR}/templated-controls.wasm"
|
|
240
|
-
build_app "demo-
|
|
259
|
+
build_app "templates/demo-mvc/src/routes/mvc_home.ts" "${MVC_OUT_DIR}/mvc-home.wasm"
|
|
260
|
+
build_app "templates/demo-mvc/src/routes/mvc_settings.ts" "${MVC_OUT_DIR}/mvc-settings.wasm"
|
|
261
|
+
build_app "templates/demo-hello-world/src/App.ts" "${HELLO_OUT_DIR}/app.wasm"
|
|
241
262
|
build_workers
|
|
242
263
|
write_worker_manifest
|
|
243
264
|
|
|
@@ -259,7 +280,16 @@ npx esbuild "${PACKAGE_DIR}/demo/harness.ts" \
|
|
|
259
280
|
--outfile="${DEMO_OUT_DIR}/harness.js" \
|
|
260
281
|
--sourcemap
|
|
261
282
|
|
|
262
|
-
npx esbuild "${PACKAGE_DIR}/demo-
|
|
283
|
+
npx esbuild "${PACKAGE_DIR}/templates/demo-mvc/harness.ts" \
|
|
284
|
+
--bundle \
|
|
285
|
+
--format=esm \
|
|
286
|
+
--platform=browser \
|
|
287
|
+
--target=es2020 \
|
|
288
|
+
--minify \
|
|
289
|
+
--outfile="${MVC_OUT_DIR}/harness.js" \
|
|
290
|
+
--sourcemap
|
|
291
|
+
|
|
292
|
+
npx esbuild "${PACKAGE_DIR}/templates/demo-hello-world/harness.ts" \
|
|
263
293
|
--bundle \
|
|
264
294
|
--format=esm \
|
|
265
295
|
--platform=browser \
|
|
@@ -281,6 +311,8 @@ cp "${FILE_PROCESSING_WORKER_BUILD}" "${OUT_DIR}/file-processing-worker.js"
|
|
|
281
311
|
cp "${FILE_PROCESSING_WORKER_MAP_BUILD}" "${OUT_DIR}/file-processing-worker.js.map"
|
|
282
312
|
cp "${FILE_PROCESSING_WORKER_BUILD}" "${DEMO_OUT_DIR}/file-processing-worker.js"
|
|
283
313
|
cp "${FILE_PROCESSING_WORKER_MAP_BUILD}" "${DEMO_OUT_DIR}/file-processing-worker.js.map"
|
|
314
|
+
cp "${FILE_PROCESSING_WORKER_BUILD}" "${MVC_OUT_DIR}/file-processing-worker.js"
|
|
315
|
+
cp "${FILE_PROCESSING_WORKER_MAP_BUILD}" "${MVC_OUT_DIR}/file-processing-worker.js.map"
|
|
284
316
|
cp "${FILE_PROCESSING_WORKER_BUILD}" "${HELLO_OUT_DIR}/file-processing-worker.js"
|
|
285
317
|
cp "${FILE_PROCESSING_WORKER_MAP_BUILD}" "${HELLO_OUT_DIR}/file-processing-worker.js.map"
|
|
286
318
|
|
|
@@ -304,22 +336,43 @@ npx esbuild "${PACKAGE_DIR}/demo/worker-host-services.ts" \
|
|
|
304
336
|
|
|
305
337
|
cp "${SMOKE_FIXTURE_DIR}/index.html" "${OUT_DIR}/index.html"
|
|
306
338
|
cp "${PACKAGE_DIR}/demo/index.html" "${DEMO_OUT_DIR}/index.html"
|
|
307
|
-
cp "${PACKAGE_DIR}/demo-hello-world/index.html" "${HELLO_OUT_DIR}/index.html"
|
|
339
|
+
cp "${PACKAGE_DIR}/templates/demo-hello-world/index.html" "${HELLO_OUT_DIR}/index.html"
|
|
308
340
|
cp "${PACKAGE_DIR}/demo/demo-texture.png" "${DEMO_OUT_DIR}/demo-texture.png"
|
|
309
341
|
cp "${PACKAGE_DIR}/demo/demo-secondary-texture.png" "${DEMO_OUT_DIR}/demo-secondary-texture.png"
|
|
310
342
|
|
|
311
343
|
mkdir -p "${DEMO_OUT_DIR}/advanced-controls" "${DEMO_OUT_DIR}/templated-controls"
|
|
312
344
|
cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/advanced-controls/index.html"
|
|
313
345
|
cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/templated-controls/index.html"
|
|
346
|
+
mkdir -p "${MVC_OUT_DIR}/mvc-home" "${MVC_OUT_DIR}/mvc-settings"
|
|
347
|
+
cp "${PACKAGE_DIR}/templates/demo-mvc/route-shell.html" "${MVC_OUT_DIR}/mvc-home/index.html"
|
|
348
|
+
cp "${PACKAGE_DIR}/templates/demo-mvc/route-shell.html" "${MVC_OUT_DIR}/mvc-settings/index.html"
|
|
349
|
+
cat > "${MVC_OUT_DIR}/index.html" <<'EOF'
|
|
350
|
+
<!doctype html>
|
|
351
|
+
<html lang="en">
|
|
352
|
+
<head>
|
|
353
|
+
<meta charset="utf-8" />
|
|
354
|
+
<meta http-equiv="refresh" content="0; url=./mvc-home/" />
|
|
355
|
+
<title>FUI-AS MVC Demo</title>
|
|
356
|
+
</head>
|
|
357
|
+
<body>
|
|
358
|
+
<p>Redirecting to <a href="./mvc-home/">MVC Home</a>…</p>
|
|
359
|
+
</body>
|
|
360
|
+
</html>
|
|
361
|
+
EOF
|
|
314
362
|
|
|
315
363
|
copy_runtime_assets "${OUT_DIR}"
|
|
316
364
|
copy_runtime_assets "${DEMO_OUT_DIR}"
|
|
365
|
+
copy_runtime_assets "${MVC_OUT_DIR}"
|
|
317
366
|
copy_runtime_assets "${HELLO_OUT_DIR}"
|
|
318
367
|
copy_worker_assets "${OUT_DIR}"
|
|
319
368
|
copy_worker_assets "${DEMO_OUT_DIR}"
|
|
369
|
+
copy_worker_assets "${MVC_OUT_DIR}"
|
|
320
370
|
copy_worker_assets "${HELLO_OUT_DIR}"
|
|
321
371
|
write_runtime_config "${OUT_DIR}" "${DEFAULT_MANIFEST_PATH}"
|
|
322
372
|
write_runtime_config "${DEMO_OUT_DIR}" "${DEFAULT_MANIFEST_PATH}"
|
|
373
|
+
write_runtime_config "${MVC_OUT_DIR}" "${DEFAULT_MANIFEST_PATH}"
|
|
323
374
|
write_runtime_config "${HELLO_OUT_DIR}" "${DEFAULT_MANIFEST_PATH}"
|
|
324
375
|
write_runtime_config "${DEMO_OUT_DIR}/advanced-controls" "../runtime/dist/effindom.v2.manifest.json"
|
|
325
376
|
write_runtime_config "${DEMO_OUT_DIR}/templated-controls" "../runtime/dist/effindom.v2.manifest.json"
|
|
377
|
+
write_runtime_config "${MVC_OUT_DIR}/mvc-home" "../runtime/dist/effindom.v2.manifest.json"
|
|
378
|
+
write_runtime_config "${MVC_OUT_DIR}/mvc-settings" "../runtime/dist/effindom.v2.manifest.json"
|
|
@@ -32,6 +32,12 @@ function asTypeName(type: HostServiceTypeName): string {
|
|
|
32
32
|
return "i32";
|
|
33
33
|
case "f64":
|
|
34
34
|
return "f64";
|
|
35
|
+
case "bytes":
|
|
36
|
+
return "Uint8Array";
|
|
37
|
+
case "i32_array":
|
|
38
|
+
return "Int32Array";
|
|
39
|
+
case "f64_array":
|
|
40
|
+
return "Float64Array";
|
|
35
41
|
case "void":
|
|
36
42
|
return "void";
|
|
37
43
|
}
|
|
@@ -53,7 +59,7 @@ function callbackTypeFor(method: NormalizedHostEventMethod): string {
|
|
|
53
59
|
function emitExportArgs(method: NormalizedHostEventMethod): string {
|
|
54
60
|
const parts: Array<string> = [];
|
|
55
61
|
method.args.forEach((type, index) => {
|
|
56
|
-
if (type === "string") {
|
|
62
|
+
if (type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array") {
|
|
57
63
|
parts.push(`arg${String(index)}Ptr: usize`, `arg${String(index)}Len: u32`);
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
@@ -66,6 +72,26 @@ function emitDecodedArgs(method: NormalizedHostEventMethod): Array<string> {
|
|
|
66
72
|
const lines: Array<string> = [];
|
|
67
73
|
method.args.forEach((type, index) => {
|
|
68
74
|
if (type !== "string") {
|
|
75
|
+
if (type === "bytes") {
|
|
76
|
+
lines.push(` const arg${String(index)} = new Uint8Array(<i32>arg${String(index)}Len);`);
|
|
77
|
+
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
78
|
+
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len);`);
|
|
79
|
+
lines.push(" }");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (type === "i32_array") {
|
|
83
|
+
lines.push(` const arg${String(index)} = new Int32Array(<i32>arg${String(index)}Len);`);
|
|
84
|
+
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
85
|
+
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 2);`);
|
|
86
|
+
lines.push(" }");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (type === "f64_array") {
|
|
90
|
+
lines.push(` const arg${String(index)} = new Float64Array(<i32>arg${String(index)}Len);`);
|
|
91
|
+
lines.push(` if (arg${String(index)}Len > 0) {`);
|
|
92
|
+
lines.push(` memory.copy(arg${String(index)}.dataStart, arg${String(index)}Ptr, <usize>arg${String(index)}Len << 3);`);
|
|
93
|
+
lines.push(" }");
|
|
94
|
+
}
|
|
69
95
|
return;
|
|
70
96
|
}
|
|
71
97
|
lines.push(
|
|
@@ -141,8 +167,9 @@ function emitBindingsFile(
|
|
|
141
167
|
exportName: string,
|
|
142
168
|
outputPath: string,
|
|
143
169
|
methods: ReturnType<typeof listHostEventMethods>,
|
|
170
|
+
primitivesImportOverride: string | undefined,
|
|
144
171
|
): string {
|
|
145
|
-
const callbackImport = relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
|
|
172
|
+
const callbackImport = primitivesImportOverride ?? relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
|
|
146
173
|
const blocks: Array<string> = [
|
|
147
174
|
`// Generated by scripts/generate-host-events.ts from ${toPosix(sourceModulePath)}#${exportName}.`,
|
|
148
175
|
`import { Callback0, Callback1, Callback2 } from "${callbackImport}";`,
|
|
@@ -159,15 +186,15 @@ function emitBindingsFile(
|
|
|
159
186
|
}
|
|
160
187
|
|
|
161
188
|
async function main(): Promise<void> {
|
|
162
|
-
const [moduleArg, exportName, outputArg] = process.argv.slice(2);
|
|
189
|
+
const [moduleArg, exportName, outputArg, primitivesImportArg] = process.argv.slice(2);
|
|
163
190
|
if (moduleArg === undefined || exportName === undefined || outputArg === undefined) {
|
|
164
|
-
throw new Error("Usage: generate-host-events <module-path> <export-name> <output-path>");
|
|
191
|
+
throw new Error("Usage: generate-host-events <module-path> <export-name> <output-path> [primitives-import]");
|
|
165
192
|
}
|
|
166
193
|
const modulePath = path.resolve(process.cwd(), moduleArg);
|
|
167
194
|
const outputPath = path.resolve(process.cwd(), outputArg);
|
|
168
195
|
const registry = await loadHostEvents(modulePath, exportName);
|
|
169
196
|
const methods = listHostEventMethods(registry as never);
|
|
170
|
-
const content = emitBindingsFile(modulePath, exportName, outputPath, methods);
|
|
197
|
+
const content = emitBindingsFile(modulePath, exportName, outputPath, methods, primitivesImportArg);
|
|
171
198
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
172
199
|
await fs.writeFile(outputPath, content, "utf8");
|
|
173
200
|
}
|
|
@@ -31,24 +31,38 @@ function asTypeName(type: HostServiceTypeName): string {
|
|
|
31
31
|
return "i32";
|
|
32
32
|
case "f64":
|
|
33
33
|
return "f64";
|
|
34
|
+
case "bytes":
|
|
35
|
+
return "Uint8Array";
|
|
36
|
+
case "i32_array":
|
|
37
|
+
return "Int32Array";
|
|
38
|
+
case "f64_array":
|
|
39
|
+
return "Float64Array";
|
|
34
40
|
case "void":
|
|
35
41
|
return "void";
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
function isPointerLengthType(type: HostServiceTypeName): boolean {
|
|
46
|
+
return type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function returnsBufferType(type: HostServiceTypeName): boolean {
|
|
50
|
+
return type === "string" || type === "bytes" || type === "i32_array" || type === "f64_array";
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
function emitExternalSignature(importName: string, args: readonly HostServiceTypeName[], returns: HostServiceTypeName): string {
|
|
40
54
|
const signatureParts: Array<string> = [];
|
|
41
55
|
args.forEach((type, index) => {
|
|
42
|
-
if (type
|
|
56
|
+
if (isPointerLengthType(type)) {
|
|
43
57
|
signatureParts.push(`arg${String(index)}Ptr: usize`, `arg${String(index)}Len: u32`);
|
|
44
58
|
return;
|
|
45
59
|
}
|
|
46
60
|
signatureParts.push(`arg${String(index)}: ${asTypeName(type)}`);
|
|
47
61
|
});
|
|
48
|
-
if (returns
|
|
62
|
+
if (returnsBufferType(returns)) {
|
|
49
63
|
signatureParts.push("resultPtr: usize", "resultCap: u32");
|
|
50
64
|
}
|
|
51
|
-
const returnType = returns
|
|
65
|
+
const returnType = returnsBufferType(returns) ? "u32" : asTypeName(returns);
|
|
52
66
|
return [
|
|
53
67
|
`@external("fui_host_service", "${importName}")`,
|
|
54
68
|
`declare function __host_${importName}(${signatureParts.join(", ")}): ${returnType};`,
|
|
@@ -61,6 +75,10 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
|
|
|
61
75
|
args.forEach((type, index) => {
|
|
62
76
|
if (type === "string") {
|
|
63
77
|
lines.push(` const arg${String(index)}Bytes = Uint8Array.wrap(String.UTF8.encode(arg${String(index)}, false));`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (type === "bytes" || type === "i32_array" || type === "f64_array") {
|
|
81
|
+
lines.push(` const arg${String(index)}Bytes = arg${String(index)};`);
|
|
64
82
|
}
|
|
65
83
|
});
|
|
66
84
|
const callArgs: Array<string> = [];
|
|
@@ -70,14 +88,32 @@ function emitWrapper(importName: string, args: readonly HostServiceTypeName[], r
|
|
|
70
88
|
callArgs.push(`<u32>arg${String(index)}Bytes.length`);
|
|
71
89
|
return;
|
|
72
90
|
}
|
|
91
|
+
if (type === "bytes") {
|
|
92
|
+
callArgs.push(`arg${String(index)}Bytes.length > 0 ? arg${String(index)}Bytes.dataStart : 0`);
|
|
93
|
+
callArgs.push(`<u32>arg${String(index)}Bytes.length`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (type === "i32_array" || type === "f64_array") {
|
|
97
|
+
callArgs.push(`arg${String(index)}Bytes.length > 0 ? arg${String(index)}Bytes.dataStart : 0`);
|
|
98
|
+
callArgs.push(`<u32>arg${String(index)}Bytes.length`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
73
101
|
callArgs.push(`arg${String(index)}`);
|
|
74
102
|
});
|
|
75
|
-
if (returns
|
|
103
|
+
if (returnsBufferType(returns)) {
|
|
76
104
|
lines.push(" const resultPtr = hostServiceResultBufferPtr();");
|
|
77
105
|
lines.push(" const resultCap = hostServiceResultBufferSize();");
|
|
78
106
|
callArgs.push("resultPtr", "resultCap");
|
|
79
107
|
lines.push(` const resultLen = __host_${importName}(${callArgs.join(", ")});`);
|
|
80
|
-
|
|
108
|
+
if (returns === "string") {
|
|
109
|
+
lines.push(` return decodeHostServiceStringResult(resultPtr, resultLen, "${importName}");`);
|
|
110
|
+
} else if (returns === "bytes") {
|
|
111
|
+
lines.push(` return decodeHostServiceBytesResult(resultPtr, resultLen, "${importName}");`);
|
|
112
|
+
} else if (returns === "i32_array") {
|
|
113
|
+
lines.push(` return decodeHostServiceI32ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
114
|
+
} else {
|
|
115
|
+
lines.push(` return decodeHostServiceF64ArrayResult(resultPtr, resultLen, "${importName}");`);
|
|
116
|
+
}
|
|
81
117
|
} else if (returns === "void") {
|
|
82
118
|
lines.push(` __host_${importName}(${callArgs.join(", ")});`);
|
|
83
119
|
} else {
|
|
@@ -118,13 +154,35 @@ function emitBindingsFile(
|
|
|
118
154
|
exportName: string,
|
|
119
155
|
outputPath: string,
|
|
120
156
|
methods: ReturnType<typeof listHostServiceMethods>,
|
|
157
|
+
primitivesImportOverride: string | undefined,
|
|
121
158
|
): string {
|
|
122
|
-
const runtimeImport = relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
|
|
159
|
+
const runtimeImport = primitivesImportOverride ?? relativeImport(outputPath, path.resolve(PACKAGE_DIR, "src/FuiPrimitives.ts"));
|
|
123
160
|
const blocks: Array<string> = [
|
|
124
161
|
`// Generated by scripts/generate-host-services.ts from ${toPosix(sourceModulePath)}#${exportName}.`,
|
|
125
162
|
];
|
|
126
|
-
if (methods.some((method) => method.returns
|
|
127
|
-
|
|
163
|
+
if (methods.some((method) => returnsBufferType(method.returns))) {
|
|
164
|
+
const helpers = [
|
|
165
|
+
"hostServiceResultBufferPtr",
|
|
166
|
+
"hostServiceResultBufferSize",
|
|
167
|
+
...new Set(
|
|
168
|
+
methods
|
|
169
|
+
.map((method) => method.returns)
|
|
170
|
+
.filter((type) => returnsBufferType(type))
|
|
171
|
+
.map((type) => {
|
|
172
|
+
if (type === "string") {
|
|
173
|
+
return "decodeHostServiceStringResult";
|
|
174
|
+
}
|
|
175
|
+
if (type === "bytes") {
|
|
176
|
+
return "decodeHostServiceBytesResult";
|
|
177
|
+
}
|
|
178
|
+
if (type === "i32_array") {
|
|
179
|
+
return "decodeHostServiceI32ArrayResult";
|
|
180
|
+
}
|
|
181
|
+
return "decodeHostServiceF64ArrayResult";
|
|
182
|
+
}),
|
|
183
|
+
),
|
|
184
|
+
];
|
|
185
|
+
blocks.push(`import { ${helpers.join(", ")} } from "${runtimeImport}";`);
|
|
128
186
|
blocks.push("");
|
|
129
187
|
} else {
|
|
130
188
|
blocks.push("");
|
|
@@ -141,15 +199,15 @@ function emitBindingsFile(
|
|
|
141
199
|
}
|
|
142
200
|
|
|
143
201
|
async function main(): Promise<void> {
|
|
144
|
-
const [moduleArg, exportName, outputArg] = process.argv.slice(2);
|
|
202
|
+
const [moduleArg, exportName, outputArg, primitivesImportArg] = process.argv.slice(2);
|
|
145
203
|
if (moduleArg === undefined || exportName === undefined || outputArg === undefined) {
|
|
146
|
-
throw new Error("Usage: generate-host-services <module-path> <export-name> <output-path>");
|
|
204
|
+
throw new Error("Usage: generate-host-services <module-path> <export-name> <output-path> [primitives-import]");
|
|
147
205
|
}
|
|
148
206
|
const modulePath = path.resolve(process.cwd(), moduleArg);
|
|
149
207
|
const outputPath = path.resolve(process.cwd(), outputArg);
|
|
150
208
|
const registry = await loadHostServices(modulePath, exportName);
|
|
151
209
|
const methods = listHostServiceMethods(registry as never);
|
|
152
|
-
const content = emitBindingsFile(modulePath, exportName, outputPath, methods);
|
|
210
|
+
const content = emitBindingsFile(modulePath, exportName, outputPath, methods, primitivesImportArg);
|
|
153
211
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
154
212
|
await fs.writeFile(outputPath, content, "utf8");
|
|
155
213
|
}
|
package/src/FuiPrimitives.ts
CHANGED
|
@@ -12,4 +12,11 @@ export {
|
|
|
12
12
|
} from "./core/BoundCallback";
|
|
13
13
|
export { bind0, bind1, bind2, bindResult0, bindResult1 } from "./core/bind";
|
|
14
14
|
export { clearCurrentSelection, tryGetBounds } from "./bindings/ui";
|
|
15
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
decodeHostServiceBytesResult,
|
|
17
|
+
decodeHostServiceF64ArrayResult,
|
|
18
|
+
decodeHostServiceI32ArrayResult,
|
|
19
|
+
decodeHostServiceStringResult,
|
|
20
|
+
hostServiceResultBufferPtr,
|
|
21
|
+
hostServiceResultBufferSize,
|
|
22
|
+
} from "./host-services/runtime";
|
|
@@ -8,7 +8,7 @@ export function hostServiceResultBufferSize(): u32 {
|
|
|
8
8
|
return __fui_text_buffer_size();
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function assertResultByteLength(resultLen: u32, importName: string): void {
|
|
12
12
|
const capacity = __fui_text_buffer_size();
|
|
13
13
|
if (resultLen > capacity) {
|
|
14
14
|
throw new Error(
|
|
@@ -21,5 +21,42 @@ export function decodeHostServiceStringResult(resultPtr: usize, resultLen: u32,
|
|
|
21
21
|
".",
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function decodeHostServiceStringResult(resultPtr: usize, resultLen: u32, importName: string): string {
|
|
27
|
+
assertResultByteLength(resultLen, importName);
|
|
24
28
|
return resultLen == 0 ? "" : String.UTF8.decodeUnsafe(resultPtr, <usize>resultLen, false);
|
|
25
29
|
}
|
|
30
|
+
|
|
31
|
+
export function decodeHostServiceBytesResult(resultPtr: usize, resultLen: u32, importName: string): Uint8Array {
|
|
32
|
+
assertResultByteLength(resultLen, importName);
|
|
33
|
+
const bytes = new Uint8Array(<i32>resultLen);
|
|
34
|
+
if (resultLen > 0) {
|
|
35
|
+
memory.copy(bytes.dataStart, resultPtr, <usize>resultLen);
|
|
36
|
+
}
|
|
37
|
+
return bytes;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function decodeHostServiceI32ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Int32Array {
|
|
41
|
+
assertResultByteLength(resultLen, importName);
|
|
42
|
+
if ((resultLen & 3) != 0) {
|
|
43
|
+
throw new Error("Host service " + importName + " returned misaligned Int32Array byte length.");
|
|
44
|
+
}
|
|
45
|
+
const values = new Int32Array(<i32>(resultLen >> 2));
|
|
46
|
+
if (resultLen > 0) {
|
|
47
|
+
memory.copy(values.dataStart, resultPtr, <usize>resultLen);
|
|
48
|
+
}
|
|
49
|
+
return values;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function decodeHostServiceF64ArrayResult(resultPtr: usize, resultLen: u32, importName: string): Float64Array {
|
|
53
|
+
assertResultByteLength(resultLen, importName);
|
|
54
|
+
if ((resultLen & 7) != 0) {
|
|
55
|
+
throw new Error("Host service " + importName + " returned misaligned Float64Array byte length.");
|
|
56
|
+
}
|
|
57
|
+
const values = new Float64Array(<i32>(resultLen >> 3));
|
|
58
|
+
if (resultLen > 0) {
|
|
59
|
+
memory.copy(values.dataStart, resultPtr, <usize>resultLen);
|
|
60
|
+
}
|
|
61
|
+
return values;
|
|
62
|
+
}
|