Skip to content

fix(brainbar): KG force-sim CPU pegging (task #22)#249

Merged
EtanHey merged 1 commit intomainfrom
fix/kg-forcesim-cpu-pegging
Apr 17, 2026
Merged

fix(brainbar): KG force-sim CPU pegging (task #22)#249
EtanHey merged 1 commit intomainfrom
fix/kg-forcesim-cpu-pegging

Conversation

@EtanHey
Copy link
Copy Markdown
Owner

@EtanHey EtanHey commented Apr 17, 2026

Summary

  • add an energy-threshold early-exit to the KG force simulation so the 30fps loop stops once the graph settles
  • move simulation loop control into a restart-safe controller with a double-start guard and explicit stop on disappear
  • add KG canvas simulation tests for stable-graph convergence and restart-after-idle behavior

Test plan

  • swift test --filter KGCanvas
  • swift test --filter KG
  • repo-wide pytest run observed; failures are pre-existing and unrelated to this Swift-only change

Notes

Pre-existing pytest failures on master unrelated to this Swift-only change: 4 failed + 21 errors in test_engine/test_think_recall (apsw.BusyError) + test_eval_baselines + test_vector_store. Will be tracked separately.


Note

Medium Risk
Touches UI-driven concurrency and the force-simulation loop; bugs could lead to stalled layouts or simulations that don’t stop/restart correctly, but the change is localized and has new tests.

Overview
Reduces Knowledge Graph canvas CPU usage by making the force-directed simulation self-terminating once the layout settles (based on a kinetic-energy threshold) instead of running a perpetual ~30fps loop.

Introduces a KGSimulationController to own the simulation task lifecycle (double-start guard, explicit stop() on view disappear), updates KGViewModel.tick() to return energy, and adds focused tests covering convergence-to-idle and restart-after-idle behavior.

Reviewed by Cursor Bugbot for commit c0f5a61. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix knowledge graph force simulation CPU pegging by auto-stopping on energy convergence

  • Introduces KGSimulationController, a @MainActor controller that drives a frame-based loop (33ms per frame), automatically stopping when reported kinetic energy drops below a configurable threshold.
  • KGViewModel.tick() now returns total kinetic energy (CGFloat) so the controller can detect convergence.
  • KGCanvasView replaces its manual Task/boolean timer with a KGSimulationController instance; onDisappear calls simulation.stop() to clean up.
  • Tests verify that a stable 3-node graph stops within ~60 frames and that the controller can restart after going idle.
  • Behavioral Change: the simulation loop now terminates on its own when the graph stabilizes rather than running indefinitely, eliminating the CPU peg.
📊 Macroscope summarized c0f5a61. 4 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

… timerActive reset

Root cause of 3 user 'BrainBar stuck' complaints — KGCanvasView.startSimulation pegs CPU at 96.8% because tick() runs a 30fps O(n²) loop with no early-exit and .onAppear doesn't reset timerActive after disappear.

Fix:

- Energy-threshold early-exit in the force sim — stop timer when KE < 0.01.

- Reset timerActive semantics through a restart-safe simulation controller.

- Double-start guard for repeated appearances.

Tests: stable-graph converges <2s; re-appear triggers restart.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Warning

Rate limit exceeded

@EtanHey has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 34 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 34 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 311e4419-00b3-4785-863a-a5cc04f3bbe2

📥 Commits

Reviewing files that changed from the base of the PR and between e8fa626 and c0f5a61.

📒 Files selected for processing (4)
  • brain-bar/Sources/BrainBar/KnowledgeGraph/KGCanvasView.swift
  • brain-bar/Sources/BrainBar/KnowledgeGraph/KGSimulationController.swift
  • brain-bar/Sources/BrainBar/KnowledgeGraph/KGViewModel.swift
  • brain-bar/Tests/BrainBarTests/KnowledgeGraphTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/kg-forcesim-cpu-pegging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented Apr 17, 2026

@codex review

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented Apr 17, 2026

@cursor @BugBot review

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 17, 2026

You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor

Comment on lines +36 to +38
defer { self.simulationTask = nil }

while self.timerActive && !Task.isCancelled {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium KnowledgeGraph/KGSimulationController.swift:36

defer { self.simulationTask = nil } at line 36 nils simulationTask unconditionally when the task completes. If stop() is called (cancelling task A and niling the property) followed immediately by start() (creating task B), task A's defer block still executes and sets simulationTask = nil, discarding the reference to task B. Subsequent stop() calls then fail to cancel task B. Consider removing the defer block and letting stop() exclusively manage the property lifecycle, or capture task identity in the defer to avoid overwriting a newer task.

-        simulationTask = Task { @MainActor [weak self] in
-            guard let self else { return }
-            defer { self.simulationTask = nil }
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file brain-bar/Sources/BrainBar/KnowledgeGraph/KGSimulationController.swift around lines 36-38:

`defer { self.simulationTask = nil }` at line 36 nils `simulationTask` unconditionally when the task completes. If `stop()` is called (cancelling task A and niling the property) followed immediately by `start()` (creating task B), task A's defer block still executes and sets `simulationTask = nil`, discarding the reference to task B. Subsequent `stop()` calls then fail to cancel task B. Consider removing the `defer` block and letting `stop()` exclusively manage the property lifecycle, or capture task identity in the defer to avoid overwriting a newer task.

Evidence trail:
brain-bar/Sources/BrainBar/KnowledgeGraph/KGSimulationController.swift lines 31-47 (start method with defer block at line 36), lines 49-53 (stop method). The defer block `defer { self.simulationTask = nil }` unconditionally nils the property without checking task identity, creating the described race condition.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0f5a61713

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +42 to +43
if tick() < self.energyThreshold {
self.timerActive = false
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restart simulation when entering idle mode

This branch stops the force loop permanently as soon as one frame reports energy below threshold, but there is no corresponding restart trigger while the canvas stays mounted. KGCanvasView only starts the loop in .onAppear, so after auto-stop any later in-place layout change (notably window resize updating canvasCenter) will not run tick() again and the graph can remain visibly off-center. Please restart the controller when layout-driving state changes, or avoid hard-idling without a resume path.

Useful? React with 👍 / 👎.

@EtanHey EtanHey merged commit 05b2eaf into main Apr 17, 2026
7 checks passed
@EtanHey EtanHey deleted the fix/kg-forcesim-cpu-pegging branch April 17, 2026 18:01
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c0f5a61. Configure here.

timerActive = true
simulationTask = Task { @MainActor [weak self] in
guard let self else { return }
defer { self.simulationTask = nil }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defer in old task clobbers new simulationTask reference

Medium Severity

When stop() is called followed quickly by start(), the defer { self.simulationTask = nil } in the old task's closure runs after start() has already assigned the new task to simulationTask, clobbering that reference. After this, stop() can no longer cancel the new task via simulationTask?.cancel() because the reference is nil. The simulation still eventually halts via the timerActive flag, but with up to a full 33ms frame of uncancellable lag — undermining the explicit lifecycle control this PR aims to provide.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c0f5a61. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant