Skip to content

Commit 365281c

Browse files
committed
Much better performance rendering runtime logs with ref and queue of updates
1 parent 09754c5 commit 365281c

File tree

4 files changed

+53
-22
lines changed

4 files changed

+53
-22
lines changed

src/components/Go.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ interface GoCommandProps extends Props {
3333
}
3434

3535
const LogListener = ({projectId, buildId}: {projectId: string; buildId: string}) => {
36-
const {tail, messages} = useGoRuntimeLogListener({projectId, buildId})
36+
const {messages} = useGoRuntimeLogListener({projectId, buildId})
3737

3838
return (
3939
<>
@@ -87,7 +87,7 @@ const GoCommand = ({command, gameId, onComplete, onError}: GoCommandProps): JSX.
8787

8888
if (qrCodeData && buildId) {
8989
return (
90-
<Box flexDirection='column'>
90+
<Box flexDirection="column">
9191
<Title>Go Build QR Code</Title>
9292
<QRCodeTerminal data={qrCodeData} />
9393
<Text>{`Go build ID: ${getShortUUID(buildId)}`}</Text>
@@ -98,7 +98,7 @@ const GoCommand = ({command, gameId, onComplete, onError}: GoCommandProps): JSX.
9898

9999
if (startedJobs && startedJobs?.length > 0) {
100100
return (
101-
<Box flexDirection='column'>
101+
<Box flexDirection="column">
102102
<Text>Generating Go build, please wait...</Text>
103103
<JobProgress job={startedJobs[0]} />
104104
</Box>

src/types/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,5 @@ export interface RuntimeLogEntry {
286286
message: string
287287
details?: any
288288
sentAt: DateTime
289+
sequence: number // will be min of 1
289290
}
Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useState} from 'react'
1+
import {useEffect, useRef, useState} from 'react'
22
import {DateTime} from 'luxon'
33

44
import {WebSocketListener, useWebSocket} from './useWebSocket.js'
@@ -8,37 +8,62 @@ interface Props {
88
projectId: string
99
buildId: string
1010
tailLength?: number
11+
maxMessages?: number
12+
flushIntervalMs?: number
1113
}
1214

1315
interface Response {
1416
messages: RuntimeLogEntry[]
1517
tail: RuntimeLogEntry[]
1618
}
1719

18-
// Listens for Go runtime logs for a build via WebSocket and gives a tail
19-
export function useGoRuntimeLogListener({projectId, buildId, tailLength = 10}: Props): Response {
20+
// Saves incoming runtime log entries to a ref queue, and flushes them to state
21+
// at a regular interval. This reduces the number of re-renders.
22+
export function useGoRuntimeLogListener({
23+
projectId,
24+
buildId,
25+
tailLength = 10,
26+
maxMessages = 500,
27+
flushIntervalMs = 100, // 10fps
28+
}: Props): Response {
2029
const [messages, setMessages] = useState<RuntimeLogEntry[]>([])
2130
const [tail, setTail] = useState<RuntimeLogEntry[]>([])
2231

32+
const queueRef = useRef<RuntimeLogEntry[]>([])
33+
2334
const listener: WebSocketListener = {
2435
async eventHandler(_: string, rawLog: any) {
25-
const log: RuntimeLogEntry = {
36+
queueRef.current.push({
2637
...rawLog,
2738
sentAt: DateTime.fromISO(rawLog.sentAt),
28-
}
29-
setMessages((prev) => [...prev, log])
30-
setTail((prev) => {
31-
const next = [...prev, log]
32-
if (next.length > tailLength) next.shift()
33-
return next
3439
})
35-
//console.log(log.message)
3640
},
3741

3842
getPattern: () => [`project.${projectId}:build.${buildId}:runtime-log`],
3943
}
4044

4145
useWebSocket([listener])
4246

47+
useEffect(() => {
48+
const id = setInterval(() => {
49+
const queued = queueRef.current
50+
if (queued.length === 0) return
51+
52+
queueRef.current = []
53+
54+
setMessages((prev) => {
55+
const next = [...prev, ...queued]
56+
return next.length > maxMessages ? next.slice(-maxMessages) : next
57+
})
58+
59+
setTail((prev) => {
60+
const next = [...prev, ...queued]
61+
return next.length > tailLength ? next.slice(-tailLength) : next
62+
})
63+
}, flushIntervalMs)
64+
65+
return () => clearInterval(id)
66+
}, [tailLength, maxMessages, flushIntervalMs])
67+
4368
return {messages, tail}
4469
}

src/utils/hooks/useWebSocket.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useEffect} from 'react'
1+
import {useEffect, useRef} from 'react'
22
import {io} from 'socket.io-client'
33

44
import {getAuthToken} from '@cli/api/index.js'
@@ -13,18 +13,21 @@ export interface WebSocketListener {
1313

1414
export function useWebSocket(listeners: WebSocketListener[] = []) {
1515
const log = false ? console.debug : () => {}
16+
const socketRef = useRef<ReturnType<typeof io> | null>(null)
1617

1718
useEffect(() => {
1819
if (listeners.length === 0) {
1920
log('Not subscribing to WebSocket - no listeners')
2021
return
2122
}
2223

23-
const token = getAuthToken()
24-
const socket = io(WS_URL, {
25-
auth: {token},
26-
forceNew: true,
27-
})
24+
if (!socketRef.current) {
25+
const token = getAuthToken()
26+
socketRef.current = io(WS_URL, {
27+
auth: {token},
28+
})
29+
}
30+
const socket = socketRef.current
2831

2932
socket.on('connect', () => log('Connected to WebSocket'))
3033

@@ -43,10 +46,12 @@ export function useWebSocket(listeners: WebSocketListener[] = []) {
4346

4447
bindSocket(pattern)
4548
}
49+
}, [])
4650

51+
useEffect(() => {
4752
return () => {
48-
log('Disconnecting from WebSocket')
49-
socket.disconnect()
53+
socketRef.current?.disconnect()
54+
socketRef.current = null
5055
}
5156
}, [])
5257
}

0 commit comments

Comments
 (0)