@@ -298,6 +298,157 @@ function drawEdge(parent: SVGElement, from: Pos, to: Pos, opacity = 1): void {
298298 }
299299}
300300
301+ /**
302+ * The RAM view: a dense memory map showing where each object *lives* — its
303+ * address, size, and reference count — rather than the abstract graph. Columns:
304+ * ADDR (hex, monotonic like malloc) · OBJ (id) · TYPE · BYTES · REFC · VALUE.
305+ *
306+ * For a numpy buffer the VALUE cell draws the raw byte cells; every ndarray
307+ * window over that buffer then paints its own offset/shape window as a tinted
308+ * strip beneath the cells — the flagship "how it works in RAM" picture, where
309+ * a slice is just a narrower window over the same contiguous run.
310+ */
311+ export function renderRam ( svg : SVGSVGElement , state : MemoryState , liveIds : string [ ] ) : void {
312+ while ( svg . firstChild ) svg . removeChild ( svg . firstChild ) ;
313+
314+ const ids = liveIds . filter ( ( id ) => state . heap [ id ] ) ;
315+ const HEADER_X = 14 ;
316+ const COL_OBJ = HEADER_X + 56 ;
317+ const COL_TYPE = COL_OBJ + 92 ;
318+ const COL_BYTES = COL_TYPE + 96 ;
319+ const COL_REF = COL_BYTES + 64 ;
320+ const COL_VALUE = COL_REF + 52 ;
321+ const VALUE_W = 360 ;
322+ const width = COL_VALUE + VALUE_W + 20 ;
323+ const ROW_H = 24 ;
324+ const ROW_GAP = 8 ;
325+ const mono = 'IBM Plex Mono, monospace' ;
326+ const CELL_W = 30 ;
327+ const CELL_GAP = 2 ;
328+ const CELL_H = 16 ;
329+ const VIEW_H = 14 ;
330+
331+ // One color per ndarray view over a given buffer, so multiple overlapping
332+ // windows stay distinguishable in the column strip.
333+ const VIEW_PALETTE = [ COLORS . amber , COLORS . coral , COLORS . teal ] ;
334+
335+ const bufferInfo = new Map < string , { data : number [ ] ; y : number } > ( ) ;
336+ const viewLists = new Map < string , string [ ] > ( ) ; // bufferId -> ndarray ids in order
337+ for ( const id of ids ) {
338+ const obj = state . heap [ id ] ! ;
339+ if ( obj . type === 'ndarray' && isNdarrayValue ( obj . value ) ) {
340+ const v = obj . value as { buffer : string } ;
341+ const list = viewLists . get ( v . buffer ) ?? [ ] ;
342+ list . push ( id ) ;
343+ viewLists . set ( v . buffer , list ) ;
344+ }
345+ }
346+ const rowH = ( obj : { id : string ; type : string } ) : number => {
347+ if ( obj . type !== 'buffer' ) return ROW_H ;
348+ const n = viewLists . get ( obj . id ) ?. length ?? 0 ;
349+ return 48 + n * VIEW_H ;
350+ } ;
351+ const totalH = Math . max ( ids . reduce ( ( acc , id ) => acc + rowH ( state . heap [ id ] ! ) + ROW_GAP , 26 ) , 220 ) ;
352+
353+ svg . setAttribute ( 'viewBox' , `0 0 ${ width } ${ totalH } ` ) ;
354+ svg . setAttribute ( 'width' , '100%' ) ;
355+ svg . setAttribute ( 'height' , String ( Math . max ( totalH , 200 ) ) ) ;
356+
357+ // Column header band
358+ addText ( svg , HEADER_X , 18 , 'ADDR' , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
359+ addText ( svg , COL_OBJ , 18 , 'OBJ' , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
360+ addText ( svg , COL_TYPE , 18 , 'TYPE' , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
361+ addText ( svg , COL_BYTES , 18 , 'BYTES' , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
362+ addText ( svg , COL_REF , 18 , 'REFC' , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
363+ addText ( svg , COL_VALUE , 18 , 'VALUE' , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
364+
365+ let y = 34 ;
366+ for ( const id of ids ) {
367+ const obj = state . heap [ id ] ! ;
368+ const h = rowH ( obj ) ;
369+ const midY = y + h / 2 ;
370+ const isBuffer = obj . type === 'buffer' ;
371+
372+ // row divider
373+ const line = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'line' ) ;
374+ line . setAttribute ( 'x1' , String ( HEADER_X ) ) ;
375+ line . setAttribute ( 'x2' , String ( width - 14 ) ) ;
376+ line . setAttribute ( 'y1' , String ( y ) ) ;
377+ line . setAttribute ( 'y2' , String ( y ) ) ;
378+ line . setAttribute ( 'stroke' , COLORS . line ) ;
379+ line . setAttribute ( 'stroke-width' , '1' ) ;
380+ svg . appendChild ( line ) ;
381+
382+ addText ( svg , HEADER_X , midY , hexAddr ( obj . addr ) , COLORS . amber , 11 , 'start' , 'middle' , mono ) ;
383+ addText ( svg , COL_OBJ , midY , obj . id , COLORS . ink , 11 , 'start' , 'middle' , mono ) ;
384+ addText ( svg , COL_TYPE , midY , obj . type , COLORS . teal , 11 , 'start' , 'middle' , mono ) ;
385+ addText ( svg , COL_BYTES , midY , String ( obj . byteSize ) , COLORS . ink , 11 , 'start' , 'middle' , mono ) ;
386+ addText ( svg , COL_REF , midY , String ( obj . refCount ) , obj . refCount === 0 ? COLORS . coral : COLORS . ink , 11 , 'start' , 'middle' , mono ) ;
387+
388+ if ( isBuffer ) {
389+ const data = ( obj . value as { data ?: number [ ] } ) . data ?? [ ] ;
390+ const barY = y + ROW_H + 4 ;
391+ for ( let i = 0 ; i < data . length ; i ++ ) {
392+ const cx = COL_VALUE + i * ( CELL_W + CELL_GAP ) ;
393+ const cell = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'rect' ) ;
394+ cell . setAttribute ( 'x' , String ( cx ) ) ;
395+ cell . setAttribute ( 'y' , String ( barY ) ) ;
396+ cell . setAttribute ( 'width' , String ( CELL_W ) ) ;
397+ cell . setAttribute ( 'height' , String ( CELL_H ) ) ;
398+ cell . setAttribute ( 'rx' , '3' ) ;
399+ cell . setAttribute ( 'fill' , '#223038' ) ;
400+ cell . setAttribute ( 'stroke' , COLORS . line ) ;
401+ svg . appendChild ( cell ) ;
402+ addText ( svg , cx + CELL_W / 2 , barY + CELL_H / 2 , String ( data [ i ] ) , COLORS . ink , 10 , 'middle' , 'middle' , mono ) ;
403+ }
404+ addText ( svg , COL_VALUE , midY , `${ hexAddr ( obj . addr ) } · ${ data . length } ×8B` , COLORS . muted , 10 , 'start' , 'middle' , mono ) ;
405+ bufferInfo . set ( id , { data, y : barY } ) ;
406+ } else {
407+ const label = obj . type === 'ndarray'
408+ ? `${ obj . repr } `
409+ : obj . repr . length > 40 ? obj . repr . slice ( 0 , 40 ) + '…' : obj . repr ;
410+ addText ( svg , COL_VALUE , midY , label , COLORS . ink , 11 , 'start' , 'middle' , mono ) ;
411+ }
412+
413+ // An ndarray view: paint its window in its buffer's column strip, aligned
414+ // to the same byte columns. Multiple views over one buffer stack as separate
415+ // rows, each showing the exact [offset..offset+shape) range it reads.
416+ if ( obj . type === 'ndarray' && isNdarrayValue ( obj . value ) ) {
417+ const v = obj . value as { buffer : string ; offset : number ; shape : number } ;
418+ const buf = bufferInfo . get ( v . buffer ) ;
419+ const list = viewLists . get ( v . buffer ) ?? [ ] ;
420+ const idx = list . indexOf ( id ) ;
421+ const bufLen = buf ? buf . data . length : 0 ;
422+ if ( buf ) {
423+ const color = VIEW_PALETTE [ idx % VIEW_PALETTE . length ] ;
424+ const barY = buf . y + CELL_H + 4 + idx * VIEW_H ;
425+ const x = COL_VALUE + v . offset * ( CELL_W + CELL_GAP ) ;
426+ const w = Math . max ( v . shape , 1 ) * ( CELL_W + CELL_GAP ) - CELL_GAP ;
427+ const win = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'rect' ) ;
428+ win . setAttribute ( 'x' , String ( x ) ) ;
429+ win . setAttribute ( 'y' , String ( barY ) ) ;
430+ win . setAttribute ( 'width' , String ( w ) ) ;
431+ win . setAttribute ( 'height' , String ( VIEW_H - 4 ) ) ;
432+ win . setAttribute ( 'rx' , '2' ) ;
433+ win . setAttribute ( 'fill' , color ) ;
434+ win . setAttribute ( 'fill-opacity' , '0.28' ) ;
435+ win . setAttribute ( 'stroke' , color ) ;
436+ win . setAttribute ( 'stroke-width' , '1' ) ;
437+ svg . appendChild ( win ) ;
438+ // Label in a gutter just past the buffer cells so it never collides.
439+ const labelX = COL_VALUE + Math . max ( bufLen , v . offset + v . shape ) * ( CELL_W + CELL_GAP ) + 8 ;
440+ addText ( svg , labelX , barY + ( VIEW_H - 4 ) / 2 , `${ obj . id } [${ v . offset } ..${ v . offset + v . shape } ]` , color , 9 , 'start' , 'middle' , mono ) ;
441+ }
442+ }
443+
444+ y += h + ROW_GAP ;
445+ }
446+ }
447+
448+ function hexAddr ( addr : number ) : string {
449+ return `0x${ addr . toString ( 16 ) . padStart ( 4 , '0' ) } ` ;
450+ }
451+
301452function addText ( parent : SVGElement , x : number , y : number , text : string , fill : string , size : number , anchor : string , baseline : string , family = 'IBM Plex Sans, sans-serif' ) : void {
302453 const t = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'text' ) ;
303454 t . setAttribute ( 'x' , String ( x ) ) ;
0 commit comments