import type { SimEventJSON, SystemState, PEState, SMState, SMCellState, MatchingEntry, } from "./types"; import { rp } from "./palette"; type EventFilter = { readonly component: string | null; readonly eventType: string | null; }; type EventLogOptions = { readonly container: HTMLElement; readonly maxEvents: number; }; type StateInspectorOptions = { readonly container: HTMLElement; }; /** * Module-level state for the event log panel. * * `allEvents` accumulates all events; `currentFilter` controls visible subset. * setEventFilter() updates the filter and re-renders with stored events. */ let currentFilter: EventFilter = { component: null, eventType: null }; let allEvents: ReadonlyArray = []; const eventTypeColors: Record = { Matched: rp.foam, Executed: rp.pine, TokenReceived: rp.iris, CellWritten: rp.gold, DeferredRead: rp.rose, Emitted: rp.love, TokenStored: rp.gold, }; function formatTime(time: number): string { return `[${time.toFixed(3)}]`; } function renderEventEntry(event: SimEventJSON): HTMLElement { const entry = document.createElement("div"); entry.className = "event-entry"; entry.addEventListener("mouseenter", () => { entry.style.backgroundColor = rp.overlay; }); entry.addEventListener("mouseleave", () => { entry.style.backgroundColor = "transparent"; }); const timeSpan = document.createElement("span"); timeSpan.textContent = formatTime(event.time); timeSpan.style.color = rp.foam; entry.appendChild(timeSpan); entry.appendChild(document.createTextNode(" ")); const componentSpan = document.createElement("span"); componentSpan.textContent = event.component; componentSpan.style.color = rp.gold; entry.appendChild(componentSpan); entry.appendChild(document.createTextNode(": ")); const eventTypeSpan = document.createElement("span"); eventTypeSpan.textContent = event.type; eventTypeSpan.style.color = eventTypeColors[event.type] || rp.subtle; entry.appendChild(eventTypeSpan); if (Object.keys(event.details).length > 0) { entry.appendChild(document.createTextNode(" — ")); const detailsSpan = document.createElement("span"); detailsSpan.textContent = JSON.stringify(event.details); detailsSpan.style.color = rp.muted; entry.appendChild(detailsSpan); } entry.addEventListener("click", () => { const evt = new CustomEvent("event-selected", { detail: event, bubbles: true, }); entry.dispatchEvent(evt); }); (entry as any)._eventData = event; return entry; } function eventMatchesFilter(event: SimEventJSON, filter: EventFilter): boolean { if (filter.component !== null && event.component !== filter.component) { return false; } if (filter.eventType !== null && event.type !== filter.eventType) { return false; } return true; } function clearEventLog(): void { allEvents = []; } function updateEventLog( events: ReadonlyArray, options: EventLogOptions ): void { if (events.length > 0) { allEvents = [...allEvents, ...events]; } options.container.innerHTML = ""; const visibleEvents = Array.from(allEvents).slice(-options.maxEvents); for (const event of visibleEvents) { if (eventMatchesFilter(event, currentFilter)) { const entry = renderEventEntry(event); options.container.appendChild(entry); } } options.container.scrollTop = options.container.scrollHeight; } function setEventFilter( filter: EventFilter, logContainer: HTMLElement | null ): void { currentFilter = filter; if (logContainer) { const maxEvents = 1000; updateEventLog(allEvents, { container: logContainer, maxEvents }); } } function renderPEState(peId: string, state: PEState): HTMLElement { const section = document.createElement("details"); section.style.marginBottom = "12px"; const summary = document.createElement("summary"); summary.textContent = `PE ${peId}`; summary.style.cursor = "pointer"; summary.style.fontWeight = "bold"; summary.style.color = rp.foam; section.appendChild(summary); const content = document.createElement("div"); content.style.paddingLeft = "16px"; content.style.fontFamily = "monospace"; content.style.fontSize = "11px"; // IRAM entries const iramDiv = document.createElement("div"); iramDiv.style.marginBottom = "8px"; const iramLabel = document.createElement("strong"); iramLabel.textContent = "IRAM:"; iramLabel.style.color = rp.pine; iramDiv.appendChild(iramLabel); iramDiv.appendChild(document.createElement("br")); const iramEntries = Object.entries(state.iram || {}); if (iramEntries.length === 0) { const empty = document.createElement("span"); empty.textContent = "(empty)"; empty.style.color = rp.muted; iramDiv.appendChild(empty); } else { for (const [offset, instr] of iramEntries) { const line = document.createElement("div"); line.textContent = ` [${offset}]: ${JSON.stringify(instr)}`; line.style.color = rp.subtle; iramDiv.appendChild(line); } } content.appendChild(iramDiv); // Matching store const msDiv = document.createElement("div"); msDiv.style.marginBottom = "8px"; const msLabel = document.createElement("strong"); msLabel.textContent = "Matching Store:"; msLabel.style.color = rp.pine; msDiv.appendChild(msLabel); msDiv.appendChild(document.createElement("br")); const matchingStore = state.matching_store || []; if (matchingStore.length === 0) { const empty = document.createElement("span"); empty.textContent = "(empty)"; empty.style.color = rp.muted; msDiv.appendChild(empty); } else { for (let ctx = 0; ctx < matchingStore.length; ctx++) { const ctx_slots = matchingStore[ctx]; if (!ctx_slots || ctx_slots.length === 0) continue; const ctxDiv = document.createElement("div"); ctxDiv.textContent = ` ctx[${ctx}]:`; ctxDiv.style.color = rp.gold; msDiv.appendChild(ctxDiv); for (let offset = 0; offset < ctx_slots.length; offset++) { const entry = ctx_slots[offset]; if (!entry.occupied) continue; const entryDiv = document.createElement("div"); entryDiv.style.paddingLeft = "16px"; const portStr = entry.port || "—"; const dataStr = entry.data !== null ? entry.data.toString() : "—"; entryDiv.textContent = `[${offset}]: data=${dataStr}, port=${portStr}`; entryDiv.style.color = rp.subtle; msDiv.appendChild(entryDiv); } } } content.appendChild(msDiv); // Gen counters const genDiv = document.createElement("div"); genDiv.style.marginBottom = "8px"; const genLabel = document.createElement("strong"); genLabel.textContent = "Gen Counters:"; genLabel.style.color = rp.pine; genDiv.appendChild(genLabel); genDiv.appendChild(document.createElement("br")); const genCounters = state.gen_counters || []; if (genCounters.length === 0) { const empty = document.createElement("span"); empty.textContent = "(none)"; empty.style.color = rp.muted; genDiv.appendChild(empty); } else { for (let i = 0; i < genCounters.length; i++) { const line = document.createElement("div"); line.textContent = ` ctx[${i}]: ${genCounters[i]}`; line.style.color = rp.subtle; genDiv.appendChild(line); } } content.appendChild(genDiv); // Input queue and output log const statsDiv = document.createElement("div"); const queueSpan = document.createElement("span"); queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`; queueSpan.style.color = rp.subtle; statsDiv.appendChild(queueSpan); statsDiv.appendChild(document.createElement("br")); const outputSpan = document.createElement("span"); outputSpan.textContent = `Output Count: ${state.output_count}`; outputSpan.style.color = rp.subtle; statsDiv.appendChild(outputSpan); content.appendChild(statsDiv); section.appendChild(content); return section; } function renderSMState(smId: string, state: SMState): HTMLElement { const section = document.createElement("details"); section.style.marginBottom = "12px"; const summary = document.createElement("summary"); summary.textContent = `SM ${smId}`; summary.style.cursor = "pointer"; summary.style.fontWeight = "bold"; summary.style.color = rp.gold; section.appendChild(summary); const content = document.createElement("div"); content.style.paddingLeft = "16px"; content.style.fontFamily = "monospace"; content.style.fontSize = "11px"; // Cells const cellsDiv = document.createElement("div"); cellsDiv.style.marginBottom = "8px"; const cellsLabel = document.createElement("strong"); cellsLabel.textContent = "Cells:"; cellsLabel.style.color = rp.pine; cellsDiv.appendChild(cellsLabel); cellsDiv.appendChild(document.createElement("br")); const cells = state.cells || {}; const cellAddrs = Object.keys(cells).filter(addr => { const cell = (cells as any)[addr]; return cell && cell.presence !== "EMPTY"; }); if (cellAddrs.length === 0) { const empty = document.createElement("span"); empty.textContent = "(all empty)"; empty.style.color = rp.muted; cellsDiv.appendChild(empty); } else { for (const addr of cellAddrs) { const cell = (cells as any)[addr]; if (!cell) continue; const line = document.createElement("div"); const dataStr = cell.data_l !== null ? cell.data_l : (cell.data_r !== null ? cell.data_r : "—"); line.textContent = ` [${addr}]: ${cell.presence} = ${dataStr}`; line.style.color = rp.subtle; cellsDiv.appendChild(line); } } content.appendChild(cellsDiv); // Deferred read if (state.deferred_read) { const deferredDiv = document.createElement("div"); deferredDiv.style.marginBottom = "8px"; const deferredLabel = document.createElement("strong"); deferredLabel.textContent = "Deferred Read:"; deferredLabel.style.color = rp.iris; deferredDiv.appendChild(deferredLabel); deferredDiv.appendChild(document.createElement("br")); const drLine = document.createElement("div"); drLine.textContent = ` cell_addr: ${state.deferred_read.cell_addr}`; drLine.style.color = rp.subtle; deferredDiv.appendChild(drLine); content.appendChild(deferredDiv); } // T0 store and input queue const statsDiv = document.createElement("div"); const t0Span = document.createElement("span"); t0Span.textContent = `T0 Store Size: ${state.t0_store_size}`; t0Span.style.color = rp.subtle; statsDiv.appendChild(t0Span); statsDiv.appendChild(document.createElement("br")); const queueSpan = document.createElement("span"); queueSpan.textContent = `Input Queue: ${state.input_queue_depth}`; queueSpan.style.color = rp.subtle; statsDiv.appendChild(queueSpan); content.appendChild(statsDiv); section.appendChild(content); return section; } function updateStateInspector( state: SystemState, options: StateInspectorOptions ): void { options.container.innerHTML = ""; const scrollContainer = document.createElement("div"); scrollContainer.style.overflowY = "auto"; scrollContainer.style.height = "100%"; scrollContainer.style.paddingRight = "8px"; // PE section const peSection = document.createElement("div"); peSection.style.marginBottom = "16px"; const peTitle = document.createElement("h3"); peTitle.textContent = "Processing Elements"; peTitle.style.color = rp.foam; peTitle.style.marginTop = "0"; peTitle.style.marginBottom = "8px"; peTitle.style.fontSize = "14px"; peSection.appendChild(peTitle); const pes = state.pes || {}; if (Object.keys(pes).length === 0) { const empty = document.createElement("span"); empty.textContent = "(none)"; empty.style.color = rp.muted; empty.style.fontSize = "11px"; peSection.appendChild(empty); } else { for (const [peId, peState] of Object.entries(pes)) { peSection.appendChild(renderPEState(peId, peState)); } } scrollContainer.appendChild(peSection); // SM section const smSection = document.createElement("div"); const smTitle = document.createElement("h3"); smTitle.textContent = "Structure Memories"; smTitle.style.color = rp.gold; smTitle.style.marginTop = "0"; smTitle.style.marginBottom = "8px"; smTitle.style.fontSize = "14px"; smSection.appendChild(smTitle); const sms = state.sms || {}; if (Object.keys(sms).length === 0) { const empty = document.createElement("span"); empty.textContent = "(none)"; empty.style.color = rp.muted; empty.style.fontSize = "11px"; smSection.appendChild(empty); } else { for (const [smId, smState] of Object.entries(sms)) { smSection.appendChild(renderSMState(smId, smState)); } } scrollContainer.appendChild(smSection); options.container.appendChild(scrollContainer); } function displayNodeMatchingStore( peId: number, ctx: number, offset: number, entry: MatchingEntry, container: HTMLElement ): void { const detailSection = document.createElement("div"); detailSection.id = "node-detail"; detailSection.style.marginTop = "16px"; detailSection.style.padding = "12px"; detailSection.style.backgroundColor = rp.overlay; detailSection.style.border = `2px solid ${rp.foam}`; detailSection.style.borderRadius = "4px"; const title = document.createElement("h4"); title.textContent = `Selected Node: PE${peId} IRAM[${offset}]`; title.style.color = rp.foam; title.style.margin = "0 0 8px 0"; title.style.fontSize = "13px"; detailSection.appendChild(title); const contentDiv = document.createElement("div"); contentDiv.style.fontFamily = "monospace"; contentDiv.style.fontSize = "11px"; const statusDiv = document.createElement("div"); statusDiv.style.marginBottom = "8px"; const statusLabel = document.createElement("span"); statusLabel.textContent = "Status: "; statusLabel.style.color = rp.pine; statusDiv.appendChild(statusLabel); const statusValue = document.createElement("span"); statusValue.textContent = entry.occupied ? "OCCUPIED" : "EMPTY"; statusValue.style.color = entry.occupied ? rp.gold : rp.muted; statusDiv.appendChild(statusValue); contentDiv.appendChild(statusDiv); if (entry.occupied) { const dataDiv = document.createElement("div"); dataDiv.style.marginBottom = "8px"; const dataLabel = document.createElement("span"); dataLabel.textContent = "Data: "; dataLabel.style.color = rp.pine; dataDiv.appendChild(dataLabel); const dataValue = document.createElement("span"); dataValue.textContent = entry.data !== null ? entry.data.toString() : "(null)"; dataValue.style.color = rp.gold; dataDiv.appendChild(dataValue); contentDiv.appendChild(dataDiv); const portDiv = document.createElement("div"); const portLabel = document.createElement("span"); portLabel.textContent = "Waiting Port: "; portLabel.style.color = rp.pine; portDiv.appendChild(portLabel); const portValue = document.createElement("span"); portValue.textContent = entry.port || "(none)"; portValue.style.color = entry.port ? rp.foam : rp.muted; portDiv.appendChild(portValue); contentDiv.appendChild(portDiv); } detailSection.appendChild(contentDiv); const existing = container.querySelector("#node-detail"); if (existing) { existing.remove(); } const scrollContainer = container.querySelector("div"); if (scrollContainer) { scrollContainer.insertBefore(detailSection, scrollContainer.firstChild); } else { container.appendChild(detailSection); } } function displaySMNodeState( smId: number, state: SMState, container: HTMLElement ): void { const detailSection = document.createElement("div"); detailSection.id = "node-detail"; detailSection.style.marginTop = "16px"; detailSection.style.padding = "12px"; detailSection.style.backgroundColor = rp.overlay; detailSection.style.border = `2px solid ${rp.gold}`; detailSection.style.borderRadius = "4px"; const title = document.createElement("h4"); title.textContent = `Selected: SM ${smId}`; title.style.color = rp.gold; title.style.margin = "0 0 8px 0"; title.style.fontSize = "13px"; detailSection.appendChild(title); const contentDiv = document.createElement("div"); contentDiv.style.fontFamily = "monospace"; contentDiv.style.fontSize = "11px"; // Cells const cells = state.cells || {}; const cellAddrs = Object.keys(cells).sort((a, b) => parseInt(a) - parseInt(b)); const nonEmptyCells = cellAddrs.filter(addr => { const cell = (cells as any)[addr]; return cell && cell.presence !== "EMPTY"; }); if (nonEmptyCells.length === 0) { const emptyDiv = document.createElement("div"); emptyDiv.style.marginBottom = "8px"; const label = document.createElement("strong"); label.textContent = "Cells: "; label.style.color = rp.pine; emptyDiv.appendChild(label); const value = document.createElement("span"); value.textContent = "(all empty)"; value.style.color = rp.muted; emptyDiv.appendChild(value); contentDiv.appendChild(emptyDiv); } else { const cellsHeader = document.createElement("strong"); cellsHeader.textContent = "Cells:"; cellsHeader.style.color = rp.pine; contentDiv.appendChild(cellsHeader); contentDiv.appendChild(document.createElement("br")); for (const addr of nonEmptyCells) { const cell = (cells as any)[addr] as SMCellState; const line = document.createElement("div"); line.style.paddingLeft = "8px"; line.style.marginBottom = "2px"; const addrSpan = document.createElement("span"); addrSpan.textContent = `[${addr}] `; addrSpan.style.color = rp.gold; line.appendChild(addrSpan); const presSpan = document.createElement("span"); presSpan.textContent = cell.presence; presSpan.style.color = cell.presence === "FULL" ? rp.pine : cell.presence === "WAITING" ? rp.iris : cell.presence === "RESERVED" ? rp.gold : rp.muted; line.appendChild(presSpan); if (cell.data_l !== null) { const dataSpan = document.createElement("span"); dataSpan.textContent = ` L=${cell.data_l}`; dataSpan.style.color = rp.subtle; line.appendChild(dataSpan); } if (cell.data_r !== null) { const dataSpan = document.createElement("span"); dataSpan.textContent = ` R=${cell.data_r}`; dataSpan.style.color = rp.subtle; line.appendChild(dataSpan); } contentDiv.appendChild(line); } } // Deferred read if (state.deferred_read) { const drDiv = document.createElement("div"); drDiv.style.marginTop = "8px"; const drLabel = document.createElement("strong"); drLabel.textContent = "Deferred Read: "; drLabel.style.color = rp.iris; drDiv.appendChild(drLabel); const drValue = document.createElement("span"); drValue.textContent = `cell[${state.deferred_read.cell_addr}]`; drValue.style.color = rp.subtle; drDiv.appendChild(drValue); contentDiv.appendChild(drDiv); } // T0 store const t0Div = document.createElement("div"); t0Div.style.marginTop = "8px"; const t0Label = document.createElement("span"); t0Label.textContent = `T0 Store: ${state.t0_store_size} entries`; t0Label.style.color = rp.muted; t0Div.appendChild(t0Label); contentDiv.appendChild(t0Div); // Input queue const queueDiv = document.createElement("div"); const queueLabel = document.createElement("span"); queueLabel.textContent = `Input Queue: ${state.input_queue_depth}`; queueLabel.style.color = rp.muted; queueDiv.appendChild(queueLabel); contentDiv.appendChild(queueDiv); detailSection.appendChild(contentDiv); const existing = container.querySelector("#node-detail"); if (existing) { existing.remove(); } const scrollContainer = container.querySelector("div"); if (scrollContainer) { scrollContainer.insertBefore(detailSection, scrollContainer.firstChild); } else { container.appendChild(detailSection); } } export type { EventFilter, EventLogOptions, StateInspectorOptions, }; export { clearEventLog, updateEventLog, setEventFilter, updateStateInspector, displayNodeMatchingStore, displaySMNodeState, };