From 711be48527c04e3ff1a58d4bcd6d952751685e5f Mon Sep 17 00:00:00 2001 From: arDTDev Date: Sun, 21 Dec 2025 23:42:39 +0900 Subject: [PATCH] Add Memory Viewer and Axis Memory Map visualization --- App.tsx | 35 ++++-- components/MemoryViewer.tsx | 203 +++++++++++++++++++++++++++++++ components/SystemSetupDialog.tsx | 17 ++- 3 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 components/MemoryViewer.tsx diff --git a/App.tsx b/App.tsx index 4b90fce..6a704e7 100644 --- a/App.tsx +++ b/App.tsx @@ -13,7 +13,8 @@ import { LogicMathOp, MEMORY_SIZE, ADDR_AXIS_BASE, ADDR_AXIS_STRIDE, OFF_AXIS_CURRENT_POS, OFF_AXIS_TARGET_POS } from './types'; import { EditableObject } from './components/SceneObjects'; -import { Layout as LayoutIcon, Cpu, Play, Pause, Square, Download, Upload, Magnet, Settings } from 'lucide-react'; +import { MemoryViewer } from './components/MemoryViewer'; +import { Layout as LayoutIcon, Cpu, Play, Pause, Square, Download, Upload, Magnet, Settings, HardDrive } from 'lucide-react'; // --- Simulation Manager (PLC Scan Cycle) --- const SimulationLoop = ({ @@ -26,7 +27,8 @@ const SimulationLoop = ({ setInputs, setOutputs, axes, - setAxes + setAxes, + memoryView }: { isPlaying: boolean, objects: SimObject[], @@ -253,7 +255,7 @@ const SimulationLoop = ({ }; export default function App() { - const [activeView, setActiveView] = useState<'layout' | 'logic'>('layout'); + const [activeView, setActiveView] = useState<'layout' | 'logic' | 'memory'>('layout'); // --- Memory System (Ref-based, high frequency) --- const memoryBuffer = useRef(new ArrayBuffer(MEMORY_SIZE)); // 10000 bytes const memoryView = useRef(new DataView(memoryBuffer.current)); @@ -412,22 +414,30 @@ export default function App() {
-
M
- MotionSim +
+ SIMP +
+ v0.9.2
-
+
+
@@ -605,6 +615,11 @@ export default function App() { axes={axes} /> )} + + {/* Memory View */} + {activeView === 'memory' && ( + + )}
; +} + +type ViewMode = 'float' | 'int16' | 'int32' | 'byte' | 'hex' | 'ascii'; + +export const MemoryViewer: React.FC = ({ memoryView }) => { + const [viewMode, setViewMode] = useState('float'); + const [startAddr, setStartAddr] = useState(0); + const [rows, setRows] = useState(20); + const [, setTick] = useState(0); // For forcing re-render + + // Auto-refresh memory view + useEffect(() => { + const interval = setInterval(() => { + setTick(t => t + 1); + }, 100); // 10Hz refresh + return () => clearInterval(interval); + }, []); + + const getStride = () => { + switch (viewMode) { + case 'float': return 4; + case 'int32': return 4; + case 'int16': return 2; + default: return 1; + } + }; + + const getCols = () => { + switch (viewMode) { + case 'float': + case 'int32': return 4; // 4 items per row + case 'int16': return 8; // 8 items per row + default: return 16; // 16 bytes per row + } + }; + + const stride = getStride(); + const cols = getCols(); + const rowSize = stride * cols; + const totalRows = Math.ceil(MEMORY_SIZE / rowSize); + + // Pagination / Scroll Helper + const onScroll = (e: React.UIEvent) => { + const top = e.currentTarget.scrollTop; + const rowHeight = 24; // approx + const startRow = Math.floor(top / rowHeight); + // Optimization: Just render what's needed? + // For now, let's keep it simple: render from startAddr relative to scroll if virtual, + // or just use a fixed window if we don't assume full scroll height. + // Let's implement full scroll height logic: + // Actually simplicity first: User can jump addresses. + }; + + const renderCell = (addr: number) => { + if (addr >= MEMORY_SIZE) return -; + try { + const view = memoryView.current; + let val: string | number = ''; + let color = 'text-gray-400'; + + switch (viewMode) { + case 'float': + val = view.getFloat32(addr, true).toFixed(4); + if (parseFloat(val) !== 0) color = 'text-blue-400 font-bold'; + break; + case 'int32': + val = view.getInt32(addr, true); + if (val !== 0) color = 'text-green-400 font-bold'; + break; + case 'int16': + val = view.getInt16(addr, true); + if (val !== 0) color = 'text-green-400 font-bold'; + break; + case 'byte': + val = view.getUint8(addr); + if (val !== 0) color = 'text-yellow-400 font-bold'; + break; + case 'hex': + val = view.getUint8(addr).toString(16).toUpperCase().padStart(2, '0'); + if (val !== '00') color = 'text-purple-400 font-bold'; + break; + case 'ascii': + const charCode = view.getUint8(addr); + // Printable ASCII 32-126 + val = (charCode >= 32 && charCode <= 126) ? String.fromCharCode(charCode) : '.'; + if (val !== '.') color = 'text-orange-400 font-bold'; + else color = 'text-gray-700'; + break; + } + return {val}; + } catch (e) { + return ERR; + } + }; + + // Generate rows + const visibleRows = []; + // For scrolling, let's just make a really tall div and stick the viewport? + // Or just a simple Pager for now to ensure performance? + // "Layout | Logic | Memory" - full screen. + // Let's do a virtualized-like list but fixed to logical startAddr for manual navigation? + // Or just render all 10000 bytes? + // 10000 / 16 = 625 rows. Not too bad for React. + // Let's render all rows (clamped to sensible limit if needed, but 600 rows is fine if simple) + + // Actually, let's render *only* visible based on scrollTop if we can, or just render all for simplicity if performant. + // 600 rows of 16 cols = 9600 elements. Might be heavy on 10Hz re-render. + // Let's render a "Window" window around `startAddr`. + + const displayRows = 50; // Show 50 rows at a time (enough for screen) + + return ( +
+ {/* Toolbar */} +
+
+
+ Data View: +
+
+ + + + + + +
+
+ +
+ Go To Address: + setStartAddr(Math.max(0, parseInt(e.target.value) || 0))} + /> +
+
+ + {/* Grid Content */} +
+ + + + + {/* Headers +0, +1 ... */} + {Array.from({ length: cols }).map((_, i) => ( + + ))} + + + + + {Array.from({ length: 100 }).map((_, rIndex) => { // Render 100 rows starting from startAddr + const rowAddr = startAddr + (rIndex * rowSize); + if (rowAddr >= MEMORY_SIZE) return null; + + const isReserved = rowAddr >= 8000; + return ( + + + {Array.from({ length: cols }).map((_, cIndex) => { + const cellAddr = rowAddr + (cIndex * stride); + return ( + + ); + })} + {/* Optional ASCII side-view for the whole row */} + + + ); + })} + {/* Empty row to indicate reserved space start if not visible? No, pure highlighting is enough */} + +
Address + +{(i * stride).toString(16).toUpperCase().padStart(2, '0')} + ASCII / Interpretation
+ M{rowAddr.toString().padStart(4, '0')} + + {renderCell(cellAddr)} + + {Array.from({ length: rowSize }).map((_, b) => { + const bAddr = rowAddr + b; + if (bAddr >= MEMORY_SIZE) return ''; + const c = memoryView.current.getUint8(bAddr); + return (c >= 32 && c <= 126) ? String.fromCharCode(c) : '.'; + }).join('')} +
+
+ +
+
+
+ ); +}; diff --git a/components/SystemSetupDialog.tsx b/components/SystemSetupDialog.tsx index b6a6f87..c4835df 100644 --- a/components/SystemSetupDialog.tsx +++ b/components/SystemSetupDialog.tsx @@ -1,7 +1,8 @@ + import React from 'react'; import { X } from 'lucide-react'; -import { AxisData } from '../types'; +import { AxisData, ADDR_AXIS_BASE, ADDR_AXIS_STRIDE, OFF_AXIS_CURRENT_POS, OFF_AXIS_TARGET_POS, OFF_AXIS_SPEED } from '../types'; interface SystemSetupDialogProps { isOpen: boolean; @@ -160,6 +161,20 @@ export const SystemSetupDialog: React.FC = ({ />
+ {/* Memory Map Info */} +
+
+ MEMORY MAP + Base: {ADDR_AXIS_BASE + (axis.id * ADDR_AXIS_STRIDE)} +
+
+ Status: +{0} + Cur Pos: +{OFF_AXIS_CURRENT_POS} + Cmd Pos: +{OFF_AXIS_TARGET_POS} + Speed: +{OFF_AXIS_SPEED} +
+
+