Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7017e08
draw lines between related heap objects
MaKhandare Jul 8, 2025
1870b0b
add a nested class to see if it behaves correctly
MaKhandare Jul 8, 2025
59f79ff
instead of fixed offset, get size of node and adjust
MaKhandare Jul 8, 2025
6e9988e
add datatype and some basic styling
MaKhandare Jul 8, 2025
f44d4c0
try to memoize with useMemo
MaKhandare Jul 8, 2025
31168ce
refac connection drawing to improve performance
MaKhandare Jul 8, 2025
974f1b5
change render order of text so it does not randomly hide behind the node
MaKhandare Jul 8, 2025
df041e3
some color adjustments
MaKhandare Jul 8, 2025
cd5d94f
Update playground/csharp/Program.cs
MaKhandare Jul 10, 2025
08856ab
Update server/src/HeapConnections.tsx
MaKhandare Jul 10, 2025
ce7dde4
Update playground/csharp/Program.cs
MaKhandare Jul 10, 2025
3b40270
Update server/src/HeapConnections.tsx
MaKhandare Jul 11, 2025
573ff7a
Update server/src/HeapConnections.tsx
MaKhandare Jul 11, 2025
214493d
Update server/src/HeapConnections.tsx
MaKhandare Jul 11, 2025
d5844c8
rename HeapConnections to HeapConnectionsProvider
MaKhandare Jul 17, 2025
3401bab
use a themeprovider instead of hardcoded colors
MaKhandare Jul 17, 2025
13784f6
invert if for readability
MaKhandare Jul 17, 2025
538be14
helper hook for connections provider
MaKhandare Jul 17, 2025
f008a5c
rename pChildren and rChildren, use else instead of else if
MaKhandare Jul 17, 2025
5cc36d3
move variables into correct scope
MaKhandare Jul 17, 2025
8449457
early check return
MaKhandare Jul 17, 2025
1f6827f
remove as cast by introducing a VariableWithParent t ype
MaKhandare Jul 17, 2025
3dc598a
ref can be null, with proper null checks no as casts needed
MaKhandare Jul 17, 2025
c12b60a
use a map to store children and pass to heapnode
MaKhandare Jul 17, 2025
5562a61
remove theme from props
MaKhandare Jul 17, 2025
9a0e63c
simplify useHeapConnections
MaKhandare Jul 17, 2025
b8c17bf
remove unnecessary if statement
MaKhandare Jul 17, 2025
7246d7d
give body temporarily a gruvbox color
MaKhandare Jul 17, 2025
74b5507
gruvbox colors
MaKhandare Jul 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions playground/csharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,37 @@ int test(int a, int b)
Z = 37,
};

var nested = new NestedClass
{
mat = new Matrix()
{
Row0 = new Vector
{
X = 32,
Y = 64,
Z = 128,
},

Row1 = new Vector
{
X = 1,
Y = 2,
Z = 3,
},

Row2 = new Vector
{
X = 5,
Y = 6,
Z = 9,
},
},

name = "mat",
age = 28

};

var result = mat.Apply(vec);

for (int i = 0; i < 10; ++i)
Expand Down Expand Up @@ -77,3 +108,11 @@ public Vector Apply(Vector target)
};
}
}

class NestedClass
{
public required Matrix mat { get; init; }
public required string name { get; init; }
public required int age { get; init; }

}
134 changes: 134 additions & 0 deletions server/src/HeapConnections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Variable } from "./DapvizProvider";
import { QuadraticBezierLine, QuadraticBezierLineRef } from "@react-three/drei";
import React, { createContext, useMemo, useRef } from "react";
import { useCallback, useState } from "react";
import * as THREE from "three";
import { useFrame } from "@react-three/fiber";


export type HeapConnectionContextType = {
registerNode: (id: number, ref: React.RefObject<THREE.Group>) => void;
unregisterNode: (id: number) => void;
};

export const HeapConnectionContext = createContext<HeapConnectionContextType | null>(null);

Check warning on line 14 in server/src/HeapConnections.tsx

View workflow job for this annotation

GitHub Actions / Run CI for server

Fast refresh only works when a file only exports components. Move your React context(s) to a separate file


interface ConnectionLineProps {
parentRef: React.RefObject<THREE.Group>;
childRef: React.RefObject<THREE.Group>;
}

