@remix_labs/machine-starter 1.1937.0-dev → 2.1841.0-dev
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/common.js +292 -0
- package/groovebox_build.js +1 -1
- package/index.js +8 -301
- package/machine-wasm.worker.js +2 -0
- package/node.js +29 -5
- package/package.json +2 -2
- package/start.js +2 -2
- package/webpack.config.js +50 -0
package/common.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
|
+
import { GROOVEBOX_BUILD } from "./groovebox_build.js";
|
|
3
|
+
|
|
4
|
+
let terminate = function(f) {};
|
|
5
|
+
if (globalThis.ThisIsNode) {
|
|
6
|
+
terminate = function(f) {
|
|
7
|
+
Process.on("exit", () => {
|
|
8
|
+
console.log("terminating machine worker");
|
|
9
|
+
f()
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function CreateVMID() {
|
|
15
|
+
return nanoid()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let initialMask = 64 + 1024; // default for web
|
|
19
|
+
globalThis.MixSetMask = (m => {
|
|
20
|
+
initialMask = m;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function StartWASM(hub, baseURL, org, workspace, vmID, user, token, noOutputViaMQTT) {
|
|
24
|
+
return StartWASM2(hub, {
|
|
25
|
+
baseURL: baseURL,
|
|
26
|
+
org: org,
|
|
27
|
+
workspace: workspace,
|
|
28
|
+
token: token,
|
|
29
|
+
noOutputViaMQTT: noOutputViaMQTT,
|
|
30
|
+
machType: "WASM"
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function StartWASM2(hub, config) {
|
|
35
|
+
console.log("GROOVEBOX_BUILD (machine-starter)", GROOVEBOX_BUILD);
|
|
36
|
+
return hub.newChannel().then(channel => {
|
|
37
|
+
// injected by either index.js or node.js
|
|
38
|
+
let worker = globalThis.GetMachineWASMWorker();
|
|
39
|
+
let localFFIs = {};
|
|
40
|
+
if (config.localFFIs) {
|
|
41
|
+
for (const [k, v] of Object.entries(config.localFFIs)) {
|
|
42
|
+
if (v.isRaw && (v.canFail || v.useJsonDecoder)) {
|
|
43
|
+
console.error("A raw, local FFI must neither be failing nor use the JSON decoder: " + k);
|
|
44
|
+
} else {
|
|
45
|
+
localFFIs[k] = {...v};
|
|
46
|
+
delete localFFIs[k].run;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let config_msg =
|
|
51
|
+
{ "_rmx_type": "msg_vm_configure",
|
|
52
|
+
"baseURL": config.baseURL,
|
|
53
|
+
"org": config.org,
|
|
54
|
+
"workspace": config.workspace,
|
|
55
|
+
"vmID": config.vmID,
|
|
56
|
+
"user": config.user,
|
|
57
|
+
"token": config.token,
|
|
58
|
+
"hub": channel.port,
|
|
59
|
+
"outputViaMQTT": !(config.noOutputViaMQTT),
|
|
60
|
+
"machType": config.machType,
|
|
61
|
+
"debugMask": initialMask,
|
|
62
|
+
"localFFIs": localFFIs,
|
|
63
|
+
"grooveboxUrlPrefix": globalThis.GROOVEBOX_URL_PREFIX,
|
|
64
|
+
"allowInsecureHttp": globalThis.GROOVEBOX_ALLOW_INSECURE_HTTP,
|
|
65
|
+
"interceptFFI": config.interceptFFI,
|
|
66
|
+
"interceptDynloadFFI": config.interceptDynloadFFI,
|
|
67
|
+
};
|
|
68
|
+
worker.postMessage(config_msg, [ channel.port ]);
|
|
69
|
+
terminate(() => worker.terminate());
|
|
70
|
+
globalThis.MixSetMask = (m => {
|
|
71
|
+
worker.postMessage({ "_rmx_type": "msg_vm_logMask",
|
|
72
|
+
"mask": m
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
if (config.localFFIs)
|
|
76
|
+
setupLocalFFIs(hub, config); // don't await!
|
|
77
|
+
return worker;
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class Token {
|
|
82
|
+
constructor(data, enabled, extras) {
|
|
83
|
+
// data: must be an Uint8Array
|
|
84
|
+
// enabled: a number with the bitset of enabled operations
|
|
85
|
+
// extras: a map
|
|
86
|
+
if (!data instanceof Uint8Array)
|
|
87
|
+
throw new Error("Token: data must be an Uint8Array");
|
|
88
|
+
if (typeof(enabled) != "number")
|
|
89
|
+
throw new Error("Token: enabled_ops must be a number");
|
|
90
|
+
this.data = data;
|
|
91
|
+
this.enabled = enabled;
|
|
92
|
+
this.extras = extras;
|
|
93
|
+
}
|
|
94
|
+
encode() {
|
|
95
|
+
return new Map([
|
|
96
|
+
[ "_rmx_type", "token" ],
|
|
97
|
+
[ "data", this.data ],
|
|
98
|
+
[ "enabled", this.enabled ],
|
|
99
|
+
[ "extras", this.extras ]
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function decode_Token(m) {
|
|
105
|
+
return new Token(m.get("data"), m.get("enabled"), m.get("extras"))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class Case {
|
|
109
|
+
constructor(name, arg) {
|
|
110
|
+
if (typeof(name) != "string")
|
|
111
|
+
throw new Error("Case: name must be a string");
|
|
112
|
+
this.name = name;
|
|
113
|
+
this.arg = arg;
|
|
114
|
+
}
|
|
115
|
+
encode() {
|
|
116
|
+
return new Map([
|
|
117
|
+
[ "_rmx_type", "case" ],
|
|
118
|
+
[ "name", this.name ],
|
|
119
|
+
[ "arg", this.arg ],
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function decode_Case(m) {
|
|
125
|
+
return new Case(m.get("name"), m.get("arg"))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class Opaque {
|
|
129
|
+
constructor(buf) {
|
|
130
|
+
if (!(buf instanceof Uint32Array))
|
|
131
|
+
throw new Error("Opaque: must be a Uint32Array");
|
|
132
|
+
this.data = buf;
|
|
133
|
+
}
|
|
134
|
+
encode() {
|
|
135
|
+
return new Map([
|
|
136
|
+
[ "_rmx_type", "opaque" ],
|
|
137
|
+
[ "data", this.data ],
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function decode_Opaque(m) {
|
|
143
|
+
return new Opaque(m.get("data"))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function encode(val) {
|
|
147
|
+
switch (typeof(val)) {
|
|
148
|
+
case "object":
|
|
149
|
+
if (val === null) return val;
|
|
150
|
+
if (Array.isArray(val)) {
|
|
151
|
+
// array: map the array elements
|
|
152
|
+
val = val.map(encode)
|
|
153
|
+
} else if (Object.getPrototypeOf(val) === Object.prototype) {
|
|
154
|
+
// simple object: map the object elements
|
|
155
|
+
val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, encode(elem) ]))
|
|
156
|
+
} else if (val.encode instanceof Function) {
|
|
157
|
+
// map after encode(): map the map elements
|
|
158
|
+
val = new Map(Array.from(val.encode().entries()).map(([ key, elem ]) => [ key, encode(elem) ]))
|
|
159
|
+
};
|
|
160
|
+
return val;
|
|
161
|
+
default:
|
|
162
|
+
return val;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function decode(val) {
|
|
167
|
+
switch (typeof(val)) {
|
|
168
|
+
case "object":
|
|
169
|
+
if (val === null) return val;
|
|
170
|
+
if (Array.isArray(val)) {
|
|
171
|
+
// array: map the array elements
|
|
172
|
+
val = val.map(decode)
|
|
173
|
+
} else if (Object.getPrototypeOf(val) === Object.prototype) {
|
|
174
|
+
// simple object: map the object elements
|
|
175
|
+
val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, decode(elem) ]))
|
|
176
|
+
} else if (val instanceof Map) {
|
|
177
|
+
val = new Map(Array.from(val.entries()).map(([ key, elem ]) => [ key, decode(elem) ]))
|
|
178
|
+
let ty = val.get("_rmx_type");
|
|
179
|
+
switch (ty) {
|
|
180
|
+
case "token":
|
|
181
|
+
return decode_Token(val);
|
|
182
|
+
case "case":
|
|
183
|
+
return decode_Case(val);
|
|
184
|
+
case "opaque":
|
|
185
|
+
return decode_Opaque(val);
|
|
186
|
+
default:
|
|
187
|
+
throw new Error("decode: bad _rmx_type: " + ty);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
return val;
|
|
191
|
+
default:
|
|
192
|
+
return val;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function setupLocalFFIs(hub, config) {
|
|
197
|
+
let channel = await hub.newChannel();
|
|
198
|
+
let comm = new FFIComm(hub, channel);
|
|
199
|
+
let localFFIs = config.localFFIs;
|
|
200
|
+
await channel.setLocalSubTopic("/local/ffi/call");
|
|
201
|
+
await channel.setLocalPubTopic("/local/ffi/return");
|
|
202
|
+
let sub = await channel.subscribe("/local/ffi/call");
|
|
203
|
+
while (true) {
|
|
204
|
+
let msg = await sub.next();
|
|
205
|
+
let name = msg.payload.name;
|
|
206
|
+
let call_id = msg.payload.call_id;
|
|
207
|
+
let args = decode(msg.payload.args);
|
|
208
|
+
let fun = localFFIs[name];
|
|
209
|
+
try {
|
|
210
|
+
if (!fun)
|
|
211
|
+
throw new Error("no such local FFI: " + name);
|
|
212
|
+
let connector =
|
|
213
|
+
{ call_id: call_id,
|
|
214
|
+
hub: hub,
|
|
215
|
+
channel: channel,
|
|
216
|
+
state: config.state,
|
|
217
|
+
};
|
|
218
|
+
let r = fun instanceof Function ? fun(connector, args) : fun.run(connector, args);
|
|
219
|
+
if (r instanceof Promise) {
|
|
220
|
+
await comm.later(call_id);
|
|
221
|
+
r.then(
|
|
222
|
+
(value) => comm.returnOrFail(fun, call_id, value),
|
|
223
|
+
(reason) => comm.error(call_id, reason.message, reason.stack));
|
|
224
|
+
} else {
|
|
225
|
+
await comm.returnOrFail(fun, call_id, r);
|
|
226
|
+
}
|
|
227
|
+
} catch (reason) {
|
|
228
|
+
await comm.error(call_id, reason.message, reason.stack);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
class FFIComm {
|
|
234
|
+
constructor(hub, channel) {
|
|
235
|
+
this.hub = hub;
|
|
236
|
+
this.channel = channel;
|
|
237
|
+
}
|
|
238
|
+
later(call_id) {
|
|
239
|
+
let r_payload =
|
|
240
|
+
{ call_id: call_id,
|
|
241
|
+
};
|
|
242
|
+
let response = this.hub.newLocalMessage("msg_ffi_later", "starter", r_payload);
|
|
243
|
+
return this.channel.publish("/local/ffi/return", response, false);
|
|
244
|
+
}
|
|
245
|
+
return_(fun, call_id, result) {
|
|
246
|
+
let r_payload =
|
|
247
|
+
{ call_id: call_id,
|
|
248
|
+
value: fun.useJsonDecoder ? result : encode(result)
|
|
249
|
+
};
|
|
250
|
+
let response = this.hub.newLocalMessage("msg_ffi_return", "starter", r_payload);
|
|
251
|
+
return this.channel.publish("/local/ffi/return", response, false);
|
|
252
|
+
}
|
|
253
|
+
error(call_id, message, stack) {
|
|
254
|
+
let r_payload =
|
|
255
|
+
{ call_id: call_id,
|
|
256
|
+
message: message,
|
|
257
|
+
stack: stack === undefined ? [] : stack,
|
|
258
|
+
};
|
|
259
|
+
let response = this.hub.newLocalMessage("msg_ffi_error", "starter", r_payload);
|
|
260
|
+
this.channel.publish("/local/ffi/return", response, false);
|
|
261
|
+
}
|
|
262
|
+
returnOrFail(fun, call_id, result) {
|
|
263
|
+
if (fun.canFail) {
|
|
264
|
+
var name, arg;
|
|
265
|
+
if (result instanceof Case) {
|
|
266
|
+
name = result.name;
|
|
267
|
+
arg = result.arg;
|
|
268
|
+
} else if (fun.useJsonDecoder && result._rmx_type && result._rmx_type.startsWith("{tag}case:")) {
|
|
269
|
+
name = result._rmx_type.substring(10);
|
|
270
|
+
arg = result._rmx_value;
|
|
271
|
+
} else {
|
|
272
|
+
return this.error(call_id, "expected result from local FFI that can fail");
|
|
273
|
+
}
|
|
274
|
+
switch (name) {
|
|
275
|
+
case "ok":
|
|
276
|
+
return this.return_(fun, call_id, arg);
|
|
277
|
+
case "error":
|
|
278
|
+
return this.error(call_id, arg);
|
|
279
|
+
default:
|
|
280
|
+
return this.error(call_id, "expected result from local FFI that can fail, found case value " + name);
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
return this.return_(fun, call_id, result);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// use worker.terminate() to shut a worker down!
|
|
289
|
+
|
|
290
|
+
export { CreateVMID, StartWASM, StartWASM2,
|
|
291
|
+
Token, Case, Opaque
|
|
292
|
+
}
|
package/groovebox_build.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var GROOVEBOX_BUILD = "
|
|
1
|
+
var GROOVEBOX_BUILD = "1935";
|
|
2
2
|
export { GROOVEBOX_BUILD }
|
package/index.js
CHANGED
|
@@ -1,308 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
let terminate = function(f) {};
|
|
5
|
-
if (globalThis.ThisIsNode) {
|
|
6
|
-
terminate = function(f) {
|
|
7
|
-
Process.on("exit", () => {
|
|
8
|
-
console.log("terminating machine worker");
|
|
9
|
-
f()
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function CreateVMID() {
|
|
15
|
-
return nanoid()
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let initialMask = 64 + 1024; // default for web
|
|
19
|
-
globalThis.MixSetMask = (m => {
|
|
20
|
-
initialMask = m;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function StartWASM(hub, baseURL, org, workspace, vmID, user, token, noOutputViaMQTT) {
|
|
24
|
-
return StartWASM2(hub, {
|
|
25
|
-
baseURL: baseURL,
|
|
26
|
-
org: org,
|
|
27
|
-
workspace: workspace,
|
|
28
|
-
vmID: vmID,
|
|
29
|
-
user: user,
|
|
30
|
-
token: token,
|
|
31
|
-
noOutputViaMQTT: noOutputViaMQTT,
|
|
32
|
-
machType: "WASM"
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function StartWASM2(hub, config) {
|
|
37
|
-
console.log("GROOVEBOX_BUILD (machine-starter)", GROOVEBOX_BUILD);
|
|
38
|
-
return hub.newChannel().then(channel => {
|
|
39
|
-
let bundle;
|
|
40
|
-
if (globalThis.ThisIsNode) {
|
|
41
|
-
bundle = new URL("../../machine-wasm/dist/node-bundle.js", import.meta.url);
|
|
42
|
-
} else if (globalThis.GROOVEBOX_URL_PREFIX) {
|
|
43
|
-
// FIXME: Stopgap solution until we figure out a way to bundle groovebox correctly
|
|
44
|
-
// already fire off the download of the wasm code:
|
|
45
|
-
let code_url = new URL(`${globalThis.GROOVEBOX_URL_PREFIX}/machine-wasm-code.wasm`, window.location.href);
|
|
46
|
-
fetch(code_url, {cache:"default"}).then(resp => { return resp.arrayBuffer() });
|
|
47
|
-
bundle = new URL(`${globalThis.GROOVEBOX_URL_PREFIX}/machine-wasm-core.js`, window.location.href);
|
|
48
|
-
} else {
|
|
49
|
-
// already fire off the download of the wasm code:
|
|
50
|
-
let code_url = new URL("/g/machine-wasm-code.wasm", window.location.href);
|
|
51
|
-
fetch(code_url, {cache:"default"}).then(resp => { return resp.arrayBuffer() });
|
|
52
|
-
bundle = new URL("/g/machine-wasm-core.js", window.location.href);
|
|
53
|
-
}
|
|
54
|
-
let localFFIs = {};
|
|
55
|
-
if (config.localFFIs) {
|
|
56
|
-
for (const [k, v] of Object.entries(config.localFFIs)) {
|
|
57
|
-
if (v.isRaw && (v.canFail || v.useJsonDecoder)) {
|
|
58
|
-
console.error("A raw, local FFI must neither be failing nor use the JSON decoder: " + k);
|
|
59
|
-
} else {
|
|
60
|
-
localFFIs[k] = {...v};
|
|
61
|
-
delete localFFIs[k].run;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
let worker = new Worker(bundle);
|
|
66
|
-
let config_msg =
|
|
67
|
-
{ "_rmx_type": "msg_vm_configure",
|
|
68
|
-
"baseURL": config.baseURL,
|
|
69
|
-
"org": config.org,
|
|
70
|
-
"workspace": config.workspace,
|
|
71
|
-
"vmID": config.vmID,
|
|
72
|
-
"user": config.user,
|
|
73
|
-
"token": config.token,
|
|
74
|
-
"hub": channel.port,
|
|
75
|
-
"outputViaMQTT": !(config.noOutputViaMQTT),
|
|
76
|
-
"machType": config.machType,
|
|
77
|
-
"debugMask": initialMask,
|
|
78
|
-
"localFFIs": localFFIs,
|
|
79
|
-
"grooveboxUrlPrefix": globalThis.GROOVEBOX_URL_PREFIX,
|
|
80
|
-
"allowInsecureHttp": globalThis.GROOVEBOX_ALLOW_INSECURE_HTTP,
|
|
81
|
-
"interceptFFI": config.interceptFFI,
|
|
82
|
-
"interceptDynloadFFI": config.interceptDynloadFFI,
|
|
83
|
-
};
|
|
84
|
-
worker.postMessage(config_msg, [ channel.port ]);
|
|
85
|
-
terminate(() => worker.terminate());
|
|
86
|
-
globalThis.MixSetMask = (m => {
|
|
87
|
-
worker.postMessage({ "_rmx_type": "msg_vm_logMask",
|
|
88
|
-
"mask": m
|
|
89
|
-
})
|
|
90
|
-
});
|
|
91
|
-
if (config.localFFIs)
|
|
92
|
-
setupLocalFFIs(hub, config); // don't await!
|
|
93
|
-
return worker;
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
class Token {
|
|
98
|
-
constructor(data, enabled, extras) {
|
|
99
|
-
// data: must be an Uint8Array
|
|
100
|
-
// enabled: a number with the bitset of enabled operations
|
|
101
|
-
// extras: a map
|
|
102
|
-
if (!data instanceof Uint8Array)
|
|
103
|
-
throw new Error("Token: data must be an Uint8Array");
|
|
104
|
-
if (typeof(enabled) != "number")
|
|
105
|
-
throw new Error("Token: enabled_ops must be a number");
|
|
106
|
-
this.data = data;
|
|
107
|
-
this.enabled = enabled;
|
|
108
|
-
this.extras = extras;
|
|
109
|
-
}
|
|
110
|
-
encode() {
|
|
111
|
-
return new Map([
|
|
112
|
-
[ "_rmx_type", "token" ],
|
|
113
|
-
[ "data", this.data ],
|
|
114
|
-
[ "enabled", this.enabled ],
|
|
115
|
-
[ "extras", this.extras ]
|
|
116
|
-
]);
|
|
117
|
-
}
|
|
1
|
+
function workerify(code) {
|
|
2
|
+
let blob = new Blob([code], {type: 'application/javascript'});
|
|
3
|
+
return new Worker(URL.createObjectURL(blob));
|
|
118
4
|
}
|
|
119
5
|
|
|
120
|
-
|
|
121
|
-
return new Token(m.get("data"), m.get("enabled"), m.get("extras"))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
class Case {
|
|
125
|
-
constructor(name, arg) {
|
|
126
|
-
if (typeof(name) != "string")
|
|
127
|
-
throw new Error("Case: name must be a string");
|
|
128
|
-
this.name = name;
|
|
129
|
-
this.arg = arg;
|
|
130
|
-
}
|
|
131
|
-
encode() {
|
|
132
|
-
return new Map([
|
|
133
|
-
[ "_rmx_type", "case" ],
|
|
134
|
-
[ "name", this.name ],
|
|
135
|
-
[ "arg", this.arg ],
|
|
136
|
-
]);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
6
|
+
import MachineWASMWorker from './machine-wasm.worker.js';
|
|
139
7
|
|
|
140
|
-
function
|
|
141
|
-
return
|
|
8
|
+
function GetMachineWASMWorker() {
|
|
9
|
+
return workerify(MachineWASMWorker);
|
|
142
10
|
}
|
|
143
11
|
|
|
144
|
-
|
|
145
|
-
constructor(buf) {
|
|
146
|
-
if (!(buf instanceof Uint32Array))
|
|
147
|
-
throw new Error("Opaque: must be a Uint32Array");
|
|
148
|
-
this.data = buf;
|
|
149
|
-
}
|
|
150
|
-
encode() {
|
|
151
|
-
return new Map([
|
|
152
|
-
[ "_rmx_type", "opaque" ],
|
|
153
|
-
[ "data", this.data ],
|
|
154
|
-
]);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function decode_Opaque(m) {
|
|
159
|
-
return new Opaque(m.get("data"))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function encode(val) {
|
|
163
|
-
switch (typeof(val)) {
|
|
164
|
-
case "object":
|
|
165
|
-
if (val === null) return val;
|
|
166
|
-
if (Array.isArray(val)) {
|
|
167
|
-
// array: map the array elements
|
|
168
|
-
val = val.map(encode)
|
|
169
|
-
} else if (Object.getPrototypeOf(val) === Object.prototype) {
|
|
170
|
-
// simple object: map the object elements
|
|
171
|
-
val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, encode(elem) ]))
|
|
172
|
-
} else if (val.encode instanceof Function) {
|
|
173
|
-
// map after encode(): map the map elements
|
|
174
|
-
val = new Map(Array.from(val.encode().entries()).map(([ key, elem ]) => [ key, encode(elem) ]))
|
|
175
|
-
};
|
|
176
|
-
return val;
|
|
177
|
-
default:
|
|
178
|
-
return val;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function decode(val) {
|
|
183
|
-
switch (typeof(val)) {
|
|
184
|
-
case "object":
|
|
185
|
-
if (val === null) return val;
|
|
186
|
-
if (Array.isArray(val)) {
|
|
187
|
-
// array: map the array elements
|
|
188
|
-
val = val.map(decode)
|
|
189
|
-
} else if (Object.getPrototypeOf(val) === Object.prototype) {
|
|
190
|
-
// simple object: map the object elements
|
|
191
|
-
val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, decode(elem) ]))
|
|
192
|
-
} else if (val instanceof Map) {
|
|
193
|
-
val = new Map(Array.from(val.entries()).map(([ key, elem ]) => [ key, decode(elem) ]))
|
|
194
|
-
let ty = val.get("_rmx_type");
|
|
195
|
-
switch (ty) {
|
|
196
|
-
case "token":
|
|
197
|
-
return decode_Token(val);
|
|
198
|
-
case "case":
|
|
199
|
-
return decode_Case(val);
|
|
200
|
-
case "opaque":
|
|
201
|
-
return decode_Opaque(val);
|
|
202
|
-
default:
|
|
203
|
-
throw new Error("decode: bad _rmx_type: " + ty);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
return val;
|
|
207
|
-
default:
|
|
208
|
-
return val;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function setupLocalFFIs(hub, config) {
|
|
213
|
-
let channel = await hub.newChannel();
|
|
214
|
-
let comm = new FFIComm(hub, channel);
|
|
215
|
-
let localFFIs = config.localFFIs;
|
|
216
|
-
await channel.setLocalSubTopic("/local/ffi/call");
|
|
217
|
-
await channel.setLocalPubTopic("/local/ffi/return");
|
|
218
|
-
let sub = await channel.subscribe("/local/ffi/call");
|
|
219
|
-
while (true) {
|
|
220
|
-
let msg = await sub.next();
|
|
221
|
-
let name = msg.payload.name;
|
|
222
|
-
let call_id = msg.payload.call_id;
|
|
223
|
-
let args = decode(msg.payload.args);
|
|
224
|
-
let fun = localFFIs[name];
|
|
225
|
-
try {
|
|
226
|
-
if (!fun)
|
|
227
|
-
throw new Error("no such local FFI: " + name);
|
|
228
|
-
let connector =
|
|
229
|
-
{ call_id: call_id,
|
|
230
|
-
hub: hub,
|
|
231
|
-
channel: channel,
|
|
232
|
-
state: config.state,
|
|
233
|
-
};
|
|
234
|
-
let r = fun instanceof Function ? fun(connector, args) : fun.run(connector, args);
|
|
235
|
-
if (r instanceof Promise) {
|
|
236
|
-
await comm.later(call_id);
|
|
237
|
-
r.then(
|
|
238
|
-
(value) => comm.returnOrFail(fun, call_id, value),
|
|
239
|
-
(reason) => comm.error(call_id, reason.message, reason.stack));
|
|
240
|
-
} else {
|
|
241
|
-
await comm.returnOrFail(fun, call_id, r);
|
|
242
|
-
}
|
|
243
|
-
} catch (reason) {
|
|
244
|
-
await comm.error(call_id, reason.message, reason.stack);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
class FFIComm {
|
|
250
|
-
constructor(hub, channel) {
|
|
251
|
-
this.hub = hub;
|
|
252
|
-
this.channel = channel;
|
|
253
|
-
}
|
|
254
|
-
later(call_id) {
|
|
255
|
-
let r_payload =
|
|
256
|
-
{ call_id: call_id,
|
|
257
|
-
};
|
|
258
|
-
let response = this.hub.newLocalMessage("msg_ffi_later", "starter", r_payload);
|
|
259
|
-
return this.channel.publish("/local/ffi/return", response, false);
|
|
260
|
-
}
|
|
261
|
-
return_(fun, call_id, result) {
|
|
262
|
-
let r_payload =
|
|
263
|
-
{ call_id: call_id,
|
|
264
|
-
value: fun.useJsonDecoder ? result : encode(result)
|
|
265
|
-
};
|
|
266
|
-
let response = this.hub.newLocalMessage("msg_ffi_return", "starter", r_payload);
|
|
267
|
-
return this.channel.publish("/local/ffi/return", response, false);
|
|
268
|
-
}
|
|
269
|
-
error(call_id, message, stack) {
|
|
270
|
-
let r_payload =
|
|
271
|
-
{ call_id: call_id,
|
|
272
|
-
message: message,
|
|
273
|
-
stack: stack === undefined ? [] : stack,
|
|
274
|
-
};
|
|
275
|
-
let response = this.hub.newLocalMessage("msg_ffi_error", "starter", r_payload);
|
|
276
|
-
this.channel.publish("/local/ffi/return", response, false);
|
|
277
|
-
}
|
|
278
|
-
returnOrFail(fun, call_id, result) {
|
|
279
|
-
if (fun.canFail) {
|
|
280
|
-
var name, arg;
|
|
281
|
-
if (result instanceof Case) {
|
|
282
|
-
name = result.name;
|
|
283
|
-
arg = result.arg;
|
|
284
|
-
} else if (fun.useJsonDecoder && result._rmx_type && result._rmx_type.startsWith("{tag}case:")) {
|
|
285
|
-
name = result._rmx_type.substring(10);
|
|
286
|
-
arg = result._rmx_value;
|
|
287
|
-
} else {
|
|
288
|
-
return this.error(call_id, "expected result from local FFI that can fail");
|
|
289
|
-
}
|
|
290
|
-
switch (name) {
|
|
291
|
-
case "ok":
|
|
292
|
-
return this.return_(fun, call_id, arg);
|
|
293
|
-
case "error":
|
|
294
|
-
return this.error(call_id, arg);
|
|
295
|
-
default:
|
|
296
|
-
return this.error(call_id, "expected result from local FFI that can fail, found case value " + name);
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
return this.return_(fun, call_id, result);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
12
|
+
globalThis.GetMachineWASMWorker = GetMachineWASMWorker;
|
|
303
13
|
|
|
304
|
-
|
|
14
|
+
export * from './common.js';
|
|
305
15
|
|
|
306
|
-
export { CreateVMID, StartWASM, StartWASM2,
|
|
307
|
-
Token, Case, Opaque
|
|
308
|
-
}
|