Skip to content

Commit

Permalink
Performance improvements for complex traces
Browse files Browse the repository at this point in the history
  • Loading branch information
spaaaacccee committed May 14, 2024
1 parent ebe440d commit 1e8b55c
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 70 deletions.
24 changes: 12 additions & 12 deletions client/src/components/app-bar/Playback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
PlayArrowOutlined as PlayIcon,
ChevronLeftOutlined as PreviousIcon,
SkipNextOutlined as SkipIcon,
StopOutlined as StopIcon,
SkipPreviousOutlined as StopIcon,
} from "@mui/icons-material";
import {
Button,
Expand Down Expand Up @@ -91,17 +91,23 @@ export function Playback({ layer }: { layer?: Layer<PlaybackLayerData> }) {
play,
stepBackward,
stepForward,
stop,
stepWithBreakpointCheck,
findBreakpoint,
step,
end,
stepTo,
} = usePlaybackState(layer?.key);
const [stepInput, setStepInput] = useState("");
const parsedStepInput = parseInt(stepInput);
const parsedStepInputValid = !isNaN(parsedStepInput);
return (
<>
<IconButton
label="previous-breakpoint"
icon={<StopIcon />}
onClick={() => {
stepTo(findBreakpoint(-1));
}}
disabled={!canStop}
/>
<IconButton
label="step-backward"
icon={<PreviousIcon />}
Expand Down Expand Up @@ -131,16 +137,10 @@ export function Playback({ layer }: { layer?: Layer<PlaybackLayerData> }) {
disabled={!canStepForward}
/>
<IconButton
label="stop"
icon={<StopIcon />}
onClick={stop}
disabled={!canStop}
/>
<IconButton
label="step-to-next-breakpoint"
label="next-breakpoint"
icon={<SkipIcon />}
onClick={() => {
stepWithBreakpointCheck(end - step, 1);
stepTo(findBreakpoint());
}}
disabled={!canStepForward}
/>
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/inspector/TraceRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Box, CircularProgress, useTheme } from "@mui/material";
import { RendererProps, SelectEvent } from "components/renderer/Renderer";
import { usePlaybackState } from "hooks/usePlaybackState";
import { RenderLayer } from "layers/RenderLayer";
import { clamp, find, map } from "lodash";
import { clamp, find, floor, map } from "lodash";
import { nanoid } from "nanoid";
import { Size } from "protocol";
import {
Expand All @@ -32,7 +32,8 @@ const tileSize = (playing: boolean = false) =>

const rendererOptions = {
tileSubdivision: 2,
workerCount: clamp(navigator.hardwareConcurrency - 1, 1, 12),
// Use 50% of available CPUs
workerCount: clamp(floor((navigator.hardwareConcurrency - 1) / 2), 1, 12),
tileResolution: {
width: tileSize(),
height: tileSize(),
Expand Down
32 changes: 12 additions & 20 deletions client/src/components/layer-editor/LayerEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
startCase,
truncate,
} from "lodash";
import { produce } from "produce";
import {
ForwardedRef,
ReactNode,
Expand Down Expand Up @@ -82,7 +81,9 @@ function useDraft<T>(
const [state, setState] = useState(initial);
useEffect(() => {
if (initial) {
setState(merge(state, omit(initial, ...stayDraft)));
requestIdleCallback(() =>
setState(merge(state, omit(initial, ...stayDraft)))
);
}
}, [setState, initial]);
const handleChange = useMemo(
Expand All @@ -91,9 +92,10 @@ function useDraft<T>(
);
return [
state,
(value: T) => {
setState(value);
handleChange(value);
(value: (prev: T) => T) => {
const next = value(state);
setState(next);
handleChange(next);
},
] as const;
}
Expand Down Expand Up @@ -221,19 +223,13 @@ function Component(
variant="filled"
label="Layer Name"
defaultValue={draft.name ?? ""}
onChange={(e) =>
setDraft?.(produce(draft, (d) => set(d, "name", e.target.value)))
}
onChange={(e) => setDraft?.((d) => set(d, "name", e.target.value))}
/>
<Box sx={{ mx: -2, pb: 1 }}>
<Tabs
variant="fullWidth"
onChange={(_, v) =>
setDraft?.(
produce(draft, (d) => {
set(d, "source", { type: v });
})
)
setDraft?.((d) => set(d, "source", { type: v }))
}
value={draft.source?.type ?? first(keys(layerHandlers)) ?? ""}
>
Expand All @@ -247,7 +243,7 @@ function Component(
{renderHeading("Source Options")}
{draft.source?.type &&
createElement(layerHandlers[draft.source.type].editor, {
onChange: (e) => setDraft(e(draft)),
onChange: setDraft,
value: draft,
})}
{renderHeading("Layer Options")}
Expand All @@ -261,9 +257,7 @@ function Component(
}))}
value={draft.transparency ?? "0"}
arrow
onChange={(e) =>
setDraft?.(produce(draft, (d) => set(d, "transparency", e)))
}
onChange={(e) => setDraft?.((d) => set(d, "transparency", e))}
/>
)}
{renderOption(
Expand All @@ -273,9 +267,7 @@ function Component(
label="Display Mode"
value={draft.displayMode ?? "source-over"}
items={options(compositeOperations)}
onChange={(e) =>
setDraft?.(produce(draft, (d) => set(d, "displayMode", e)))
}
onChange={(e) => setDraft?.((d) => set(d, "displayMode", e))}
/>
)}
</Box>
Expand Down
104 changes: 85 additions & 19 deletions client/src/components/renderer/NodeList.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,113 @@
import { useRendererInstance } from "components/inspector/TraceRenderer";
import { floor, slice } from "lodash";
import { useEffect, useMemo } from "react";
import { ComponentEntry } from "renderer";
import { useRendererInstance } from "components/inspector/TraceRenderer";
/**
* For distinguish between persisted views like grid, mesh, tree, map
* and non-persisted views like tile and multiagent
* if true, then use split nodelists and draw every history event
* if false, use a single nodelist and draw current event
*/

function generateNumberArray(n: number) {
const numberArray = [0];
const digits = n.toString(2).split("").map(Number);

for (let i = 0; i < digits.length; i++) {
const power = digits.length - i - 1;
const newNumber = digits.slice(0, i + 1).join("") + "0".repeat(power);
numberArray.push(parseInt(newNumber, 2));
}

return numberArray;
}

function pairwise<T, U>(arr: T[], func: (x: T, y: T) => U) {
const out: U[] = [];
for (let i = 0; i < arr.length - 1; i++) {
out.push(func(arr[i], arr[i + 1]));
}
return out;
}

export type NodeListProps = {
nodes?: ComponentEntry[][];
start?: number;
end?: number;
};

export type LazyNodeListProps = NodeListProps & {
step?: number;
export type NodeList2Props = {
nodes?: ComponentEntry[];
};

export function NodeList({ nodes }: NodeListProps) {
export type LazyNodeListProps = NodeListProps;

export function NodeList({
nodes,
start = 0,
end: step = nodes?.length ?? 0,
}: NodeListProps) {
const { renderer } = useRendererInstance();
useEffect(() => {
if (renderer && nodes?.length) {
return renderer.add(nodes.flat());
return renderer.add(slice(nodes, start, step).flat());
}
}, [renderer, nodes, start, step]);

return <></>;
}
export function NodeList2({ nodes }: NodeList2Props) {
const { renderer } = useRendererInstance();
useEffect(() => {
if (renderer && nodes?.length) {
return renderer.add(nodes);
}
}, [renderer, nodes]);

return <></>;
}
// export function LazyNodeList({ nodes, start = 0, end = 0 }: LazyNodeListProps) {
// return pairwise(range(start, end, 1), (x, y) => (
// <NodeList key={`${x}::${y}`} nodes={nodes} start={x} end={y} />
// ));
// const c2 = generateNumberArray(end);
// return pairwise(c2, (start, end) => {
// const end2 = end + 1;
// const cacheSize = 2;
// const run = end2 - start;
// // number of nodes needed to be cached
// const threshold = floor((run ?? 0) / 2 / cacheSize) * cacheSize;
// return [
// !!threshold && (
// <NodeList
// key={`${start}::${start + threshold}`}
// nodes={nodes}
// start={start}
// end={start + threshold}
// />
// ),
// !!(end2 - (start + threshold)) && (
// <NodeList
// key={`${start + threshold}::${end2}`}
// nodes={nodes}
// start={start + threshold}
// end={end2}
// />
// ),
// ];
// }).flat();
// }

export function LazyNodeList({ nodes, step }: LazyNodeListProps) {
const cacheSize = 200;
export function LazyNodeList({ nodes, end }: LazyNodeListProps) {
const cacheSize = 100;

// number of nodes needed to be cached
const threshold = floor((step ?? 0) / cacheSize) * cacheSize;
const threshold = floor((end ?? 0) / cacheSize) * cacheSize;

const cached = useMemo(() => slice(nodes, 0, threshold), [nodes, threshold]);
const uncached = useMemo(
() => slice(nodes, threshold, (step ?? 0) + 1),
[nodes, threshold, step]
() => slice(nodes, threshold, (end ?? 0) + 1),
[nodes, threshold, end]
);
return (
<>
{!!threshold && <NodeList nodes={cached} />}
<NodeList nodes={uncached} />
{uncached.map((c, i) => (
<NodeList2 key={threshold + i} nodes={c}></NodeList2>
))}
</>
);
}
}
11 changes: 10 additions & 1 deletion client/src/hooks/usePlaybackState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function usePlaybackState(key?: string) {
step,
canPlay: ready && !playing && step < end,
canPause: ready && playing,
canStop: ready,
canStop: ready && step,
canStepForward: ready && !playing && step < end,
canStepBackward: ready && !playing && step > 0,
};
Expand Down Expand Up @@ -81,6 +81,14 @@ export function usePlaybackState(key?: string) {
}
);

const findBreakpoint = (direction: 1 | -1 = 1) => {
let i;
for (i = step + direction; i <= end && i >= 0; i += direction) {
if (shouldBreak(i)?.result) break;
}
return i;
};

const stepBy = (n: number) => clamp(step + n, start, end);

const callbacks = {
Expand All @@ -94,6 +102,7 @@ export function usePlaybackState(key?: string) {
stepForward: () => setPlaybackState({ step: stepBy(1) }),
stepBackward: () => setPlaybackState({ step: stepBy(-1) }),
tick,
findBreakpoint,
stepWithBreakpointCheck,
};

Expand Down
5 changes: 5 additions & 0 deletions client/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import App from "App";
import "index.css";
import { defer, set } from "lodash";
import "overlayscrollbars/overlayscrollbars.css";
import { createRoot } from "react-dom/client";
import { SliceProvider as EnvironmentProvider } from "slices/SliceProvider";
Expand All @@ -15,6 +16,10 @@ import { ScreenshotsProvider } from "slices/screenshots";
import { SettingsProvider } from "slices/settings";
import { ViewProvider } from "slices/view";

if (!("requestIdleCallback" in window)) {
set(window, "requestIdleCallback", (f: () => void) => defer(f));
}

const root = createRoot(document.getElementById("root")!);

const slices = [
Expand Down
Loading

0 comments on commit 1e8b55c

Please sign in to comment.