export const ConnectionLine = ({ parentRef, childRef }: ConnectionLineProps) => {

const lineRef = useRef<QuadraticBezierLineRef>(null);

const startCircleRef = useRef<THREE.Mesh>(null);
const endCircleRef = useRef<THREE.Mesh>(null);

const startPos = useMemo(() => new THREE.Vector3(), []);
const endPos = useMemo(() => new THREE.Vector3(), []);
const midPos = useMemo(() => new THREE.Vector3(), []);
const box = useMemo(() => new THREE.Box3(), []);
const size = useMemo(() => new THREE.Vector3(), []);

useFrame(() => {
if (parentRef.current && childRef.current && lineRef.current) {
parentRef.current.getWorldPosition(startPos);
childRef.current.getWorldPosition(endPos);

box.setFromObject(parentRef.current);
box.getSize(size);
const startOffset = size.x / 2;

box.setFromObject(childRef.current);
box.getSize(size);
const endOffset = size.x / 2;

const padding = 3;

const finalStart = startPos.clone();
finalStart.x += startOffset + padding;

const finalEnd = endPos.clone();
finalEnd.x -= endOffset + padding;


midPos.addVectors(finalStart, finalEnd).multiplyScalar(0.5);

midPos.y += 80;

lineRef.current.setPoints(finalStart, finalEnd, midPos);
startCircleRef.current?.position.copy(finalStart);
endCircleRef.current?.position.copy(finalEnd);
}
});

return (
<>
<QuadraticBezierLine
ref={lineRef}
start={[0, 0, 0]}
end={[0, 0, 0]}
color="#a89984"
lineWidth={1}
/>

<mesh ref={startCircleRef}>
<circleGeometry args={[2.5, 16]} />
<meshBasicMaterial color="#689d6a" />
</mesh>
<mesh ref={endCircleRef}>
<circleGeometry args={[2.5, 16]} />
<meshBasicMaterial color="#d79921" />
</mesh>
</>

);
}

export const HeapConnectionsProvider = ({ children, allVariables }: { children: React.ReactNode, allVariables: Variable[] }) => {

const [nodeRefs, setNodeRefs] = useState<Map<number, React.RefObject<THREE.Group>>>(new Map());

const registerNode = useCallback((id: number, ref: React.RefObject<THREE.Group>) => {
setNodeRefs(prev => new Map(prev).set(id, ref));
}, []);

const unregisterNode = useCallback((id: number) => {
setNodeRefs(prev => {
const newMap = new Map(prev);
newMap.delete(id);
return newMap;
});
}, []);

const connections = useMemo(() =>
allVariables.filter(v => v.parent && v.parent > 0 && v.reference > 0),
[allVariables]
);

return (
<HeapConnectionContext.Provider value={{ registerNode, unregisterNode }}>
{children}

<group>
{connections.map(variable => {
const parentRef = nodeRefs.get(variable.parent as number);
const childRef = nodeRefs.get(variable.reference);

if (parentRef && childRef) {
return (
<ConnectionLine
key={`${variable.parent}-${variable.reference}`}
parentRef={parentRef}
childRef={childRef}
/>
);
}
return null;
})}
</group>
</HeapConnectionContext.Provider>
);
}
124 changes: 98 additions & 26 deletions server/src/Visualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { StackFrame, ThreadInfo, Variable } from "./DapvizProvider";
import DebugJsonInfo from "./DebugJsonInfo";
import { Container, DefaultProperties, Root, Text } from "@react-three/uikit";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./components/default/accordion";
import { HeapConnectionContext, HeapConnectionsProvider } from "./HeapConnections";
import * as THREE from "three";
import { useContext, useEffect, useMemo, useRef } from "react";


const BackgroundGrid = () => {
const size = 100;
Expand All @@ -23,7 +27,7 @@ const BackgroundGrid = () => {
);
}

