Skip to content

Commit

Permalink
add render log tab (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
seveibar authored Jan 8, 2025
1 parent d650088 commit c2199e7
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 16 deletions.
Binary file modified bun.lockb
Binary file not shown.
21 changes: 21 additions & 0 deletions examples/example6-render-logs.fixture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { RunFrame } from "lib/components/RunFrame"
import React from "react"

export default () => (
<RunFrame
fsMap={{
"main.tsx": `
circuit.add(
<board width="10mm" height="10mm">
<resistor name="R1" resistance="1k" footprint="0402" />
<capacitor name="C1" capacitance="1uF" footprint="0603" pcbX={4} />
<trace from=".R1 .pin1" to=".C1 .pin1" />
</board>
)
`,
}}
defaultActiveTab="render_log"
entrypoint="main.tsx"
// showRenderLogTab
/>
)
20 changes: 20 additions & 0 deletions examples/example7-large-led-matrix.fixture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { RunFrame } from "lib/components/RunFrame"
import React from "react"

export default () => (
<RunFrame
fsMap={{
"main.tsx": `
import LedMatrix from "@tsci/seveibar.contribution-board"
circuit.add(
<LedMatrix />
)
`,
"manual-edits.json": "{}",
}}
defaultActiveTab="render_log"
entrypoint="main.tsx"
// showRenderLogTab
/>
)
59 changes: 46 additions & 13 deletions lib/components/CircuitJsonPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { cn } from "lib/utils"
import { applyPcbEditEvents } from "lib/utils/pcbManualEditEventHandler"
import { CadViewer } from "@tscircuit/3d-viewer"
import { PCBViewer } from "@tscircuit/pcb-viewer"
import { useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import { ErrorFallback } from "./ErrorFallback"
import { ErrorBoundary } from "react-error-boundary"
import { ErrorTabContent } from "./ErrorTabContent"
Expand All @@ -34,6 +34,19 @@ import { Button } from "./ui/button"
import { PcbViewerWithContainerHeight } from "./PcbViewerWithContainerHeight"
import { useStyles } from "lib/hooks/use-styles"
import type { ManualEditEvent } from "@tscircuit/props"
import type { RenderLog } from "lib/render-logging/RenderLog"
import { RenderLogViewer } from "./RenderLogViewer"

export type TabId =
| "code"
| "pcb"
| "schematic"
| "assembly"
| "cad"
| "bom"
| "circuitjson"
| "error"
| "render_log"

export interface PreviewContentProps {
code?: string
Expand All @@ -45,6 +58,7 @@ export interface PreviewContentProps {
circuitJsonKey?: string
className?: string
showCodeTab?: boolean
showRenderLogTab?: boolean
codeTabContent?: React.ReactNode
showJsonTab?: boolean
showImportAndFormatButtons?: boolean
Expand All @@ -62,18 +76,14 @@ export interface PreviewContentProps {
hasCodeChangedSinceLastRun?: boolean
// onManualEditsFileContentChange?: (newmanualEditsFileContent: string) => void

defaultActiveTab?:
| "code"
| "pcb"
| "schematic"
| "assembly"
| "cad"
| "bom"
| "circuitjson"
| "error"
defaultActiveTab?: TabId

renderLog?: RenderLog | null

onEditEvent?: (editEvent: ManualEditEvent) => void
editEvents?: ManualEditEvent[]

onActiveTabChange?: (tab: TabId) => any
}

export const CircuitJsonPreview = ({
Expand All @@ -86,6 +96,9 @@ export const CircuitJsonPreview = ({
showCodeTab = false,
codeTabContent,
showJsonTab = true,
showRenderLogTab = true,
onActiveTabChange,
renderLog,
showImportAndFormatButtons = true,
className,
headerClassName,
Expand All @@ -101,7 +114,15 @@ export const CircuitJsonPreview = ({
defaultActiveTab,
}: PreviewContentProps) => {
useStyles()
const [activeTab, setActiveTab] = useState(defaultActiveTab ?? "pcb")

const [activeTab, setActiveTabState] = useState(defaultActiveTab ?? "pcb")
const setActiveTab = useCallback(
(tab: TabId) => {
setActiveTabState(tab)
onActiveTabChange?.(tab)
},
[onActiveTabChange],
)

useEffect(() => {
if (errorMessage) {
Expand Down Expand Up @@ -229,6 +250,15 @@ export const CircuitJsonPreview = ({
/>
JSON
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => setActiveTab("render_log")}>
<CheckIcon
className={cn(
"w-3 h-3 mr-2",
activeTab !== "render_log" && "invisible",
)}
/>
Render Log
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TabsList>
Expand All @@ -247,7 +277,6 @@ export const CircuitJsonPreview = ({
<div className="h-full">{codeTabContent}</div>
</TabsContent>
)}

<TabsContent value="pcb">
<div
className={cn(
Expand Down Expand Up @@ -369,7 +398,6 @@ export const CircuitJsonPreview = ({
</ErrorBoundary>
</div>
</TabsContent>

<TabsContent value="circuitjson">
<div
className={cn(
Expand All @@ -393,6 +421,11 @@ export const CircuitJsonPreview = ({
<PreviewEmptyState onRunClicked={onRunClicked} />
)}
</TabsContent>
{showRenderLogTab && (
<TabsContent value="render_log">
<RenderLogViewer renderLog={renderLog} />
</TabsContent>
)}
</Tabs>
</div>
</div>
Expand Down
39 changes: 39 additions & 0 deletions lib/components/RenderLogViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { RenderLog } from "lib/render-logging/RenderLog"

export const RenderLogViewer = ({
renderLog,
}: { renderLog?: RenderLog | null }) => {
if (!renderLog)
return (
<div className="p-4 bg-gray-100 rounded-md">
No render log, make sure this tab is open when you render (TODO add a
rerender button here)
</div>
)

const orderedPhaseTimings = Object.entries(
renderLog?.phaseTimings ?? {},
).sort((a, b) => b[1] - a[1])

return (
<div>
<div>Render Logs</div>
<table className="w-full text-xs">
<thead>
<tr>
<th className="text-left p-2">Phase</th>
<th className="text-left p-2">Duration (ms)</th>
</tr>
</thead>
<tbody>
{orderedPhaseTimings.map(([phase, duration]) => (
<tr key={phase}>
<td className="p-2">{phase}</td>
<td className="p-2">{duration}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
33 changes: 31 additions & 2 deletions lib/components/RunFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createCircuitWebWorker } from "@tscircuit/eval-webworker"
import { CircuitJsonPreview } from "./CircuitJsonPreview"
import { CircuitJsonPreview, type TabId } from "./CircuitJsonPreview"
import { useEffect, useRef, useState } from "react"
import Debug from "debug"

Expand All @@ -14,6 +14,8 @@ import evalWebWorkerBlobUrl from "@tscircuit/eval-webworker/blob-url"
import type { ManualEditEvent } from "@tscircuit/props"
import { useRunFrameStore } from "./RunFrameWithApi/store"
import { getChangesBetweenFsMaps } from "../utils/getChangesBetweenFsMaps"
import type { RenderLog } from "lib/render-logging/RenderLog"
import { getPhaseTimingsFromRenderEvents } from "lib/render-logging/getPhaseTimingsFromRenderEvents"

interface Props {
/**
Expand Down Expand Up @@ -76,6 +78,10 @@ interface Props {
* If true, turns on debug logging
*/
debug?: boolean

defaultActiveTab?: Parameters<
typeof CircuitJsonPreview
>[0]["defaultActiveTab"]
}

export const RunFrame = (props: Props) => {
Expand All @@ -89,6 +95,10 @@ export const RunFrame = (props: Props) => {
error?: string
stack?: string
} | null>(null)
const [renderLog, setRenderLog] = useState<RenderLog | null>(null)
const [activeTab, setActiveTab] = useState<TabId>(
props.defaultActiveTab ?? "pcb",
)
useEffect(() => {
if (props.debug) Debug.enable("run-frame*")
}, [props.debug])
Expand Down Expand Up @@ -124,6 +134,7 @@ export const RunFrame = (props: Props) => {
async function runWorker() {
debug("running render worker")
setError(null)
const renderLog: RenderLog = {}
const worker: Awaited<ReturnType<typeof createCircuitWebWorker>> =
globalThis.runFrameWorker ??
(await createCircuitWebWorker({
Expand All @@ -144,6 +155,15 @@ export const RunFrame = (props: Props) => {
return
}

if (activeTab === "render_log") {
worker.on("renderable:renderLifecycle:anyEvent", (event: any) => {
renderLog.renderEvents = renderLog.renderEvents ?? []
event.createdAt = Date.now()
renderLog.renderEvents.push(event)
setRenderLog(renderLog)
})
}

const evalResult = await worker
.executeWithFsMap({
entrypoint: props.entrypoint,
Expand Down Expand Up @@ -182,15 +202,24 @@ export const RunFrame = (props: Props) => {
props.onCircuitJsonChange?.(circuitJson)
setCircuitJson(circuitJson)
props.onRenderFinished?.({ circuitJson })

if (activeTab === "render_log") {
renderLog.phaseTimings = getPhaseTimingsFromRenderEvents(
renderLog.renderEvents ?? [],
)
setRenderLog(renderLog)
}
}
runWorker()
}, [props.fsMap, props.entrypoint])

return (
<CircuitJsonPreview
defaultActiveTab="schematic"
defaultActiveTab={props.defaultActiveTab}
leftHeaderContent={props.leftHeaderContent}
onActiveTabChange={setActiveTab}
circuitJson={circuitJson}
renderLog={renderLog}
errorMessage={error?.error}
onEditEvent={props.onEditEvent}
editEvents={props.editEvents}
Expand Down
6 changes: 6 additions & 0 deletions lib/render-logging/RenderLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface RenderLog {
renderEvents?: any[]

// Not sure if we can do this because of async
phaseTimings?: Record<string, number>
}
47 changes: 47 additions & 0 deletions lib/render-logging/getPhaseTimingsFromRenderEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
type RenderEvent = {
type:
| `renderable:renderLifecycle:${string}:start`
| `renderable:renderLifecycle:${string}:end`
/**
* Corresponds to the element that was rendered
*/
renderId: string
createdAt: number
}
/**
* Given a list of render events, return a map of how much time was spent in each
* render phase.
*
* To get the time spent in each phase, you have to find the end event for each
* start event and subtract the createdAt of the start event from the createdAt
*/
export const getPhaseTimingsFromRenderEvents = (
renderEvents: RenderEvent[],
): Record<string, number> => {
const phaseTimings: Record<string, number> = {}
if (!renderEvents) return phaseTimings

// Create a map to store start events by phase and renderId
const startEvents = new Map<string, RenderEvent>()

for (const event of renderEvents) {
const [, , phase, eventType] = event.type.split(":")

// For start events, store them in the map keyed by phase+renderId
if (eventType === "start") {
startEvents.set(`${phase}:${event.renderId}`, event)
continue
}

// For end events, find matching start event and calculate duration
if (eventType === "end") {
const startEvent = startEvents.get(`${phase}:${event.renderId}`)
if (startEvent) {
const duration = event.createdAt - startEvent.createdAt
phaseTimings[phase] = (phaseTimings[phase] || 0) + duration
}
}
}

return phaseTimings
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@tscircuit/3d-viewer": "^0.0.86",
"@tscircuit/assembly-viewer": "^0.0.1",
"@tscircuit/core": "^0.0.254",
"@tscircuit/eval-webworker": "^0.0.52",
"@tscircuit/eval-webworker": "^0.0.53",
"@tscircuit/file-server": "^0.0.13",
"@tscircuit/pcb-viewer": "^1.10.22",
"@tscircuit/props": "^0.0.128",
Expand Down

0 comments on commit c2199e7

Please sign in to comment.