mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
fixed some stuff for headless targets and extension
This commit is contained in:
1 parent
b752da4528
commit
389fd02323
3 files changed
+64
-25
No files matched your search
+28
-3
@@ -69,7 +69,7 @@ export function _setKeyboardEvents(canvas: HTMLElement, callback: KeyboardCallba
|
||||
|
||||
type VideoCanvasOptions = { rotate?: number, overscan?: boolean, aspect?: number };
|
||||
|
||||
export class RasterVideo {
|
||||
class RasterVideoImpl {
|
||||
|
||||
mainElement: HTMLElement;
|
||||
width: number;
|
||||
@@ -167,7 +167,7 @@ export class RasterVideo {
|
||||
};
|
||||
}
|
||||
|
||||
export class VectorVideo extends RasterVideo {
|
||||
class VectorVideoImpl extends RasterVideoImpl {
|
||||
|
||||
persistenceAlpha = 0.5;
|
||||
jitter = 1.0;
|
||||
@@ -247,6 +247,28 @@ export function drawCrosshair(ctx: CanvasRenderingContext2D, x: number, y: numbe
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Platforms construct video and timers through these bindings, so a headless
|
||||
// host can swap in stand-ins with setVideoClasses (see src/tools/emutarget.ts).
|
||||
export type RasterVideo = RasterVideoImpl;
|
||||
export let RasterVideo = RasterVideoImpl;
|
||||
export type VectorVideo = VectorVideoImpl;
|
||||
export let VectorVideo = VectorVideoImpl;
|
||||
|
||||
export interface VideoClasses {
|
||||
RasterVideo: typeof RasterVideoImpl;
|
||||
VectorVideo: typeof VectorVideoImpl;
|
||||
AnimationTimer: typeof AnimationTimerImpl;
|
||||
}
|
||||
|
||||
/** Replace the classes platforms construct; returns the previous ones. */
|
||||
export function setVideoClasses(classes: Partial<VideoClasses>): VideoClasses {
|
||||
const previous = { RasterVideo, VectorVideo, AnimationTimer };
|
||||
if (classes.RasterVideo) RasterVideo = classes.RasterVideo;
|
||||
if (classes.VectorVideo) VectorVideo = classes.VectorVideo;
|
||||
if (classes.AnimationTimer) AnimationTimer = classes.AnimationTimer;
|
||||
return previous;
|
||||
}
|
||||
|
||||
export class RAM {
|
||||
mem: Uint8Array;
|
||||
constructor(size: number) {
|
||||
@@ -266,7 +288,7 @@ export class EmuHalt extends Error {
|
||||
|
||||
export var useRequestAnimationFrame: boolean = false;
|
||||
|
||||
export class AnimationTimer {
|
||||
class AnimationTimerImpl {
|
||||
|
||||
callback;
|
||||
running: boolean = false;
|
||||
@@ -347,6 +369,9 @@ export class AnimationTimer {
|
||||
}
|
||||
}
|
||||
|
||||
export type AnimationTimer = AnimationTimerImpl;
|
||||
export let AnimationTimer = AnimationTimerImpl;
|
||||
|
||||
// TODO: move to util?
|
||||
|
||||
export function dumpRAM(ram: ArrayLike<number>, ramofs: number, ramlen: number): string {
|
||||
|
||||
+31
-19
@@ -24,12 +24,17 @@ import { CPU6809 } from "../common/cpu/6809";
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import * as emu from "../common/emu";
|
||||
import { getRootBasePlatform } from "../common/util";
|
||||
import { importPlatform } from "../platform/_index";
|
||||
import { mockAudio, mockDOM, mockFetch, mockGlobals } from "./nodemock";
|
||||
|
||||
export interface VideoOutput {
|
||||
pixels: Uint32Array;
|
||||
width: number;
|
||||
height: number;
|
||||
/** display rotation in degrees (RasterVideo's `rotate` option) */
|
||||
rotate?: number;
|
||||
/** display aspect ratio, if not width/height */
|
||||
aspect?: number;
|
||||
}
|
||||
|
||||
export interface DebugSection {
|
||||
@@ -81,19 +86,18 @@ function installHeadlessVideo() {
|
||||
// and put the real ones back afterwards. Leaving the stubs installed would
|
||||
// leak into every other module sharing the (cached) emu import -- which is
|
||||
// exactly what happens when mocha reuses a worker across test files.
|
||||
const original = {
|
||||
RasterVideo: (emu as any).RasterVideo,
|
||||
VectorVideo: (emu as any).VectorVideo,
|
||||
AnimationTimer: (emu as any).AnimationTimer,
|
||||
};
|
||||
let pixels: Uint32Array | null = null;
|
||||
let params: { width: number, height: number } | null = null;
|
||||
(emu as any).RasterVideo = function (_el: any, width: number, height: number) {
|
||||
let params: { width: number, height: number, rotate?: number, aspect?: number } | null = null;
|
||||
let frameRate = 60;
|
||||
const RasterVideo: any = function (_el: any, width: number, height: number, options?: { rotate?: number, aspect?: number }) {
|
||||
const buffer = new ArrayBuffer(width * height * 4);
|
||||
const datau8 = new Uint8Array(buffer);
|
||||
const datau32 = new Uint32Array(buffer);
|
||||
params = { width, height };
|
||||
pixels = datau32;
|
||||
// the first one is the screen; later ones are debug views (nes nametables)
|
||||
if (!pixels) {
|
||||
params = { width, height, rotate: options?.rotate, aspect: options?.aspect };
|
||||
pixels = datau32;
|
||||
}
|
||||
this.create = function () { this.width = width; this.height = height; };
|
||||
this.setKeyboardEvents = function () { };
|
||||
this.getFrameData = function () { return datau32; };
|
||||
@@ -108,26 +112,27 @@ function installHeadlessVideo() {
|
||||
this.putImageData = function () { };
|
||||
this.style = {};
|
||||
};
|
||||
(emu as any).VectorVideo = function () {
|
||||
const VectorVideo: any = function () {
|
||||
this.create = function () { this.drawops = 0; };
|
||||
this.setKeyboardEvents = function () { };
|
||||
this.clear = function () { };
|
||||
this.drawLine = function () { this.drawops++; };
|
||||
};
|
||||
(emu as any).AnimationTimer = function () {
|
||||
const AnimationTimer: any = function (fps: number) {
|
||||
if (fps > 0) frameRate = fps;
|
||||
this.running = false;
|
||||
this.start = function () { };
|
||||
this.stop = function () { };
|
||||
this.isRunning = function () { return this.running; };
|
||||
};
|
||||
const original = emu.setVideoClasses({ RasterVideo, VectorVideo, AnimationTimer });
|
||||
return {
|
||||
get(): VideoOutput | null {
|
||||
return pixels && params ? { pixels, width: params.width, height: params.height } : null;
|
||||
return pixels && params ? { pixels, ...params } : null;
|
||||
},
|
||||
get frameRate() { return frameRate; },
|
||||
restore() {
|
||||
(emu as any).RasterVideo = original.RasterVideo;
|
||||
(emu as any).VectorVideo = original.VectorVideo;
|
||||
(emu as any).AnimationTimer = original.AnimationTimer;
|
||||
emu.setVideoClasses(original);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -214,6 +219,8 @@ export class EmuTarget {
|
||||
}
|
||||
|
||||
getVideo(): VideoOutput | null { return this.video ? this.video.get() : null; }
|
||||
/** Frames per second the platform's timer asked for (60 if unknown). */
|
||||
get frameRate(): number { return this.video ? this.video.frameRate : 60; }
|
||||
saveState(): EmuState | null { return this.platform.saveState ? this.platform.saveState() : null; }
|
||||
|
||||
getDebugInfo(): DebugSection[] {
|
||||
@@ -329,7 +336,8 @@ export class EmuTarget {
|
||||
export async function loadPlatform(platformId: string): Promise<EmuTarget> {
|
||||
installNodeMocks();
|
||||
const baseId = getRootBasePlatform(platformId);
|
||||
await import('../platform/' + baseId);
|
||||
// the explicit import switch, so bundlers can find every platform module
|
||||
await importPlatform(baseId);
|
||||
const PlatformClass = PLATFORMS[platformId] || PLATFORMS[baseId];
|
||||
if (!PlatformClass) {
|
||||
throw new Error(`Platform '${platformId}' not found. Available: ${Object.keys(PLATFORMS).sort().join(', ')}`);
|
||||
@@ -339,12 +347,16 @@ export async function loadPlatform(platformId: string): Promise<EmuTarget> {
|
||||
|
||||
let mocksInstalled = false;
|
||||
|
||||
/** Stub out the browser APIs that the platform modules expect. */
|
||||
export function installNodeMocks() {
|
||||
/**
|
||||
* Stub out the browser APIs that the platform modules expect. fetch() reads
|
||||
* files (BIOS images, wasm cores) relative to `rootDir`. Only the first call
|
||||
* takes effect.
|
||||
*/
|
||||
export function installNodeMocks(rootDir: string = process.cwd()) {
|
||||
if (mocksInstalled) return;
|
||||
mocksInstalled = true;
|
||||
mockGlobals();
|
||||
mockAudio();
|
||||
mockFetch();
|
||||
mockFetch(rootDir);
|
||||
mockDOM();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import { SampledAudioSink } from "../common/devices";
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export function mockGlobals() {
|
||||
global.atob = require('atob');
|
||||
@@ -38,9 +39,10 @@ export function mockAudio() {
|
||||
global.PsgDeviceChannel = NullPsgDeviceChannel;
|
||||
}
|
||||
|
||||
export function mockFetch() {
|
||||
global.fetch = async (path, init) => {
|
||||
let bin = fs.readFileSync(path);
|
||||
/** Serve fetch() from the filesystem, relative to `rootDir`. */
|
||||
export function mockFetch(rootDir: string = process.cwd()) {
|
||||
global.fetch = async (url, init) => {
|
||||
let bin = fs.readFileSync(path.resolve(rootDir, String(url)));
|
||||
let blob = new Blob([bin]);
|
||||
return new Response(blob);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user