Skip to content

Commit

Permalink
perf: switch to sharedarraybuffer to reduce serialization and messagi…
Browse files Browse the repository at this point in the history
…ng (#14)
  • Loading branch information
sverben authored Sep 19, 2024
1 parent 1250e7c commit 460324b
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 64 deletions.
94 changes: 67 additions & 27 deletions libserial/LibSerial.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,24 @@ void errorCallback() {


EM_JS(void, write_data, (unsigned char* buf, int len), {
var jsBuffer = new Uint8Array(Module.HEAPU8.buffer, buf, len);
window.avrDudeWorker.postMessage({ type: 'write', data: jsBuffer });
window.avrdudeLog = [...window.avrdudeLog, "Sending: " + Array.from(jsBuffer).join(",")];
let writeAddress = window.writeAddressBuf[0];
let written = 0;

while (written !== len) {
const initialLength = Math.min(len - written, window.writeBuffer.length - writeAddress);
const data = new Uint8Array(Module.HEAPU8.buffer.slice(buf + written, buf + written + initialLength));
window.writeBuffer.set(data, writeAddress);
window.avrdudeLog = [...window.avrdudeLog, "Sending: " + Array.from(data).join(",")];

written += initialLength;
writeAddress += initialLength;

if (writeAddress === window.writeBuffer.length) {
writeAddress = 3;
};
};

window.writeAddressBuf[0] = writeAddress;
});


Expand All @@ -36,36 +51,44 @@ EM_ASYNC_JS(void, clear_read_buffer, (int timeoutMs), {
resolve();
};
});
window.readAddress = 3;
window.avrdudeLog = [...window.avrdudeLog, "Read buffer cleared"];
});

