From 70daf4a16dc838adf19ac55532a18600dde4bf2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Tue, 15 Oct 2024 15:55:52 +0200 Subject: [PATCH] tracer: Virtualize the event view list To keep the UI responsive as the number of events grows. --- apps/tracer/package.json | 1 + apps/tracer/src/EventView.tsx | 67 ++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/apps/tracer/package.json b/apps/tracer/package.json index 0e42de8..60321c5 100644 --- a/apps/tracer/package.json +++ b/apps/tracer/package.json @@ -19,6 +19,7 @@ "react-resplit": "^1.3.2-alpha.0", "react-stay-at-bottom": "^1.1.1", "react-use-websocket": "^4.8.1", + "react-viewport-list": "^7.1.2", "use-debounce": "^10.0.3" }, "devDependencies": { diff --git a/apps/tracer/src/EventView.tsx b/apps/tracer/src/EventView.tsx index a45f832..4e98f55 100644 --- a/apps/tracer/src/EventView.tsx +++ b/apps/tracer/src/EventView.tsx @@ -4,6 +4,7 @@ import { Button, Card } from "@blueprintjs/core"; import Ansi from "@curvenote/ansi-to-react"; import { ReactElement, useCallback, useEffect, useRef, useState } from "react"; import { useStayAtBottom } from "react-stay-at-bottom"; +import { ViewportList } from "react-viewport-list"; export interface EventViewProps { events: Event[]; @@ -31,15 +32,28 @@ export default function EventView({ }: EventViewProps) { const containerRef = useRef(null); const selectedRef = useRef(null); + const [items, setItems] = useState<(EventItem | ThreadIdMarkerItem)[]>([]); const [selectedCallerSymbol, setSelectedCallerSymbol] = useState(""); const [selectedBacktraceSymbols, setSelectedBacktraceSymbols] = useState(null); - let lastTid: number | null = null; useStayAtBottom(containerRef, { initialStay: true, autoStay: true }); + useEffect(() => { + let lastTid: number | null = null; + setItems(events.reduce((result, event, i) => { + const [_targetId, _timestamp, threadId, _depth, _caller, _backtrace, _message, style] = event; + if (threadId !== lastTid) { + result.push([i, threadId, style]); + lastTid = threadId; + } + result.push([i, event]); + return result; + }, [] as (EventItem | ThreadIdMarkerItem)[])); + }, [events]); + useEffect(() => { const item = selectedRef.current; if (item === null) { @@ -137,34 +151,38 @@ export default function EventView({ return (
- { - events.reduce((result, [targetId, timestamp, threadId, depth, _caller, _backtrace, message, style], i) => { - let timestampStr = timestamp.toString(); - const timestampPaddingNeeded = Math.max(6 - timestampStr.length, 0); - for (let i = 0; i !== timestampPaddingNeeded; i++) { - timestampStr = NON_BLOCKING_SPACE + timestampStr; - } - - const colorClass = "ansi-" + style.join("-"); - - if (threadId !== lastTid) { - result.push( -
+ + {(item) => { + if (item.length === 3) { + const [index, threadId, style] = item; + const colorClass = "ansi-" + style.join("-"); + return ( +
/* TID 0x{threadId.toString(16)} */
); - lastTid = threadId; } - const isSelected = i === selectedIndex; + const [index, event] = item; + const [targetId, timestamp, _threadId, depth, _caller, _backtrace, message, style] = event; + + const isSelected = index === selectedIndex; const eventClasses = ["event-item"]; if (isSelected) { eventClasses.push("event-selected"); } - result.push( + let timestampStr = timestamp.toString(); + const timestampPaddingNeeded = Math.max(6 - timestampStr.length, 0); + for (let i = 0; i !== timestampPaddingNeeded; i++) { + timestampStr = NON_BLOCKING_SPACE + timestampStr; + } + + const colorClass = "ansi-" + style.join("-"); + + return (
@@ -175,7 +193,7 @@ export default function EventView({ className={"event-message " + colorClass} minimal={true} alignText="left" - onClick={() => onActivate(targetId, i)} + onClick={() => onActivate(targetId, index)} > {message} @@ -183,10 +201,11 @@ export default function EventView({ {isSelected ? selectedEventDetails : null}
); - - return result; - }, [] as JSX.Element[]) - } + }} +
); -} \ No newline at end of file +} + +type EventItem = [index: number, event: Event]; +type ThreadIdMarkerItem = [index: number, threadId: number, style: string[]];