-
Notifications
You must be signed in to change notification settings - Fork 19
perf(ios): debounce thread persistence off the main thread #3456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1490,13 +1490,38 @@ final class AppModel { | |
| return dir.appendingPathComponent("cave-threads.json") | ||
| } | ||
|
|
||
| /// Pending debounced thread-persist flush. Not observable state. | ||
| @ObservationIgnored private var persistThreadsTask: Task<Void, Never>? | ||
|
|
||
| func persistThreads() { | ||
| // Debounce: many call sites (message send/receive, edits, archive, | ||
| // reorder) fire in quick bursts. Encoding every thread + message to | ||
| // JSON and writing to disk on each call — on the main thread — was a | ||
| // needless hitch. Coalesce bursts into one write shortly after the last | ||
| // change, and do the encode + write off the main thread. | ||
| persistThreadsTask?.cancel() | ||
| persistThreadsTask = Task { [weak self] in | ||
| try? await Task.sleep(for: .milliseconds(400)) | ||
| guard !Task.isCancelled else { return } | ||
| self?.flushThreads() | ||
| } | ||
| } | ||
|
|
||
| /// Snapshot on the main actor (cheap value-type map), then encode + write | ||
| /// off-main. Call directly when a synchronous flush is required (e.g. app | ||
| /// moving to the background). | ||
| func flushThreads() { | ||
| persistThreadsTask?.cancel() | ||
| persistThreadsTask = nil | ||
| let snapshots = threads.map(\.snapshot) | ||
| do { | ||
| let data = try JSONEncoder().encode(snapshots) | ||
| try data.write(to: threadsFileURL, options: .atomic) | ||
| } catch { | ||
| // Non-fatal: persistence is best-effort. | ||
| let url = threadsFileURL | ||
| Task.detached(priority: .utility) { | ||
| do { | ||
| let data = try JSONEncoder().encode(snapshots) | ||
| try data.write(to: url, options: .atomic) | ||
| } catch { | ||
| // Non-fatal: persistence is best-effort. | ||
| } | ||
| } | ||
|
Comment on lines
+1518
to
1525
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 38381fb: normal writes now enter one serial persistence queue, so writes run in snapshot order and a stale write cannot finish after a newer queued snapshot. The commit is published at romgenie@38381fb; it needs cherry-picking because I do not have permission to push this PR branch. |
||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 38381fb: the scene-phase path now synchronously drains the serial background persistence queue and writes the final snapshot before returning. Normal debounced saves remain asynchronous. The commit is published at romgenie@38381fb; it needs cherry-picking because I do not have permission to push this PR branch.