Skip to content

Commit 104becc

Browse files
committed
fix: prevent CPU spikes by using refs for event callbacks and simplifying Tauri listener registration
1 parent 102942b commit 104becc

File tree

1 file changed

+55
-48
lines changed

1 file changed

+55
-48
lines changed

src/hooks/codex/useCodexEvents.ts

Lines changed: 55 additions & 48 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 { listen } from '@tauri-apps/api/event';
33
import {
44
useCodexStore,
@@ -50,6 +50,18 @@ export function useCodexEvents(enabled = true) {
5050
const taskCompleteBeepMode = useSettingsStore((state) => state.enableTaskCompleteBeep);
5151
const isChatInterfaceActive = useLayoutStore((state) => state.view === 'agent');
5252

53+
// Use refs for values that change but are only read inside callbacks.
54+
// This avoids re-registering all Tauri listeners whenever the user switches
55+
// views or settings change — listener accumulation was the cause of 100% CPU.
56+
const isChatInterfaceActiveRef = useRef(isChatInterfaceActive);
57+
isChatInterfaceActiveRef.current = isChatInterfaceActive;
58+
const taskCompleteBeepModeRef = useRef(taskCompleteBeepMode);
59+
taskCompleteBeepModeRef.current = taskCompleteBeepMode;
60+
const preventSleepDuringTasksRef = useRef(preventSleepDuringTasks);
61+
preventSleepDuringTasksRef.current = preventSleepDuringTasks;
62+
const showReasoningRef = useRef(showReasoning);
63+
showReasoningRef.current = showReasoning;
64+
5365
useEffect(() => {
5466
if (!enabled) {
5567
return;
@@ -105,7 +117,7 @@ export function useCodexEvents(enabled = true) {
105117
payload.method === 'item/reasoning/summaryTextDelta' ||
106118
payload.method === 'item/reasoning/summaryPartAdded' ||
107119
(payload.method === 'item/completed' && payload.params.item.type === 'reasoning');
108-
if (!showReasoning && isReasoningEvent) {
120+
if (!showReasoningRef.current && isReasoningEvent) {
109121
return;
110122
}
111123

@@ -122,7 +134,7 @@ export function useCodexEvents(enabled = true) {
122134
}
123135
}
124136

125-
if (preventSleepDuringTasks && payload.method === 'turn/started') {
137+
if (preventSleepDuringTasksRef.current && payload.method === 'turn/started') {
126138
void preventSleep(threadId).catch((error) => {
127139
console.warn('[useCodexEvents] preventSleep failed:', error);
128140
});
@@ -136,7 +148,7 @@ export function useCodexEvents(enabled = true) {
136148
const turnStatus = payload.params.turn.status;
137149
if (
138150
turnStatus === 'completed' &&
139-
shouldPlayCompletionBeep(taskCompleteBeepMode, isChatInterfaceActive)
151+
shouldPlayCompletionBeep(taskCompleteBeepModeRef.current, isChatInterfaceActiveRef.current)
140152
) {
141153
playBeep();
142154
}
@@ -162,43 +174,48 @@ export function useCodexEvents(enabled = true) {
162174
};
163175

164176
if (isDesktopTauri()) {
165-
const unlistenPromises: Promise<() => void>[] = [];
166177
console.log('[useCodexEvents] Setting up Tauri event listeners...');
167178

168-
unlistenPromises.push(
169-
listen<ApprovalRequest>('codex/approval-request', (event) => {
170-
addApproval(event.payload);
171-
})
172-
);
173-
174-
unlistenPromises.push(
175-
listen<RequestUserInputRequest>('codex/request-user-input', (event) => {
176-
addRequest(event.payload);
177-
})
178-
);
179-
180-
unlistenPromises.push(
181-
listen<ServerNotification>('codex:notification', (event) => {
182-
handleServerNotification(event.payload);
183-
})
184-
);
185-
186-
unlistenPromises.push(
187-
listen<CodexStderrEvent>('codex:stderr', (event) => {
188-
console.error('[useCodexEvents] codex stderr:', event.payload.message);
189-
})
190-
);
191-
192-
unlistenPromises.push(
193-
listen<CodexParseErrorEvent>('codex:parseError', (event) => {
194-
console.error('[useCodexEvents] codex parseError:', event.payload.error, event.payload.raw);
195-
})
196-
);
179+
// Collect resolved unlisten functions synchronously as promises settle.
180+
// Using a cancelled flag ensures we don't register listeners after cleanup.
181+
let cancelled = false;
182+
const unlisteners: (() => void)[] = [];
183+
184+
const registerListener = async <T>(
185+
event: string,
186+
handler: (event: { payload: T }) => void
187+
) => {
188+
const unlisten = await listen<T>(event, handler);
189+
if (cancelled) {
190+
unlisten();
191+
} else {
192+
unlisteners.push(unlisten);
193+
}
194+
};
195+
196+
void registerListener<ApprovalRequest>('codex/approval-request', (event) => {
197+
addApproval(event.payload);
198+
});
199+
200+
void registerListener<RequestUserInputRequest>('codex/request-user-input', (event) => {
201+
addRequest(event.payload);
202+
});
203+
204+
void registerListener<ServerNotification>('codex:notification', (event) => {
205+
handleServerNotification(event.payload);
206+
});
207+
208+
void registerListener<CodexStderrEvent>('codex:stderr', (event) => {
209+
console.error('[useCodexEvents] codex stderr:', event.payload.message);
210+
});
211+
212+
void registerListener<CodexParseErrorEvent>('codex:parseError', (event) => {
213+
console.error('[useCodexEvents] codex parseError:', event.payload.error, event.payload.raw);
214+
});
197215

198216
return () => {
199-
Promise.all(unlistenPromises).then((unlisteners) => {
200-
unlisteners.forEach((unlisten) => unlisten());
201-
});
217+
cancelled = true;
218+
unlisteners.forEach((unlisten) => unlisten());
202219
};
203220
}
204221

@@ -245,15 +262,5 @@ export function useCodexEvents(enabled = true) {
245262
return () => {
246263
es.close();
247264
};
248-
}, [
249-
addEvent,
250-
addApproval,
251-
addRequest,
252-
setHasAccount,
253-
enabled,
254-
taskCompleteBeepMode,
255-
preventSleepDuringTasks,
256-
showReasoning,
257-
isChatInterfaceActive,
258-
]);
265+
}, [addEvent, addApproval, addRequest, setHasAccount, enabled]);
259266
}

0 commit comments

Comments
 (0)