@remix_labs/machine-starter 1.1457.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/index.js ADDED
@@ -0,0 +1,306 @@
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
+ 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
+ };
82
+ worker.postMessage(config_msg, [ channel.port ]);
83
+ terminate(() => worker.terminate());
84
+ globalThis.MixSetMask = (m => {
85
+ worker.postMessage({ "_rmx_type": "msg_vm_logMask",
86
+ "mask": m
87
+ })
88
+ });
89
+ if (config.localFFIs)
90
+ setupLocalFFIs(hub, config); // don't await!
91
+ return worker;
92
+ })
93
+ }
94
+
95
+ class Token {
96
+ constructor(data, enabled, extras) {
97
+ // data: must be an Uint8Array
98
+ // enabled: a number with the bitset of enabled operations
99
+ // extras: a map
100
+ if (!data instanceof Uint8Array)
101
+ throw new Error("Token: data must be an Uint8Array");
102
+ if (typeof(enabled) != "number")
103
+ throw new Error("Token: enabled_ops must be a number");
104
+ this.data = data;
105
+ this.enabled = enabled;
106
+ this.extras = extras;
107
+ }
108
+ encode() {
109
+ return new Map([
110
+ [ "_rmx_type", "token" ],
111
+ [ "data", this.data ],
112
+ [ "enabled", this.enabled ],
113
+ [ "extras", this.extras ]
114
+ ]);
115
+ }
116
+ }
117
+
118
+ function decode_Token(m) {
119
+ return new Token(m.get("data"), m.get("enabled"), m.get("extras"))
120
+ }
121
+
122
+ class Case {
123
+ constructor(name, arg) {
124
+ if (typeof(name) != "string")
125
+ throw new Error("Case: name must be a string");
126
+ this.name = name;
127
+ this.arg = arg;
128
+ }
129
+ encode() {
130
+ return new Map([
131
+ [ "_rmx_type", "case" ],
132
+ [ "name", this.name ],
133
+ [ "arg", this.arg ],
134
+ ]);
135
+ }
136
+ }
137
+
138
+ function decode_Case(m) {
139
+ return new Case(m.get("name"), m.get("arg"))
140
+ }
141
+
142
+ class Opaque {
143
+ constructor(buf) {
144
+ if (!(buf instanceof Uint32Array))
145
+ throw new Error("Opaque: must be a Uint32Array");
146
+ this.data = buf;
147
+ }
148
+ encode() {
149
+ return new Map([
150
+ [ "_rmx_type", "opaque" ],
151
+ [ "data", this.data ],
152
+ ]);
153
+ }
154
+ }
155
+
156
+ function decode_Opaque(m) {
157
+ return new Opaque(m.get("data"))
158
+ }
159
+
160
+ function encode(val) {
161
+ switch (typeof(val)) {
162
+ case "object":
163
+ if (val === null) return val;
164
+ if (Array.isArray(val)) {
165
+ // array: map the array elements
166
+ val = val.map(encode)
167
+ } else if (Object.getPrototypeOf(val) === Object.prototype) {
168
+ // simple object: map the object elements
169
+ val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, encode(elem) ]))
170
+ } else if (val.encode instanceof Function) {
171
+ // map after encode(): map the map elements
172
+ val = new Map(Array.from(val.encode().entries()).map(([ key, elem ]) => [ key, encode(elem) ]))
173
+ };
174
+ return val;
175
+ default:
176
+ return val;
177
+ }
178
+ }
179
+
180
+ function decode(val) {
181
+ switch (typeof(val)) {
182
+ case "object":
183
+ if (val === null) return val;
184
+ if (Array.isArray(val)) {
185
+ // array: map the array elements
186
+ val = val.map(decode)
187
+ } else if (Object.getPrototypeOf(val) === Object.prototype) {
188
+ // simple object: map the object elements
189
+ val = Object.fromEntries(Object.entries(val).map(([ key, elem ]) => [ key, decode(elem) ]))
190
+ } else if (val instanceof Map) {
191
+ val = new Map(Array.from(val.entries()).map(([ key, elem ]) => [ key, decode(elem) ]))
192
+ let ty = val.get("_rmx_type");
193
+ switch (ty) {
194
+ case "token":
195
+ return decode_Token(val);
196
+ case "case":
197
+ return decode_Case(val);
198
+ case "opaque":
199
+ return decode_Opaque(val);
200
+ default:
201
+ throw new Error("decode: bad _rmx_type: " + ty);
202
+ }
203
+ };
204
+ return val;
205
+ default:
206
+ return val;
207
+ }
208
+ }
209
+
210
+ async function setupLocalFFIs(hub, config) {
211
+ let channel = await hub.newChannel();
212
+ let comm = new FFIComm(hub, channel);
213
+ let localFFIs = config.localFFIs;
214
+ await channel.setLocalSubTopic("/local/ffi/call");
215
+ await channel.setLocalPubTopic("/local/ffi/return");
216
+ let sub = await channel.subscribe("/local/ffi/call");
217
+ while (true) {
218
+ let msg = await sub.next();
219
+ let name = msg.payload.name;
220
+ let call_id = msg.payload.call_id;
221
+ let args = decode(msg.payload.args);
222
+ let fun = localFFIs[name];
223
+ try {
224
+ if (!fun)
225
+ throw new Error("no such local FFI: " + name);
226
+ let connector =
227
+ { call_id: call_id,
228
+ hub: hub,
229
+ channel: channel,
230
+ state: config.state,
231
+ };
232
+ let r = fun instanceof Function ? fun(connector, args) : fun.run(connector, args);
233
+ if (r instanceof Promise) {
234
+ await comm.later(call_id);
235
+ r.then(
236
+ (value) => comm.returnOrFail(fun, call_id, value),
237
+ (reason) => comm.error(call_id, reason.message, reason.stack));
238
+ } else {
239
+ await comm.returnOrFail(fun, call_id, r);
240
+ }
241
+ } catch (reason) {
242
+ await comm.error(call_id, reason.message, reason.stack);
243
+ }
244
+ }
245
+ }
246
+
247
+ class FFIComm {
248
+ constructor(hub, channel) {
249
+ this.hub = hub;
250
+ this.channel = channel;
251
+ }
252
+ later(call_id) {
253
+ let r_payload =
254
+ { call_id: call_id,
255
+ };
256
+ let response = this.hub.newLocalMessage("msg_ffi_later", "starter", r_payload);
257
+ return this.channel.publish("/local/ffi/return", response, false);
258
+ }
259
+ return_(fun, call_id, result) {
260
+ let r_payload =
261
+ { call_id: call_id,
262
+ value: fun.useJsonDecoder ? result : encode(result)
263
+ };
264
+ let response = this.hub.newLocalMessage("msg_ffi_return", "starter", r_payload);
265
+ return this.channel.publish("/local/ffi/return", response, false);
266
+ }
267
+ error(call_id, message, stack) {
268
+ let r_payload =
269
+ { call_id: call_id,
270
+ message: message,
271
+ stack: stack === undefined ? [] : stack,
272
+ };
273
+ let response = this.hub.newLocalMessage("msg_ffi_error", "starter", r_payload);
274
+ this.channel.publish("/local/ffi/return", response, false);
275
+ }
276
+ returnOrFail(fun, call_id, result) {
277
+ if (fun.canFail) {
278
+ var name, arg;
279
+ if (result instanceof Case) {
280
+ name = result.name;
281
+ arg = result.arg;
282
+ } else if (fun.useJsonDecoder && result._rmx_type && result._rmx_type.startsWith("{tag}case:")) {
283
+ name = result._rmx_type.substring(10);
284
+ arg = result._rmx_value;
285
+ } else {
286
+ return this.error(call_id, "expected result from local FFI that can fail");
287
+ }
288
+ switch (name) {
289
+ case "ok":
290
+ return this.return_(fun, call_id, arg);
291
+ case "error":
292
+ return this.error(call_id, arg);
293
+ default:
294
+ return this.error(call_id, "expected result from local FFI that can fail, found case value " + name);
295
+ }
296
+ } else {
297
+ return this.return_(fun, call_id, result);
298
+ }
299
+ }
300
+ }
301
+
302
+ // use worker.terminate() to shut a worker down!
303
+
304
+ export { CreateVMID, StartWASM, StartWASM2,
305
+ Token, Case, Opaque
306
+ }
package/node.js ADDED
@@ -0,0 +1,7 @@
1
+ import Worker from "web-worker";
2
+ global.Worker = Worker;
3
+ import WT from "worker_threads";
4
+ global.MessageChannel = WT.MessageChannel;
5
+ import Process from "process";
6
+ global.Process = Process;
7
+ export * from "./index.js";
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@remix_labs/machine-starter",
3
+ "version": "1.1457.0-dev",
4
+ "description": "start the groove",
5
+ "main": "node.js",
6
+ "browser": "index.js",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "author": "Remixlabs staff",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "@remix_labs/hub-client": "1.1457.0-dev",
15
+ "nanoid": "^3.1.12",
16
+ "web-worker": "^1.2.0"
17
+ },
18
+ "repository": "https://github.com/remixlabs/groovebox.git"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@remix_labs/machine-starter",
3
+ "version": "_FULLVERSION_",
4
+ "description": "start the groove",
5
+ "main": "node.js",
6
+ "browser": "index.js",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "author": "Remixlabs staff",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "@remix_labs/hub-client": "_FULLVERSION_",
15
+ "nanoid": "^3.1.12",
16
+ "web-worker": "^1.2.0"
17
+ },
18
+ "repository": "https://github.com/remixlabs/groovebox.git"
19
+ }
package/start.js ADDED
@@ -0,0 +1,54 @@
1
+ import Process from "process";
2
+ import { StartWASM2 } from "./node.js";
3
+ import * as Hub from "@remix_labs/hub-client";
4
+ if (Process.stdout._handle)
5
+ Process.stdout._handle.setBlocking(true);
6
+ if (Process.stderr._handle)
7
+ Process.stderr._handle.setBlocking(true);
8
+ Process.on("SIGINT", () => {
9
+ console.log("SIGINT");
10
+ Process.exit(1);
11
+ });
12
+ Process.on("SIGHUP", () => {
13
+ console.log("SIGHUP");
14
+ Process.exit(1);
15
+ });
16
+ globalThis.ThisIsNode = true;
17
+ let variant = Process.env["MIX_MACHINE_VARIANT"] || "JS";
18
+ let id = Process.env["MIX_MQTT_VMID"] || "prototest";
19
+ let baseURL = Process.env["MIX_BASEURL"];
20
+ let mqttURL = Process.env["MIX_MQTT_URL"] || (baseURL ? baseURL + "/x/broker.ws" : "ws://localhost:9025/");
21
+ baseURL = baseURL || "http://localhost:8001";
22
+ if (mqttURL.startsWith("http:")) {
23
+ mqttURL = "ws:" + mqttURL.substring(5);
24
+ }
25
+ if (mqttURL.startsWith("https:")) {
26
+ mqttURL = "wss:" + mqttURL.substring(5);
27
+ }
28
+ let mqttUser = Process.env["MIX_MQTT_USER"] || "dummy";
29
+ let mqttToken = Process.env["MIX_MQTT_TOKEN"] || "";
30
+ let noOutputViaMQTT = Process.env["MIX_MQTT_NO_OUTPUT"] !== undefined;
31
+ console.log("Machine params: variant=" + variant + " baseURL=" + baseURL, " mqttURL=" + mqttURL, " mqttUser=" + mqttUser, " mqttToken=" + mqttToken, " id=" + id, " noOutputViaMQTT=" + noOutputViaMQTT);
32
+ let w = new Hub.Worker();
33
+ w.configure({wsURL: mqttURL, user:mqttUser, token:mqttToken}).then(resp => {
34
+ if (variant == "BYTE") variant = "QCODE";
35
+ switch(variant) {
36
+ case "WASM":
37
+ case "WAT":
38
+ case "QCODE":
39
+ MixSetMask(64 + 256 + 512 + 1024);
40
+ StartWASM2(w, {
41
+ baseURL: baseURL,
42
+ org: "local",
43
+ workspace: "local",
44
+ vmID: id,
45
+ user: mqttUser,
46
+ token: mqttToken,
47
+ noOutputViaMQTT: noOutputViaMQTT,
48
+ machType: variant
49
+ });
50
+ break
51
+ default:
52
+ throw(new Error("unknown MIX_MACHINE_VARIANT: " + variant))
53
+ }
54
+ })