const VariableViz = ({ variable }: { variable: Variable }) => {
const StackFrameVariable = ({ variable }: { variable: Variable }) => {
return (
<Container flexDirection="row" justifyContent="space-between" width="auto">
<Text fontSize={16} color="white">{variable.name}</Text>
Expand All @@ -32,16 +36,29 @@ const VariableViz = ({ variable }: { variable: Variable }) => {
);
}


const PrimitiveVariable = ({ variable }: { variable: Variable }) => {
return (
<Container flexDirection="row" justifyContent="space-around" width="auto" renderOrder={1}>
<Text fontSize={16} color="#d65d0e">{variable.type}</Text>
<Text fontSize={16} color="#ebdbb2">{variable.name}</Text>
<Text fontSize={16} color="#ebdbb2">{variable.value}</Text>
</Container>
);
}

const StackFrameViz = ({ stackFrame }: { stackFrame: StackFrame }) => {

const allFrameVariables = stackFrame.scopes.flatMap((scope) => scope.variables);
const rootVariables = allFrameVariables.filter((variable) => variable.parent === null);
const rootVariables = useMemo(() => {
const allFrameVariables = stackFrame.scopes.flatMap((scope) => scope.variables);
return allFrameVariables.filter((variable) => variable.parent === null);
}, [stackFrame.scopes]);

return (
<Container flexDirection="column-reverse" flexGrow={1}>
<Accordion width="auto">
{rootVariables.map((variable) => (
<VariableViz
<StackFrameVariable
key={variable.name}
variable={variable}
/>
Expand Down Expand Up @@ -78,28 +95,77 @@ const HeapNode = ({ variable, allVariables, initialPosition }: {
initialPosition: [number, number, number],
}) => {

const primitiveChildren = allVariables.filter(v => v.parent === variable.reference && v.reference === 0);
const referenceChildren = allVariables.filter(v => v.parent === variable.reference && v.reference > 0);
const groupRef = useRef<THREE.Group>(null);
const context = useContext(HeapConnectionContext);

if (!context) {
throw new Error("HeapNode must be used within a HeapConnectionsProvider");
}

const { registerNode, unregisterNode } = context;

const xOffset = 150;
useEffect(() => {
if (variable.reference > 0 && groupRef.current) {
registerNode(variable.reference, groupRef as React.RefObject<THREE.Group>);
}

return () => {
if (variable.reference > 0) {
unregisterNode(variable.reference);
}
};
}, [variable.reference, registerNode, unregisterNode]);

const { primitiveChildren, referenceChildren } = useMemo(() => {
const pChildren: Variable[] = [];
const rChildren: Variable[] = [];
for (const v of allVariables) {
if (v.parent === variable.reference) {
if (v.reference === 0) {
pChildren.push(v);
} else if (v.reference > 0) {
rChildren.push(v);
}
}
}
return { primitiveChildren: pChildren, referenceChildren: rChildren };
}, [allVariables, variable.reference]);

const xOffset = 250;
const yOffset = 100;

return (
<>
<DragControls>
<group position={initialPosition} >
<group ref={groupRef} position={initialPosition} >
<Root justifyContent="flex-start" flexDirection="column" pixelSize={0.5}>
<DefaultProperties fontWeight="medium" >
<Container
flexDirection="column"
backgroundColor={"#333"}
padding={20}
backgroundColor={"#282828"}
hover={{ backgroundColor: "#3c3836" }}
padding={15}
borderRadius={12}
borderWidth={1}
borderColor={"#504945"}
>
<Text fontSize={20} color="white">{variable.name} : [{variable.type}]</Text>

{primitiveChildren.map(child => (
<VariableViz key={child.name} variable={child} />
))}
<Container flexDirection="row" justifyContent="space-evenly" alignItems="center" paddingBottom={10}>
<Text fontSize={22} color="#ebdbb2" fontWeight="bold" paddingRight={24} renderOrder={1}>
{variable.name}
</Text>
<Text fontSize={16} color="#a89984" fontWeight="thin" renderOrder={1}>
{variable.type}
</Text>
</Container>

<Container height={1} backgroundColor="#4A4A5A" marginY={4} />

<Container flexDirection="column" marginTop={14} gap={8}>
{primitiveChildren.map(child => (
<PrimitiveVariable key={child.name} variable={child} />
))}
</Container>

</Container>
</DefaultProperties>
Expand Down Expand Up @@ -132,11 +198,15 @@ const HeapNode = ({ variable, allVariables, initialPosition }: {
}

const Visualizer = ({ thread }: { thread: ThreadInfo }) => {
const allVariables = thread.stack_frames.flatMap(frame =>
frame.scopes.flatMap(scope => scope.variables)
);

const heapVariables = allVariables.filter((variable) => variable.reference > 0 && variable.parent === null);
const allVariables = useMemo(() =>
thread.stack_frames.flatMap(frame =>
frame.scopes.flatMap(scope => scope.variables)
), [thread.stack_frames]);

const heapVariables = useMemo(() =>
allVariables.filter((variable) => variable.reference > 0 && variable.parent === null),
[allVariables]);

return (
<>
Expand All @@ -149,14 +219,16 @@ const Visualizer = ({ thread }: { thread: ThreadInfo }) => {
</Root>
</DragControls>

{heapVariables.map((variable, index) => (
<HeapNode
key={variable.name}
variable={variable}
allVariables={allVariables}
initialPosition={[300, index * -200, 0]}
/>
))}
<HeapConnectionsProvider allVariables={allVariables}>
{heapVariables.map((variable, index) => (
<HeapNode
key={variable.name}
variable={variable}
allVariables={allVariables}
initialPosition={[300, index * -200, 0]}
/>
))}
</HeapConnectionsProvider>

<BackgroundGrid />
<MapControls maxZoom={2} minZoom={0.10} makeDefault />
Expand Down