Skip to content

Fix: Unnecessary Re-rendering #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
133 changes: 38 additions & 95 deletions ui/src/components/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ const ScopeNode = memo<Props>(({ data, id, isConnectable }) => {
const [frame] = React.useState({
translate: [0, 0],
});
const setSelected = useStore(store, (state) => state.setSelected);
const selected = useStore(store, (state) => state.selected);
const { setNodes } = useReactFlow();

const onResize = useCallback(({ width, height, offx, offy }) => {
const node = nodesMap.get(id);
Expand Down Expand Up @@ -186,6 +184,8 @@ const ScopeNode = memo<Props>(({ data, id, isConnectable }) => {
);
});

// FIXME: the resultblock is rendered every time the parent codeNode changes (e.g., dragging), we may set the result number as a state of a pod to memoize the resultblock.

function ResultBlock({ pod, id }) {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
Expand Down Expand Up @@ -252,12 +252,8 @@ function ResultBlock({ pod, id }) {
const CodeNode = memo<Props>(({ data, id, isConnectable }) => {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const pod = useStore(store, (state) => state.pods[id]);
const setPodContent = useStore(store, (state) => state.setPodContent);
const updatePod = useStore(store, (state) => state.updatePod);
const clearResults = useStore(store, (s) => s.clearResults);
// const pod = useStore(store, (state) => state.pods[id]);
const wsRun = useStore(store, (state) => state.wsRun);
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
const ref = useRef(null);
const [target, setTarget] = React.useState<any>(null);
const [frame] = React.useState({
Expand All @@ -266,30 +262,25 @@ const CodeNode = memo<Props>(({ data, id, isConnectable }) => {
// right, bottom
const [layout, setLayout] = useState("right");
const { setNodes } = useReactFlow();
const selected = useStore(store, (state) => state.selected);
// const selected = useStore(store, (state) => state.selected);
const setSelected = useStore(store, (state) => state.setSelected);
const getPod = useStore(store, (state) => state.getPod);
const pod = getPod(id);

const showResult = useStore(
store,
(state) =>
state.pods[id].running ||
state.pods[id].result ||
state.pods[id].error ||
state.pods[id].stdout ||
state.pods[id].stderr
);

const onResize = useCallback(({ width, height, offx, offy }) => {
const node = nodesMap.get(id);
if (node) {
node.style = { ...node.style, width, height };
node.position.x += offx;
node.position.y += offy;
nodesMap.set(id, node);
}
}, []);
const onLayout = useCallback(({ height }) => {
const node = nodesMap.get(id);
if (node) {
node.style = { ...node.style, height };
nodesMap.set(id, node);
}
}, []);

React.useEffect(() => {
useEffect(() => {
setTarget(ref.current);
}, []);
if (!pod) return <Box>ERROR</Box>;
// if (!pod) return <Box>ERROR</Box>;
return (
<Box
sx={{
Expand Down Expand Up @@ -387,24 +378,8 @@ const CodeNode = memo<Props>(({ data, id, isConnectable }) => {
);
}}
>
<MyMonaco
value={pod.content || ""}
id={pod.id}
onChange={(value) => {
setPodContent({ id: pod.id, content: value });
}}
lang={pod.lang || "javascript"}
onRun={() => {
clearResults(pod.id);
wsRun(pod.id);
}}
onLayout={onLayout}
/>
{(pod.running ||
pod.stdout ||
pod.stderr ||
pod.result ||
pod.error) && (
<MyMonaco id={id} gitvalue="" />
{showResult && (
<Box
className="nowheel"
sx={{
Expand All @@ -424,43 +399,6 @@ const CodeNode = memo<Props>(({ data, id, isConnectable }) => {
</Box>
)}
</Box>
{false && (
<Moveable
target={target}
resizable={true}
keepRatio={false}
throttleResize={1}
renderDirections={["e", "s", "se"]}
edge={false}
zoom={1}
origin={true}
padding={{ left: 0, top: 0, right: 0, bottom: 0 }}
onResizeStart={(e) => {
e.setOrigin(["%", "%"]);
e.dragStart && e.dragStart.set(frame.translate);
}}
onResize={(e) => {
const beforeTranslate = e.drag.beforeTranslate;
frame.translate = beforeTranslate;
e.target.style.width = `${e.width}px`;
e.target.style.height = `${e.height}px`;
e.target.style.transform = `translate(${beforeTranslate[0]}px, ${beforeTranslate[1]}px)`;
onResize({
width: e.width,
height: e.height,
offx: beforeTranslate[0],
offy: beforeTranslate[1],
});
updatePod({
id,
data: {
width: e.width,
height: e.height,
},
});
}}
/>
)}
</Box>
);
});
Expand All @@ -484,35 +422,39 @@ export function Canvas() {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
// the real pods
const id2children = useStore(store, (state) => state.id2children);
const pods = useStore(store, (state) => state.pods);
const getId2children = useStore(store, (state) => state.getId2children);
// const pods = useStore(store, (state) => state.pods);
const getPod = useStore(store, (state) => state.getPod);
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));

const getRealNodes = useCallback(
(id, level) => {
let res: any[] = [];
let children = id2children[id];
let children = getId2children(id) || [];
console.log("getChildren", id, children);
const pod = getPod(id);
if (id !== "ROOT") {
res.push({
id: id,
type: pods[id].type === "CODE" ? "code" : "scope",
type: pod.type === "CODE" ? "code" : "scope",
data: {
// label: `ID: ${id}, parent: ${pods[id].parent}, pos: ${pods[id].x}, ${pods[id].y}`,
label: id,
},
// position: { x: 100, y: 100 },
position: { x: pods[id].x, y: pods[id].y },
parentNode: pods[id].parent !== "ROOT" ? pods[id].parent : undefined,
position: { x: pod.x, y: pod.y },
parentNode: pod.parent !== "ROOT" ? pod.parent : undefined,
extent: "parent",
dragHandle: ".custom-drag-handle",
level,
style: {
backgroundColor:
pods[id].type === "CODE"
pod.type === "CODE"
? undefined
: level2color[level] || level2color["default"],
width: 700,
height: pods[id].height,
// for code node, don't set height, let it be auto
height: pod.height || undefined,
},
});
}
Expand All @@ -521,7 +463,7 @@ export function Canvas() {
}
return res;
},
[id2children, pods]
[getId2children, getPod]
);
useEffect(() => {
let nodes = getRealNodes("ROOT", -1);
Expand Down Expand Up @@ -593,7 +535,8 @@ export function Canvas() {
} else {
style = {
width: 700,
height: 300,
// we must not set the height here, otherwise the auto layout will not work
height: undefined,
};
}

Expand Down Expand Up @@ -640,19 +583,19 @@ export function Canvas() {
(node) => {
let x = node.position.x;
let y = node.position.y;
let parent = pods[node.parent];
let parent = getPod(node.parent);
while (parent) {
x += parent.x;
y += parent.y;
if (parent.parent) {
parent = pods[parent.parent];
parent = getPod(parent.parent);
} else {
break;
}
}
return [x, y];
},
[pods]
[getPod]
);

const getScopeAt = useCallback(
Expand Down
31 changes: 21 additions & 10 deletions ui/src/components/MyMonaco.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Position } from "monaco-editor";
import { useState, useContext } from "react";
import { useState, useContext, memo } from "react";
import MonacoEditor, { MonacoDiffEditor } from "react-monaco-editor";
import { monaco } from "react-monaco-editor";
import { useStore } from "zustand";
Expand Down Expand Up @@ -298,20 +298,31 @@ async function updateGitGutter(editor) {
// not, and the instance will only be mounted once. All variables, even a object
// like the pod object will be fixed at original state: changing pod.staged
// won't be visible in the editorDidMount callback.
export function MyMonaco({
lang = "javascript",
value = "",

interface MyMonacoProps {
id: string;
gitvalue: string;
}

export const MyMonaco = memo<MyMonacoProps>(function MyMonaco({
id = "0",
gitvalue = null,
onChange = (value) => {},
onRun = () => {},
onLayout = (height) => {},
}) {
// console.log("rendering monaco ..");
// there's no racket language support
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const showLineNumbers = useStore(store, (state) => state.showLineNumbers);
const getPod = useStore(store, (state) => state.getPod);
const setPodContent = useStore(store, (state) => state.setPodContent);
const clearResults = useStore(store, (s) => s.clearResults);
const wsRun = useStore(store, (state) => state.wsRun);
const value = getPod(id).content || "";
let lang = getPod(id).lang || "javascript";
const onChange = (value) => setPodContent({ id, content: value });
const onRun = () => {
clearResults(id);
wsRun(id);
};

if (lang === "racket") {
lang = "scheme";
Expand Down Expand Up @@ -346,7 +357,7 @@ export function MyMonaco({
// width: 800
// editor.layout({ width: 800, height: contentHeight });
editor.layout();
onLayout(`${contentHeight}px`);
// onLayout(`${contentHeight}px`);
};
editor.onDidContentSizeChange(updateHeight);
// FIXME clean up?
Expand Down Expand Up @@ -420,4 +431,4 @@ export function MyMonaco({
editorDidMount={onEditorDidMount}
/>
);
}
});
9 changes: 8 additions & 1 deletion ui/src/lib/nodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,22 @@ export function useNodesStateSynced(nodeList) {
if (!isNodeAddChange(change) && !isNodeResetChange(change)) {
if (isNodeRemoveChange(change)) {
nodesMap.delete(change.id);
return;
}
const node = nextNodes.find((n) => n.id === change.id);
if (!node) return;

if (change.type === "select") {
if (change.type === "select" && change.selected) {
// FIXME: consider the case where only unselect is called
setSelected(node.id);
return;
}

if (change.type === "dimensions" && node.type === "code") {
// the re-size event of codeNode don't need to be sync, just skip.
return;
}

if (node) {
nodesMap.set(change.id, node);
}
Expand Down
6 changes: 6 additions & 0 deletions ui/src/lib/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ export interface RepoSlice {
deleteClient: (clientId: any) => void;
flipShowLineNumbers: () => void;
disconnect: () => void;
getPod: (string) => any;
getPods: () => any;
getId2children: (string) => string[];
}

type BearState = RepoSlice & RuntimeSlice;
Expand Down Expand Up @@ -752,6 +755,9 @@ const createRepoSlice: StateCreator<
state.ydoc.destroy();
})
),
getPod: (id: string) => get().pods[id],
getPods: () => get().pods,
getId2children: (id: string) => get().id2children[id],
});

export const createRepoStore = () =>
Expand Down