diff --git a/src/device/simulator.ts b/src/device/simulator.ts index 20d5cfa41..a7b9901e6 100644 --- a/src/device/simulator.ts +++ b/src/device/simulator.ts @@ -120,6 +120,11 @@ export type SensorStateKey = Extract< interface Config { language: string; translations: Record; + /** + * If true, the simulator waits for serial output to be acknowledged via + * acknowledgeSerialOutput rather than limiting it to the device's baud rate. + */ + serialFlowControl: boolean; } export interface DataLog { @@ -318,6 +323,19 @@ export class SimulatorDeviceConnection this.setStatus(ConnectionStatus.Disconnected); } + /** + * Acknowledge serial output once it's been processed, so the simulator can + * send more. + * + * @param length The total length of the serial data processed. + */ + acknowledgeSerialOutput(length: number): void { + // The iframe may have gone by the time the terminal finishes writing. + if (this.iframe()) { + this.postMessage("serial_output_ack", { length }); + } + } + async serialWrite(data: string): Promise { this.postMessage("serial_input", { data, diff --git a/src/serial/XTerm.tsx b/src/serial/XTerm.tsx index eab1df359..72f0e4def 100644 --- a/src/serial/XTerm.tsx +++ b/src/serial/XTerm.tsx @@ -14,6 +14,7 @@ import useIsUnmounted from "../common/use-is-unmounted"; import { backgroundColorTerm } from "../deployment/misc"; import { SerialData } from "@microbit/microbit-connection/usb"; import { parseTraceLine, useDevice } from "../device/device-hooks"; +import { SimulatorDeviceConnection } from "../device/simulator"; import { useSelection } from "../workbench/use-selection"; import { WebLinkProvider } from "./link-provider"; import { useCurrentTerminalRef } from "./serial-hooks"; @@ -104,9 +105,20 @@ const useManagedTermimal = ( customKeyEventHandler(e, tabOutRef) ); + // The simulator waits for us to acknowledge serial output so a busy + // program can't send it faster than we can write it. + const acknowledge = (length: number) => { + if (device instanceof SimulatorDeviceConnection) { + device.acknowledgeSerialOutput(length); + } + }; const serialListener = (event: SerialData) => { + const { length } = event.data; if (!isUnmounted()) { - terminal.write(event.data); + terminal.write(event.data, () => acknowledge(length)); + } else { + // Discarded, but still acknowledged so the simulator doesn't wait. + acknowledge(length); } }; const resetListener = () => { diff --git a/src/simulator/Simulator.tsx b/src/simulator/Simulator.tsx index 7c9b5b7eb..b8bb1b27a 100644 --- a/src/simulator/Simulator.tsx +++ b/src/simulator/Simulator.tsx @@ -45,10 +45,16 @@ const Simulator = ({ const staging = "https://python-simulator.usermbit.org/staging/simulator.html?flag=sw"; - // For testing with sim branches: - //const branch = "upgrade-mpy"; - //const url = new URL(`https://review-python-simulator.usermbit.org/${branch}/simulator.html`); - const url = new URL(stage === "PRODUCTION" ? production : staging); + // For testing with sim branches. + // TODO: remove before merge, once the simulator is released. + const branch: string | undefined = "serial-output-flow-control"; + const url = new URL( + branch + ? `https://review-python-simulator.usermbit.org/${branch}/simulator.html` + : stage === "PRODUCTION" + ? production + : staging + ); url.searchParams.append("color", brand500); return url.toString(); }, [brand500]); @@ -163,6 +169,8 @@ const updateTranslations = ( intl.formatMessage({ id: "simulator-" + k }), ]) ), + // The terminal acknowledges serial output (see XTerm.tsx). + serialFlowControl: true, }; simulator.configure(config); };