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 */}
+
+
+
+
+ | Address |
+ {/* Headers +0, +1 ... */}
+ {Array.from({ length: cols }).map((_, i) => (
+
+ +{(i * stride).toString(16).toUpperCase().padStart(2, '0')}
+ |
+ ))}
+ ASCII / Interpretation |
+
+
+
+ {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 (
+
+ |
+ M{rowAddr.toString().padStart(4, '0')}
+ |
+ {Array.from({ length: cols }).map((_, cIndex) => {
+ const cellAddr = rowAddr + (cIndex * stride);
+ return (
+
+ {renderCell(cellAddr)}
+ |
+ );
+ })}
+ {/* Optional ASCII side-view for the whole row */}
+
+ {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('')}
+ |
+
+ );
+ })}
+ {/* Empty row to indicate reserved space start if not visible? No, pure highlighting is enough */}
+
+
+
+
+
+
+
+ );
+};
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}
+
+
+