# Dataflow Graph Renderer (dfgraph/) Last verified: 2026-02-26 ## Purpose Interactive visualisation tool for dfasm dataflow programs. Renders the assembler's IR as a live-updating graph in the browser, enabling developers to see node placement, edge routing, and pipeline errors as they edit source files. ## Contracts - **Exposes**: `python -m dfgraph path/to/file.dfasm [--port 8420]` launches a web server with WebSocket-driven graph updates - **Guarantees**: Progressive pipeline captures the deepest successful IRGraph even when later passes fail, so partial graphs are always shown. Parse errors are fatal (no graph); resolve/place/allocate errors accumulate in `IRGraph.errors` and are displayed alongside the partial graph. File changes trigger reassembly within 300ms debounce window. - **Expects**: Valid path to a `.dfasm` file. The `dfasm.lark` grammar file at project root. Node modules installed in `dfgraph/frontend/` for the TypeScript build. ## Dependencies - **Uses**: `cm_inst` (ArithOp, LogicOp, RoutingOp, MemOp, Addr), `asm/` (ir, lower, resolve, place, allocate, errors, opcodes), `lark` (parser), `fastapi`/`uvicorn` (server), `watchdog` (file watcher), `cytoscape`/`cytoscape-elk` (frontend) - **Used by**: Developer tooling only. `monitor/graph_json.py` imports `dfgraph/categories.py` for opcode categorisation. - **Boundary**: `cm_inst`, `asm/`, `emu/`, and root-level modules must NEVER import from `dfgraph/` ## Key Decisions - **isinstance dispatch for categories** (`categories.py`): ALUOp subclasses (ArithOp, LogicOp, RoutingOp) share IntEnum values across types, so dict/set lookup would collide. isinstance checks on the type hierarchy avoid this. - **LogicOp split**: EQ/LT/LTE/GT/GTE map to COMPARISON; AND/OR/XOR/NOT map to LOGIC. This gives visually distinct colours for boolean-producing vs bitwise ops. - **CONST and FREE_CTX as CONFIG**: RoutingOp.CONST and FREE_CTX are categorised as CONFIG (not ROUTING) since they are infrastructure ops, not data-movement ops. - **ELK layout engine**: Uses Eclipse Layout Kernel (via cytoscape-elk) for layered DAG layout with orthogonal edge routing. Replaced dagre for better edge routing. - **Progressive pipeline**: Unlike `asm._run_pipeline()` which raises on error, `run_progressive()` runs each pass independently so the frontend can show partial graphs with error annotations. ## Invariants - `dfgraph/` never imports from `emu/` -- it only needs the assembler IR, not the simulator - `categorise()` covers all ALUOp/MemOp values; raises `ValueError` on unknown types - `graph_to_json()` always returns a dict with `type: "graph_update"` -- even on parse failure (with empty node/edge lists) - WebSocket protocol: server sends `GraphUpdate` JSON on connect and on every file change; client sends nothing meaningful (receive loop is just for keepalive) ## Key Files - `pipeline.py` -- `PipelineStage` enum, `PipelineResult` dataclass, `run_progressive()` function - `categories.py` -- `OpcodeCategory` enum, `CATEGORY_COLOURS` dict, `categorise()` function - `graph_json.py` -- `graph_to_json(PipelineResult) -> dict` serialisation - `server.py` -- `create_app(source_path) -> FastAPI`, `ConnectionManager`, `DebouncedFileHandler` - `frontend/src/types.ts` -- TypeScript interfaces matching `graph_to_json` output shape - `frontend/src/main.ts` -- Cytoscape graph rendering, logical/physical view toggle - `frontend/src/layout.ts` -- ELK layout configurations for both views ## Gotchas - The Lark parser is lazily initialised and cached globally in `pipeline.py` (`_parser` module var) -- safe for single-process use but not for parallel test runners that fork - `DebouncedFileHandler` runs its callback on a `threading.Timer` thread, so `_on_file_change` uses `asyncio.run_coroutine_threadsafe` to bridge into the async event loop - Frontend `dist/` directory must contain the compiled TypeScript bundle; the server mounts it as static files at `/dist` - Shared TypeScript modules (layout, style, export, types) have been extracted to `frontend-common/`; `dfgraph/frontend/` now imports from `frontend-common` as a workspace dependency