All files / src/web tight-loop-note.js

100% Statements 38/38
94.44% Branches 17/18
100% Functions 14/14
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84                4x                   9x                         248x 248x 248x 248x         256x 256x         494x 494x 34x 34x 34x 9x   494x 494x     2x         512x 512x       9x 9x 9x 9x 9x 9x 9x 27x 9x       2x 2x 2x      
import { hexword } from "../hex.js";
import { machineSheilaBlocks, sheilaName, sheilaRegister } from "../sheila.js";
import { tightLoopAt } from "../tight-loop.js";
import { addressRange } from "./debug-dom.js";
import { textButton } from "./dom-utils.js";
import { DebugGroup } from "./floating-panel.js";
 
/** The decoded panel, by id, of each SHEILA device that has one, by its block's `device`. */
const DevicePanels = {
    CRTC: "crtc",
    FDC: "fdc",
    Paging: "paging",
    SysVIA: "sysvia",
    UserVIA: "uservia",
};
 
/** `nodes` listed as in prose: "a", "a and b", "a, b and c". */
function inProse(nodes) {
    return nodes.flatMap((node, i) => (i === 0 ? [node] : [i < nodes.length - 1 ? ", " : " and ", node]));
}
 
/**
 * A line saying, while the machine is stopped in a tight loop, where the loop is and what it polls, by its SHEILA
 * register's name where it has one, with a button to open each polled device's decoded panel.
 */
export class TightLoopNote {
    /**
     * @param {HTMLElement} line
     * @param {import("./floating-panel.js").PanelArrangement} arrangement
     */
    constructor(line, arrangement) {
        this._line = line;
        this._arrangement = arrangement;
        this._cpu = null;
        this._sheila = [];
    }
 
    /** @param {import("../6502.js").Cpu6502} cpu */
    setCpu(cpu) {
        this._cpu = cpu;
        this._sheila = machineSheilaBlocks(cpu);
    }
 
    /** Shows the tight loop the machine is stopped in, if it is in one. */
    show() {
        const loop = tightLoopAt(this._cpu.disassembler, this._cpu.pc);
        if (!loop) return this.clear();
        const reads = loop.reads.map((address) => this._polled(address));
        const panels = new Set(reads.flatMap(({ panel }) => (panel ? [panel] : [])));
        const polling = reads.length
            ? [" polling ", ...inProse(reads.map(({ node }) => node))]
            : [" that reads nothing"];
        this._line.hidden = false;
        this._line.replaceChildren(
            `In a loop at ${addressRange(loop)}`,
            ...polling,
            ...[...panels].flatMap((panel) => [" ", this._opener(panel)]),
        );
    }
 
    clear() {
        this._line.hidden = true;
        this._line.replaceChildren();
    }
 
    _polled(address) {
        const node = document.createElement("span");
        node.className = "polled";
        const found = sheilaRegister(this._sheila, address, "read");
        const named = sheilaName(this._sheila, address, ["read"]);
        node.textContent = named ? `${named.name} ($${hexword(address)})` : `$${hexword(address)}`;
        if (named) node.title = named.title;
        const id = found && DevicePanels[found.block.device];
        const panel = id && this._arrangement.panelsIn(DebugGroup).find((floating) => floating.id === id);
        return { node, panel };
    }
 
    _opener(floating) {
        const button = textButton("", `open ${floating.title}`, "");
        button.addEventListener("click", () => floating.open());
        return button;
    }
}