mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-24 08:02:27 +00:00
EmuHalt gets a 'normal' flag, fixed some problems in runpresets
This commit is contained in:
1 parent
e0a1e9a38f
commit
448c7d4f9d
7 files changed
+71
-12
No files matched your search
@@ -3820,7 +3820,7 @@ ARMCore.prototype.compileArm = function(instruction) {
|
||||
// test for UDF instruction
|
||||
if ((instruction & 0xfff000f0) == (0xe7f000f0|0)) {
|
||||
var immediate = instruction & 0x0000000f; // TODO: full range
|
||||
throw new EmuHalt("Program exited (" + immediate + ")");
|
||||
throw new EmuHalt("Program exited (" + immediate + ")", undefined, true);
|
||||
}
|
||||
|
||||
var address : AddressFunction = function() {
|
||||
|
||||
+8
-1
@@ -280,9 +280,16 @@ export class RAM {
|
||||
export class EmuHalt extends Error {
|
||||
$loc: SourceLocation;
|
||||
squelchError = true;
|
||||
constructor(msg: string, loc?: SourceLocation) {
|
||||
/**
|
||||
* true when the program stopped by design (e.g. a semihost exit), as opposed
|
||||
* to a fault the CPU reported (illegal opcode, watchdog, bad ROM). Hosts can
|
||||
* use this to tell "the program ended" from "the emulator gave up".
|
||||
*/
|
||||
normal: boolean;
|
||||
constructor(msg: string, loc?: SourceLocation, normal = false) {
|
||||
super(msg);
|
||||
this.$loc = loc;
|
||||
this.normal = normal;
|
||||
Object.setPrototypeOf(this, EmuHalt.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,12 @@ function installHeadlessVideo() {
|
||||
this.setKeyboardEvents = setKeyboardEvents;
|
||||
this.getFrameData = function () { return datau32; };
|
||||
this.getImageData = function () { return { data: datau8, width, height }; };
|
||||
// PCE (and other platforms) build their own ImageData from the canvas
|
||||
// context during start(), so hand out a real one
|
||||
this.createImageData = function (w: number, h: number) {
|
||||
if (w === width && h === height) return { data: datau8, width, height };
|
||||
return { data: new Uint8ClampedArray(w * h * 4), width: w, height: h };
|
||||
};
|
||||
this.updateFrame = function () { };
|
||||
this.clearRect = function () { };
|
||||
this.setupMouseEvents = function () { };
|
||||
|
||||
@@ -46,6 +46,12 @@ class PCEnginePlatform implements Platform {
|
||||
this.pce.Init();
|
||||
this.pce.SetROM(rom);
|
||||
}
|
||||
// headless hosts drive frames through advance(); the IDE uses the
|
||||
// requestAnimationFrame loop that resume() starts
|
||||
advance(novideo?: boolean): number {
|
||||
this.pce.Run();
|
||||
return 1;
|
||||
}
|
||||
getPlatformName(): string {
|
||||
return "PC Engine";
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import { CompileResult, TOOLS, compileSourceFile, getToolForFilename, preload, romBytes } from './testlib';
|
||||
import { EmuTarget, VideoOutput, captureRejectionListeners, dropAbortHandlers, installNodeMocks, loadPlatform } from './emutarget';
|
||||
import { PLATFORMS } from '../common/emu';
|
||||
import { EmuHalt, PLATFORMS } from '../common/emu';
|
||||
import { getSkeletonName, getToolMeta } from '../common/toolmeta';
|
||||
import { getBasePlatform } from '../common/util';
|
||||
import { c } from './cliformat';
|
||||
@@ -48,7 +48,7 @@ const PLATFORM_SRC_DIR = 'src/platform';
|
||||
export interface RunResult {
|
||||
ok: boolean; // platform started and ran without throwing
|
||||
frames: number; // frames actually advanced
|
||||
verdict: 'ok' | 'solid' | 'blank' | 'novideo' | 'error';
|
||||
verdict: 'ok' | 'solid' | 'blank' | 'novideo' | 'halted' | 'error';
|
||||
width?: number;
|
||||
height?: number;
|
||||
colors?: number; // distinct pixel colors on the last frame
|
||||
@@ -479,9 +479,18 @@ export async function runPreset(
|
||||
if (opts.pngDir) run.png = await writePresetPNG(video, preset, opts.pngDir);
|
||||
}
|
||||
} catch (e) {
|
||||
run.ok = false;
|
||||
run.verdict = 'error';
|
||||
run.error = '' + (e && e.message ? e.message : e);
|
||||
if (e instanceof EmuHalt && e.normal) {
|
||||
// the program stopped by design (arm32 semihost exit); the emulator
|
||||
// did its job, so that's a clean halt. Other EmuHalts (HLT opcode,
|
||||
// watchdog, illegal instruction) are still failures.
|
||||
run.ok = true;
|
||||
run.verdict = 'halted';
|
||||
run.error = '' + (e.message || e);
|
||||
} else {
|
||||
run.ok = false;
|
||||
run.verdict = 'error';
|
||||
run.error = '' + (e && e.message ? e.message : e);
|
||||
}
|
||||
}
|
||||
// report what actually ran, which is less than requested if it threw
|
||||
if (target) run.frames = target.frameCount;
|
||||
@@ -596,6 +605,7 @@ function runStatus(run: RunResult): string {
|
||||
(run.color != null ? ` #${(run.color >>> 0).toString(16).padStart(8, '0')}` : '');
|
||||
switch (run.verdict) {
|
||||
case 'ok': return green('run ok') + ' ' + dim(info);
|
||||
case 'halted': return dim('run halted') + ' ' + dim(info);
|
||||
case 'novideo': return dim('run: no video');
|
||||
case 'blank': return yellow(bold('run BLANK')) + ' ' + dim(info);
|
||||
case 'solid': return yellow(bold('run SOLID')) + ' ' + dim(info);
|
||||
@@ -657,7 +667,7 @@ async function main() {
|
||||
console.log(bold(`${run ? 'building and running' : 'building'} ${presets.length} presets...`));
|
||||
const results = await buildAllPresets({
|
||||
presets, timeout, verbose, run, frames, pngDir, onResult: (r) => {
|
||||
const clean = !r.run || r.run.verdict === 'ok' || r.run.verdict === 'novideo';
|
||||
const clean = !r.run || r.run.verdict === 'ok' || r.run.verdict === 'novideo' || r.run.verdict === 'halted';
|
||||
if (quiet && r.ok && clean) return;
|
||||
const status = r.ok ? green('ok ') : red(bold('FAIL'));
|
||||
const size = r.size != null ? `${r.size} bytes` : '';
|
||||
@@ -690,7 +700,7 @@ async function main() {
|
||||
// what the builds did when loaded, separately from whether they built
|
||||
const ran = results.filter((r) => r.run);
|
||||
if (ran.length) {
|
||||
const problems = ran.filter((r) => r.run.verdict !== 'ok' && r.run.verdict !== 'novideo');
|
||||
const problems = ran.filter((r) => r.run.verdict !== 'ok' && r.run.verdict !== 'novideo' && r.run.verdict !== 'halted');
|
||||
const tally2 = `${ran.length - problems.length}/${ran.length} presets run clean`;
|
||||
console.log('\n' + bold(problems.length ? yellow(tally2) : green(tally2)));
|
||||
if (problems.length) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { EmuCore } from "../common/emucore";
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import { getRootBasePlatform } from "../common/util";
|
||||
import { importPlatform } from "../platform/_index";
|
||||
import { mockAudio, mockDOM, mockFetch, mockGlobals, mockScripts } from "./nodemock";
|
||||
import { mockAudio, mockDOM, mockFetch, mockGlobals, mockScripts, resetScripts } from "./nodemock";
|
||||
|
||||
export { EmuCore as EmuTarget, DISASSEMBLERS, DEFAULT_MAX_FRAMES } from "../common/emucore";
|
||||
export type { VideoOutput, DebugSection } from "../common/emucore";
|
||||
@@ -15,6 +15,9 @@ type EmuTarget = EmuCore;
|
||||
/** Load a platform module by ID (e.g. "nes", "c64.wasm", "atari8-5200"). */
|
||||
export async function loadPlatform(platformId: string): Promise<EmuTarget> {
|
||||
installNodeMocks();
|
||||
// a headless host may load more than one platform (or the same one twice)
|
||||
// in a process; scripts kept global state, so evaluate them fresh each time
|
||||
resetScripts();
|
||||
const baseId = getRootBasePlatform(platformId);
|
||||
// a platform module (verilog) may pull in an Emscripten runtime whose
|
||||
// rejection handler aborts the host; disarm it around the import
|
||||
|
||||
+29
-2
@@ -37,18 +37,32 @@ export class NullAudio implements SampledAudioSink {
|
||||
}
|
||||
|
||||
export function mockAudio() {
|
||||
// The real TSS PsgDeviceChannel is a small register machine; platforms
|
||||
// (astrocade, vectrex) read/write its registers and set its clock, so the
|
||||
// stand-in has to keep those too, not just absorb the calls.
|
||||
class NullPsgDeviceChannel {
|
||||
clock = 0;
|
||||
register = new Int32Array(16);
|
||||
setMode() { }
|
||||
setDevice() { }
|
||||
setClock(clock: number) { this.clock = clock; }
|
||||
generate() { }
|
||||
setBufferLength() { }
|
||||
setSampleRate() { }
|
||||
getBuffer() { return []; }
|
||||
writeRegister() { }
|
||||
writeRegisterSN() { }
|
||||
writeRegisterAY() { }
|
||||
readRegister() { return 0; }
|
||||
writeRegisterAY(addr: number, val: number) { if (addr >= 0 && addr < 16) this.register[addr] = val & 0xff; }
|
||||
readRegister(addr: number) { return this.register[addr & 0xf]; }
|
||||
}
|
||||
// the constants the audio wrappers reference; values match tss/js/tss/PsgDeviceChannel.js
|
||||
(NullPsgDeviceChannel as any).MODE_UNSIGNED = 0;
|
||||
(NullPsgDeviceChannel as any).MODE_SIGNED = 1;
|
||||
(NullPsgDeviceChannel as any).DEVICE_PSG = 0;
|
||||
(NullPsgDeviceChannel as any).DEVICE_SSG = 1;
|
||||
(NullPsgDeviceChannel as any).DEVICE_AY_3_8910 = 0;
|
||||
(NullPsgDeviceChannel as any).DEVICE_YM_2149 = 1;
|
||||
(NullPsgDeviceChannel as any).DEVICE_SN76489 = 2;
|
||||
class NullMasterChannel {
|
||||
addChannel() { }
|
||||
}
|
||||
@@ -69,8 +83,11 @@ export function mockFetch(rootDir: string = process.cwd()) {
|
||||
* Make loadScript() evaluate files from `rootDir` in the global scope, the
|
||||
* way a <script> tag would. Each script loads once.
|
||||
*/
|
||||
let clearLoadedScripts: (() => void) | null = null;
|
||||
|
||||
export function mockScripts(rootDir: string = process.cwd()) {
|
||||
const loaded = new Map<string, Promise<void>>();
|
||||
clearLoadedScripts = () => loaded.clear();
|
||||
setScriptLoader((url) => {
|
||||
const file = path.resolve(rootDir, url);
|
||||
if (!loaded.has(file)) {
|
||||
@@ -82,6 +99,16 @@ export function mockScripts(rootDir: string = process.cwd()) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget which scripts have been evaluated, so the next loadScript() runs them
|
||||
* again. A platform that keeps global state (Javatari deletes its own start())
|
||||
* needs a fresh script for a second run in the same process; a browser gets
|
||||
* that from a page reload.
|
||||
*/
|
||||
export function resetScripts() {
|
||||
if (clearLoadedScripts) clearLoadedScripts();
|
||||
}
|
||||
|
||||
export function mockDOM() {
|
||||
const jsdom = require('jsdom');
|
||||
const { JSDOM } = jsdom;
|
||||
|
||||
Reference in new issue
Block a user