diff --git a/libserial/LibSerial.c b/libserial/LibSerial.c index 4d1f59a3d..fbd76157f 100644 --- a/libserial/LibSerial.c +++ b/libserial/LibSerial.c @@ -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; }); @@ -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 @@ -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") { @@ -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; diff --git a/libserial/avrdude-worker.js b/libserial/avrdude-worker.js index f48dfbab3..026db9ae5 100644 --- a/libserial/avrdude-worker.js +++ b/libserial/avrdude-worker.js @@ -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 @@ -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 } diff --git a/package.json b/package.json index b8cf686ce..a207fc716 100644 --- a/package.json +++ b/package.json @@ -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": {