@devicescript/vm
Version:
DeviceScript Virtual Machine
424 lines • 13.4 kB
JavaScript
// devs_timeout === undefined - program is not running
// devs_timeout === null - the C code is executing, program running
// devs_timeout is number - we're waiting for timeout, program running
var devs_timeout = undefined;
function copyToHeap(buf, fn) {
const ptr = Module._malloc(buf.length);
Module.HEAPU8.set(buf, ptr);
const r = fn(ptr);
Module._free(ptr);
return r;
}
function bufferConcat(a, b) {
const r = new Uint8Array(a.length + b.length);
r.set(a, 0);
r.set(b, a.length);
return r;
}
export var Exts;
(function (Exts) {
/**
* Debug output and stack traces are sent here.
*/
Exts.dmesg = (s) => console.debug(" " + s);
/**
* Logging function
*/
Exts.log = console.log;
/**
* Error logging function
*/
Exts.error = console.error;
/**
* Callback to invoke when a packet needs to be handled by the virtual machine
* TODO: frame or packet?
* @param pkt a Jacdac frame
*/
function handlePacket(pkt) {
if (devs_timeout) {
try {
copyToHeap(pkt, Module._jd_em_frame_received);
}
catch (_a) { }
clearDevsTimeout();
process();
}
}
Exts.handlePacket = handlePacket;
/**
* Starts a packet transport over a TCP socket in a node.js application
* @param require module resolution function, requires "net" package
* @param host socket url host
* @param port socket port
*/
function setupNodeTcpSocketTransport(require, host, port) {
return new Promise((resolve, reject) => {
const net = require("net");
let sock = null;
const send = (data) => {
let buf;
if (data.length >= 0xff) {
buf = new Uint8Array(3 + data.length);
buf[0] = 0xff;
buf[1] = data.length & 0xff;
buf[2] = data.length >> 8;
buf.set(data, 3);
}
else {
buf = new Uint8Array(1 + data.length);
buf[0] = data.length;
buf.set(data, 1);
}
if (sock)
sock.write(buf);
};
const disconnect = (err) => {
Module.log("disconnect", err === null || err === void 0 ? void 0 : err.message);
if (sock)
try {
sock.end();
}
catch (_a) {
}
finally {
sock = undefined;
}
if (resolve) {
resolve = null;
reject(new Error(`can't connect to ${host}:${port}`));
}
};
const close = () => disconnect(undefined);
Module["sendPacket"] = send;
sock = net.createConnection(port, host, () => {
Module.log(`connected to ${host}:${port}`);
const f = resolve;
if (f) {
resolve = null;
reject = null;
f({ close });
}
});
sock.on("error", disconnect);
sock.on("end", disconnect);
sock.setNoDelay();
let acc = null;
sock.on("data", (buf) => {
if (acc) {
buf = bufferConcat(acc, buf);
acc = null;
}
else {
buf = new Uint8Array(buf);
}
while (buf) {
const endp = buf[0] + 1;
if (buf.length >= endp) {
const pkt = buf.slice(1, endp);
if (buf.length > endp)
buf = buf.slice(endp);
else
buf = null;
Module.handlePacket(pkt);
}
else {
acc = buf;
buf = null;
}
}
});
});
}
Exts.setupNodeTcpSocketTransport = setupNodeTcpSocketTransport;
/**
* Starts a packet transport over a WebSocket using arraybuffer binary type.
* @param url socket url
* @param port socket port
*/
function setupWebsocketTransport(url, protocols) {
return new Promise((resolve, reject) => {
let sock = new WebSocket(url, protocols);
if (sock.binaryType != "arraybuffer")
sock.binaryType = "arraybuffer";
const send = (data) => {
if (sock && sock.readyState == WebSocket.OPEN) {
sock.send(data);
return 0;
}
else {
return -1;
}
};
const disconnect = (err) => {
Module.log("disconnect", err === null || err === void 0 ? void 0 : err.message);
if (sock)
try {
sock.close();
}
catch (_a) {
}
finally {
sock = undefined;
}
if (resolve) {
resolve = null;
reject(new Error(`can't connect to ${url}; ${err === null || err === void 0 ? void 0 : err.message}`));
}
};
const close = () => disconnect(undefined);
Module["sendPacket"] = send;
sock.onopen = () => {
Module.log(`connected to ${url}`);
const f = resolve;
if (f) {
resolve = null;
reject = null;
f({ close });
}
};
sock.onerror = disconnect;
sock.onclose = disconnect;
sock.onmessage = ev => {
const data = ev.data;
if (typeof data == "string") {
Module.error("got string msg");
return;
}
else {
const pkt = new Uint8Array(ev.data);
Module.handlePacket(pkt);
}
};
});
}
Exts.setupWebsocketTransport = setupWebsocketTransport;
/**
* Utility that converts a base64-encoded buffer into a Uint8Array
* TODO: nobody is using this?
* @param s
* @returns
*/
function b64ToBin(s) {
s = atob(s);
const r = new Uint8Array(s.length);
for (let i = 0; i < s.length; ++i)
r[i] = s.charCodeAt(i);
return r;
}
Exts.b64ToBin = b64ToBin;
/**
* Deploys a DeviceScript bytecode to the virtual machine
* @param binary
* @returns error code, 0 if deployment is successful
*/
function devsDeploy(binary) {
return copyToHeap(binary, ptr => Module._jd_em_devs_deploy(ptr, binary.length));
}
Exts.devsDeploy = devsDeploy;
/**
* Verifies the format and version of the bytecode
* @param binary DeviceScript bytecode
* @returns error code, 0 if verification is successful
*/
function devsVerify(binary) {
return copyToHeap(binary, ptr => Module._jd_em_devs_verify(ptr, binary.length));
}
Exts.devsVerify = devsVerify;
/**
* Deploys to the first virtual machine on Jacdac stack (experimental)
* @internal
* @alpha
* @param binary
* @returns error code, 0 if deployment is successful
*/
function devsClientDeploy(binary) {
// this will call exit(0) when done
const ptr = Module._malloc(binary.length);
Module.HEAPU8.set(binary, ptr);
return Module._jd_em_devs_client_deploy(ptr, binary.length);
}
Exts.devsClientDeploy = devsClientDeploy;
/**
* Initalises the virtual machine data structure.
*/
function devsInit() {
Module._jd_em_init();
}
Exts.devsInit = devsInit;
/**
* Enables/disables GC stress testing.
*/
function devsGcStress(en) {
Module._jd_em_devs_enable_gc_stress(en ? 1 : 0);
}
Exts.devsGcStress = devsGcStress;
/**
* Clear settings.
*/
function devsClearFlash() {
if (Module.flashSave)
Module.flashSave(new Uint8Array([0, 0, 0, 0]));
}
Exts.devsClearFlash = devsClearFlash;
function process() {
devs_timeout = null;
try {
const us = Module._jd_em_process();
devs_timeout = setTimeout(process, us / 1000);
}
catch (e) {
Module.error(e);
devsStop();
}
}
function clearDevsTimeout() {
if (devs_timeout)
clearInterval(devs_timeout);
devs_timeout = undefined;
}
/**
* Initializes and start the virtual machine (calls init).
*/
function devsStart() {
if (devs_timeout)
return;
Module.devsInit();
devs_timeout = setTimeout(process, 10);
}
Exts.devsStart = devsStart;
/**
* Stops the virtual machine
*/
function devsStop() {
clearDevsTimeout();
}
Exts.devsStop = devsStop;
/**
* Indicates if the virtual machine is running
* @returns true if the virtual machine is started.
*/
function devsIsRunning() {
return devs_timeout !== undefined;
}
Exts.devsIsRunning = devsIsRunning;
/**
* Specifices the virtual macine device id.
* @remarks
*
* Must be called before `devsStart`.
*
* @param id0 a hex-encoded device id string or the first 32bit of the device id
* @param id1 the second 32 bits of the device id, undefined if id0 is a string
*/
function devsSetDeviceId(id0, id1) {
if (devsIsRunning())
throw new Error("cannot change deviceid while running");
Module.devsInit();
if (typeof id0 == "string") {
if (id1 !== undefined)
throw new Error("invalid arguments");
const s = allocateUTF8(id0);
Module._jd_em_set_device_id_string(s);
Module._free(s);
}
else if (typeof id0 == "number" && typeof id1 == "number") {
Module._jd_em_set_device_id_2x_i32(id0, id1);
}
else {
throw new Error("invalid arguments");
}
}
Exts.devsSetDeviceId = devsSetDeviceId;
let currSock;
function sockClose() {
if (!currSock)
return -10;
currSock.end();
currSock = null;
return 0;
}
Exts.sockClose = sockClose;
function sockWrite(data, len) {
if (!currSock)
return -10;
const buf = Module.HEAPU8.slice(data, data + len);
currSock.write(buf);
return 0;
}
Exts.sockWrite = sockWrite;
function sockIsAvailable() {
try {
require("node:tls");
return true;
}
catch (_a) {
return false;
}
}
Exts.sockIsAvailable = sockIsAvailable;
function sockOpen(hostptr, port) {
const host = UTF8ToString(hostptr, 256);
const JD_CONN_EV_OPEN = 0x01;
const JD_CONN_EV_CLOSE = 0x02;
const JD_CONN_EV_ERROR = 0x03;
const JD_CONN_EV_MESSAGE = 0x04;
const isTLS = port < 0;
if (isTLS)
port = -port;
const name = `${isTLS ? "tls" : "tcp"}://${host}:${port}`;
currSock === null || currSock === void 0 ? void 0 : currSock.end();
currSock = null;
const sock = isTLS
? require("tls").connect({
host,
port,
})
: require("net").createConnection({ host, port });
currSock = sock;
currSock.once("connect", () => {
if (sock === currSock)
cb(JD_CONN_EV_OPEN);
});
currSock.on("data", (buf) => {
if (sock === currSock)
cb(JD_CONN_EV_MESSAGE, buf);
});
currSock.on("error", (err) => {
if (sock === currSock) {
cb(JD_CONN_EV_ERROR, `${name}: ${err.message}`);
currSock = null;
}
});
currSock.on("close", (hadError) => {
if (sock === currSock) {
cb(JD_CONN_EV_CLOSE);
currSock = null;
}
});
function cb(tp, arg) {
let len = arg ? arg.length : 0;
let ptr = 0;
if (typeof arg === "string") {
len = lengthBytesUTF8(arg);
ptr = allocateUTF8(arg);
}
else if (arg) {
ptr = Module._malloc(len);
Module.HEAPU8.set(arg, ptr);
}
Module._jd_em_tcpsock_on_event(tp, ptr, len);
if (ptr)
Module._free(ptr);
}
}
Exts.sockOpen = sockOpen;
})(Exts || (Exts = {}));
for (const kn of Object.keys(Exts)) {
;
Module[kn] = Exts[kn];
}
function factory() {
return null;
}
export default factory;
//# sourceMappingURL=wasmpre.js.map