Skip to content

Commit f80238a

Browse files
committed
fix: decouple state change logic from main event loop
1 parent 1f597a5 commit f80238a

File tree

1 file changed

+25
-19
lines changed

1 file changed

+25
-19
lines changed

electron/renderer/components/game/game-stream.tsx

+25-19
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,20 @@ export const GameStream: React.FC<GameStreamProps> = (
3737
});
3838

3939
const [gameLogLines, setGameLogLines] = useState<Array<GameLogLine>>([]);
40+
const clearStreamTimeoutRef = useRef<NodeJS.Timeout>();
4041

41-
const appendGameLogLine = useCallback((newLogLine: GameLogLine) => {
42+
const appendGameLogLines = useCallback((newLogLines: Array<GameLogLine>) => {
4243
// Max number of most recent lines to keep.
4344
const scrollbackBuffer = 500;
4445
setGameLogLines((oldLogLines) => {
4546
// Append new log line to the list.
46-
let newLogLines = oldLogLines.concat(newLogLine);
47+
newLogLines = oldLogLines.concat(newLogLines);
4748
// Trim the back of the list to keep it within the scrollback buffer.
4849
newLogLines = newLogLines.slice(scrollbackBuffer * -1);
4950
return newLogLines;
5051
});
5152
}, []);
5253

53-
const clearStreamTimeoutRef = useRef<NodeJS.Timeout>();
54-
5554
// Ensure all timeouts are cleared when the component is unmounted.
5655
useEffect(() => {
5756
return () => {
@@ -60,23 +59,30 @@ export const GameStream: React.FC<GameStreamProps> = (
6059
}, []);
6160

6261
useSubscription(filteredStream$, (logLine) => {
63-
if (logLine.text === '__CLEAR_STREAM__') {
64-
// Clear the stream after a short delay to prevent flickering
65-
// caused by a flash of empty content then the new content.
66-
clearStreamTimeoutRef.current = setTimeout(() => {
67-
setGameLogLines([]);
68-
}, 1000);
69-
} else {
70-
// If we receieved a new log line, cancel any pending clear stream.
71-
// Set the game log lines to the new log line to prevent flickering.
72-
if (clearStreamTimeoutRef.current) {
73-
clearTimeout(clearStreamTimeoutRef.current);
74-
clearStreamTimeoutRef.current = undefined;
75-
setGameLogLines([logLine]);
62+
// Decouple state updates from the stream subscription to mitigate
63+
// "Cannot update a component while rendering a different component".
64+
// This gives some control of the event loop back to react
65+
// to smartly (re)render all components and state changes.
66+
// We use `setTimeout` because browser doesn't have `setImmediate`.
67+
setTimeout(() => {
68+
if (logLine.text === '__CLEAR_STREAM__') {
69+
// Clear the stream after a short delay to prevent flickering
70+
// caused by a flash of empty content then the new content.
71+
clearStreamTimeoutRef.current = setTimeout(() => {
72+
setGameLogLines([]);
73+
}, 1000);
7674
} else {
77-
appendGameLogLine(logLine);
75+
// If we receieved a new log line, cancel any pending clear stream.
76+
// Set the game log lines to the new log line to prevent flickering.
77+
if (clearStreamTimeoutRef.current) {
78+
clearTimeout(clearStreamTimeoutRef.current);
79+
clearStreamTimeoutRef.current = undefined;
80+
setGameLogLines([logLine]);
81+
} else {
82+
appendGameLogLines([logLine]);
83+
}
7884
}
79-
}
85+
});
8086
});
8187

8288
// Scroll to the bottom of the scrollable element when new content is added.

0 commit comments

Comments
 (0)