EM_ASYNC_JS(void, read_data, (int timeoutMs, int length), {
window.avrDudeWorker.postMessage({ type: 'read', timeout: timeoutMs, requiredBytes: length });
const data = await new Promise((resolve, _) => {
window.avrDudeWorker.onmessage = (event) => {
// check if the response type is an error
if (event.data.type === "error") {
window.funcs._errorCallback();
} else if (event.data.type == "read") {
resolve(event.data);
}
const result = new Uint8Array(length);
let read = 0;
let end = Date.now() + timeoutMs;

while (read !== length && end > Date.now()) {
if (window.readAddress === window.readAddressBuf[0]) continue;

let targetAddress = window.readAddressBuf[0];
if (targetAddress < window.readAddress) {
targetAddress = window.readBuffer.length;
};
});
const result = data.result;

if (result instanceof Uint8Array && result.length > 0) {
//console.log("Received: ", result);
// convert data into an readable string formated like this 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
const printResult = Array.from(result).join(",");
window["avrdudeLog"] = [...window["avrdudeLog"], "Received: " + printResult];
const ptr = window.funcs._malloc(result.length * Uint8Array.BYTES_PER_ELEMENT);
window.funcs.HEAPU8.set(result, ptr);

// Call the C++ function with the pointer and the length of the array
window.funcs._dataCallback(ptr, result.length);
} else {
targetAddress = Math.min(targetAddress, window.readAddress + length - read);

result.set(window.readBuffer.slice(window.readAddress, targetAddress), read);
read += targetAddress - window.readAddress;

window.readAddress = targetAddress;
if (window.readAddress === window.readBuffer.length) {
window.readAddress = 3;
}
}

if (read === 0) {
window["avrdudeLog"] = [...window["avrdudeLog"], "Timeout"];
return;
}

const ptr = window.funcs._malloc(read * Uint8Array.BYTES_PER_ELEMENT);
const data = result.slice(0, read);
window.funcs.HEAPU8.set(result.slice(0, read), ptr);
window["avrdudeLog"] = [...window["avrdudeLog"], "Received: " + Array.from(data).join(",")];

window.funcs._dataCallback(ptr, read);
});

// clang-format off
Expand Down Expand Up @@ -109,7 +132,15 @@ EM_ASYNC_JS(void, open_serial_port, (int baudRateInt), {
}
}

worker.postMessage({ type: 'init', options: serialOpts, port: portNumber });
const writeBuffer = new SharedArrayBuffer(4096);
const readBuffer = new SharedArrayBuffer(4096);
worker.postMessage({
type: 'init',
options: serialOpts,
port: portNumber,
writeBuffer, readBuffer,
});

await new Promise(resolve => {
worker.onmessage = (event) => {
if (event.data.type === "error") {
Expand All @@ -119,6 +150,15 @@ EM_ASYNC_JS(void, open_serial_port, (int baudRateInt), {
};
});

// Initialize read and write buffers with address
window.writeBuffer = new Uint8Array(writeBuffer);
window.readBuffer = new Uint8Array(readBuffer);
window.writeAddressBuf = new Uint16Array(writeBuffer);
window.readAddressBuf = new Uint16Array(readBuffer);
window.writeAddressBuf[0] = 3;
window.readAddressBuf[0] = 3;
window.readAddress = 3;

// open the port with the correct baud rate
window.avrDudeWorker = worker;
window.activePort = port;
Expand Down
88 changes: 52 additions & 36 deletions libserial/avrdude-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,59 @@ let opts;
let writer;
let reader;

let buffer = new Uint8Array([])
let onData
let available = new Promise(resolve => onData = resolve)
let continuousRead = null
let writeBuffer
let readBuffer
let writeAddressBuf
let readAddressBuf

const readPromise = (customReader) => new Promise(async () => {
while (true) {
const { value, done } = await customReader.read()
if (done) break

buffer = new Uint8Array([...buffer, ...value])
onData()
let address = readAddressBuf[0];
let read = 0;

while (read !== value.length) {
const initialLength = Math.min(value.length, readBuffer.length - address);
readBuffer.set(value.slice(read, read + initialLength), address);
read += initialLength;
address += initialLength;

if (address === readBuffer.length) {
address = 3;
}
}
readAddressBuf[0] = address;

onData && onData()
}
})

function readFromBuffer(currentAddress, targetAddress, buffer) {
if (currentAddress < targetAddress) {
return buffer.slice(currentAddress, targetAddress)
}

const array = new Uint8Array(buffer.length - currentAddress + targetAddress - 3)
array.set(buffer.slice(currentAddress))
array.set(buffer.slice(3, targetAddress), buffer.length - currentAddress)

return array
}

addEventListener('message', async msg => {
try {
const data = msg.data

switch (data.type) {
case 'write': {
await writer.write(data.data)
break
}
case 'read': {
const neededBytes = data.requiredBytes
// await available
// available = new Promise(resolve => onData = resolve)
let dataBuffer = new Uint8Array([])

while (dataBuffer.length < neededBytes) {
if (buffer.length > 0) {
// only take the needed bytes
const bytesToTake = Math.min(neededBytes - dataBuffer.length, buffer.length)
dataBuffer = new Uint8Array([...dataBuffer, ...buffer.slice(0, bytesToTake)])
buffer = buffer.slice(bytesToTake)
continue
}
await available
available = new Promise(resolve => onData = resolve)
}

postMessage({type: 'read', result: dataBuffer})
break
}
case 'clear-read-buffer': {
const timeoutPromise = new Promise(resolve => setTimeout(resolve, data.timeout))
// await available
// available = new Promise(resolve => onData = resolve)
let result = await Promise.race([timeoutPromise, available])
available = new Promise(resolve => onData = resolve)
buffer = new Uint8Array([])
const onDone = new Promise(resolve => onData = resolve)

await Promise.race([timeoutPromise, onDone])
readAddressBuf[0] = 3

postMessage({type: 'clear-read-buffer'})
break
Expand All @@ -71,11 +70,28 @@ addEventListener('message', async msg => {
port = new WebUSBSerial(device)
}

readBuffer = new Uint8Array(data.readBuffer)
writeBuffer = new Uint8Array(data.writeBuffer)
readAddressBuf = new Uint16Array(data.readBuffer)
writeAddressBuf = new Uint16Array(data.writeBuffer)

await port.open(data.options)
opts = data.options
writer = port.writable.getWriter()
reader = port.readable.getReader()
continuousRead = readPromise(reader)

let address = 3
setInterval(async () => {
if (writeAddressBuf[0] === address) return

const target = writeAddressBuf[0]
const data = readFromBuffer(address, target, writeBuffer)
await writer.write(data)

address = target
}, 0)

readPromise(reader).then()
postMessage({type: 'ready'})
break
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@leaphy-robotics/avrdude-webassembly",
"version": "1.6.2",
"version": "1.7.0",
"description": "An port of avrdude to the browser using WebAssembly",
"type": "module",
"scripts": {
Expand Down

0 comments on commit 460324b

Please sign in to comment.