Skip to content
Snippets Groups Projects
Verified Commit 2352edfd authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

feat: support logger and printer in Web

This makes uses of WASM to JS pipeline.
parent 04e20222
No related branches found
No related tags found
No related merge requests found
......@@ -50,9 +50,12 @@ const BACKGROUNDS = [
background: background,
backgrounds: BACKGROUNDS
});
await emulator.main({ romUrl: romUrl });
// sets the emulator in the global scope this is useful
// to be able to access the emulator from global functions
window.emulator = emulator;
// starts the emulator with the provided ROM URL, this is
// going to run the main emulator (infinite) loop
await emulator.main({ romUrl: romUrl });
})();
......@@ -2,4 +2,5 @@ export * from "./audio-gb/audio-gb";
export * from "./debug/debug";
export * from "./help/help";
export * from "./registers-gb/registers-gb";
export * from "./serial-section/serial-section";
export * from "./tiles-gb/tiles-gb";
.serial-section .printer > .printer-lines {
font-size: 0px;
}
.serial-section .printer > .printer-lines > .printer-line {
display: block;
}
.serial-section .printer > .printer-lines > .placeholder {
font-size: initial;
}
import React, { FC, useEffect, useRef, useState } from "react";
import { ButtonSwitch, Info, Pair, PanelTab } from "emukit";
import { GameboyEmulator, bufferToDataUrl } from "../../../ts";
import "./serial-section.css";
const DEVICE_ICON: { [key: string]: string } = {
Null: "🛑",
Logger: "📜",
Printer: "🖨️"
};
export type LoggerCallback = (data: Uint8Array) => void;
export type PrinterCallback = (imageBuffer: Uint8Array) => void;
type SerialSectionProps = {
emulator: GameboyEmulator;
style?: string[];
onLogger?: (onLoggerData: LoggerCallback) => void;
onPrinter?: (onPrinterData: PrinterCallback) => void;
};
export const SerialSection: FC<SerialSectionProps> = ({
emulator,
style = [],
onLogger,
onPrinter
}) => {
const classes = () => ["serial-section", ...style].join(" ");
const [loggerData, setLoggerData] = useState<string>();
const [printerImageUrls, setPrinterImageUrls] = useState<string[]>();
const loggerDataRef = useRef<string[]>([]);
const printerDataRef = useRef<string[]>([]);
const loggerRef = useRef<HTMLDivElement>(null);
const imagesRef = useRef<HTMLDivElement>(null);
const onLoggerData = (data: Uint8Array) => {
const byte = data[0];
const charByte = String.fromCharCode(byte);
loggerDataRef.current.push(charByte);
setLoggerData(loggerDataRef.current.join(""));
};
const onPrinterData = (imageBuffer: Uint8Array) => {
const imageUrl = bufferToDataUrl(imageBuffer, 160);
printerDataRef.current.unshift(imageUrl);
setPrinterImageUrls([...printerDataRef.current]);
};
useEffect(() => {
if (loggerRef.current) {
onLogger && onLogger(onLoggerData);
}
}, [loggerRef, loggerRef.current]);
useEffect(() => {
if (imagesRef.current) {
onPrinter && onPrinter(onPrinterData);
}
}, [imagesRef, imagesRef.current]);
const onEngineChange = (option: string) => {
switch (option) {
case "Null":
emulator.loadNullDevice();
break;
case "Logger":
emulator.loadLoggerDevice();
break;
case "Printer":
emulator.loadPrinterDevice();
break;
}
const optionIcon = DEVICE_ICON[option] ?? "";
emulator.handlers.showToast?.(
`${optionIcon} ${option} attached to the serial port & active`
);
};
const getTabs = () => {
return [
<Info>
<Pair
key="button-device"
name={"Device"}
valueNode={
<ButtonSwitch
options={["Null", "Logger", "Printer"]}
size={"large"}
style={["simple"]}
onChange={onEngineChange}
/>
}
/>
<Pair key="baud-rate" name={"Baud Rate"} value={"1 KB/s"} />
</Info>,
<div className="logger" ref={loggerRef}>
<div className="logger-data">
{loggerData || "Logger contents are empty."}
</div>
</div>,
<div className="printer" ref={imagesRef}>
<div className="printer-lines">
{printerImageUrls ? (
printerImageUrls.map((url, index) => (
<img
key={index}
className="printer-line"
src={url}
/>
))
) : (
<span className="placeholder">
Printer contents are empty.
</span>
)}
</div>
</div>
];
};
const getTabNames = () => {
return ["Settings", "Logger", "Printer"];
};
return (
<div className={classes()}>
<PanelTab
tabs={getTabs()}
tabNames={getTabNames()}
selectors={true}
/>
</div>
);
};
export default SerialSection;
<svg width="48px" height="48px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-labelledby="swapHorizontalIconTitle" stroke="#ffffff" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter" fill="none" color="#ffffff"> <title id="swapHorizontalIconTitle">Swap items (horizontally)</title> <path d="M16 4L19 7L16 10"/> <path d="M4 7L18 7"/> <path d="M7 20L4 17L7 14"/> <path d="M19 17L5 17"/> </svg>
\ No newline at end of file
import {
AudioSpecs,
BenchmarkResult,
SectionInfo,
Compilation,
Compiler,
DebugPanel,
......@@ -16,8 +17,16 @@ import {
Size
} from "emukit";
import { PALETTES, PALETTES_MAP } from "./palettes";
import { base64ToBuffer, bufferToBase64, bufferToDataUrl } from "./util";
import { DebugAudio, DebugVideo, HelpFaqs, HelpKeyboard } from "../react";
import { base64ToBuffer, bufferToBase64 } from "./util";
import {
DebugAudio,
DebugVideo,
HelpFaqs,
HelpKeyboard,
LoggerCallback,
PrinterCallback,
SerialSection,
} from "../react";
import {
Cartridge,
......@@ -124,6 +133,10 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
*/
private extraSettings: Record<string, string> = {};
private onLoggerData: ((data: Uint8Array) => void) | null = null;
private onPrinterData: ((imageBuffer: Uint8Array) => void) | null = null;
constructor(extraSettings = {}) {
super();
this.extraSettings = extraSettings;
......@@ -388,7 +401,7 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
// a valid state ready to be used
this.gameBoy.reset();
this.gameBoy.load_boot_default();
this.gameBoy.load_printer_ws();
this.gameBoy.load_null_ws();
const cartridge = this.gameBoy.load_rom_ws(romData);
// updates the name of the currently selected engine
......@@ -465,6 +478,22 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
];
}
get sections(): SectionInfo[] {
return [
{
name: "Serial",
icon: require("../res/serial.svg"),
node: SerialSection({
emulator: this,
onLogger: (onLoggerData: LoggerCallback) =>
(this.onLoggerData = onLoggerData),
onPrinter: (onPrinterData: PrinterCallback) =>
(this.onPrinterData = onPrinterData)
})
}
];
}
get help(): HelpPanel[] {
return [
{
......@@ -740,6 +769,26 @@ export class GameboyEmulator extends EmulatorBase implements Emulator {
this.storeSettings();
}
loadNullDevice() {
this.gameBoy?.load_null_ws();
}
loadLoggerDevice() {
this.gameBoy?.load_logger_ws();
}
loadPrinterDevice() {
this.gameBoy?.load_printer_ws();
}
onLoggerDevice(data: Uint8Array) {
this.onLoggerData?.(data);
}
onPrinterDevice(imageBuffer: Uint8Array) {
this.onPrinterData?.(imageBuffer);
}
/**
* Tries to load game RAM from the `localStorage` using the
* current cartridge title as the name of the item and
......@@ -810,6 +859,7 @@ declare global {
interface Window {
emulator: GameboyEmulator;
panic: (message: string) => void;
loggerCallback: (data: Uint8Array) => void;
printerCallback: (imageBuffer: Uint8Array) => void;
}
......@@ -822,9 +872,12 @@ window.panic = (message: string) => {
console.error(message);
};
window.loggerCallback = (data: Uint8Array) => {
window.emulator.onLoggerDevice(data);
};
window.printerCallback = (imageBuffer: Uint8Array) => {
const imageUrl = bufferToDataUrl(imageBuffer, 160);
console.image(imageUrl, imageBuffer.length / 160 / 3);
window.emulator.onPrinterDevice(imageBuffer);
};
console.image = (url: string, size = 80) => {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment