Skip to content

Refactor: eliminate redundant client map lookups#202

Merged
paperbenni merged 1 commit intomainfrom
fix/double-client-lookups-12851667626280172314
Mar 9, 2026
Merged

Refactor: eliminate redundant client map lookups#202
paperbenni merged 1 commit intomainfrom
fix/double-client-lookups-12851667626280172314

Conversation

@paperbenni
Copy link
Copy Markdown
Member

@paperbenni paperbenni commented Mar 9, 2026

Refactored rust/src/ to eliminate inefficient "double lookup" anti-patterns on the global clients map.
Specifically removed cases where client state was queried via .get().unwrap_or(false) and then subsequently retrieved again via .get_mut() for the same window.

Instead, we now use a single .get_mut() with early returns (let Some(client) = ... else { return; }) and combine queries and mutations efficiently. For read-only queries with defaults, switched to cleaner .is_some_and(|c| ...) syntax.

Affected files:

  • rust/src/backend/x11/lifecycle.rs: configure_client_border
  • rust/src/scratchpad.rs: scratchpad_toggle, scratchpad_status, scratchpad_hide_name
  • rust/src/toggles.rs: set_border_width
  • rust/src/systray.rs: update_systray_icon_state

PR created automatically by Jules for task 12851667626280172314 started by @paperbenni

Summary by Sourcery

Refactor client state handling to avoid redundant map lookups and streamline geometry/border updates across window management components.

Enhancements:

  • Simplify border width updates by using a single mutable client lookup when adjusting geometry and borders.
  • Unify snap state changes to operate on a single mutable client reference while preserving previous snap status and geometry where needed.
  • Streamline scratchpad visibility toggling and hiding by avoiding duplicate client fetches and using more expressive option checks.
  • Refine X11 border configuration logic to work directly on a mutable client reference instead of separate read and write passes.
  • Tighten systray icon state updates by consolidating client access into a single mutable lookup.

Summary by CodeRabbit

  • Refactor
    • Optimized internal client access patterns across multiple modules to reduce redundant lookups and simplify control flow, improving code efficiency and maintainability.

Refactored `rust/src/` to eliminate inefficient "double lookup" anti-patterns on the global `clients` map.
Specifically removed cases where client state was queried via `.get().unwrap_or(false)` and then subsequently retrieved again via `.get_mut()` for the same window.

Instead, we now use a single `.get_mut()` with early returns (`let Some(client) = ... else { return; }`) and combine queries and mutations efficiently. For read-only queries with defaults, switched to cleaner `.is_some_and(|c| ...)` syntax.

Affected files:
- `rust/src/backend/x11/lifecycle.rs`: `configure_client_border`
- `rust/src/scratchpad.rs`: `scratchpad_toggle`, `scratchpad_status`, `scratchpad_hide_name`
- `rust/src/toggles.rs`: `set_border_width`
- `rust/src/systray.rs`: `update_systray_icon_state`

Co-authored-by: paperbenni <15818888+paperbenni@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 9, 2026

Reviewer's Guide

Refactors several window-management functions to avoid redundant lookups into the global clients map by reusing a single mutable client reference and using more idiomatic option handling, while preserving existing behavior for border, scratchpad, snapping, and systray logic.

Sequence diagram for updated change_snap client access

sequenceDiagram
    actor User
    participant WmCtx
    participant Globals
    participant ClientsMap as Clients
    participant Client

    User->>WmCtx: change_snap(win, direction)
    WmCtx->>WmCtx: g_mut()
    WmCtx->>Globals: get mutable Globals
    Globals->>ClientsMap: get_mut(win)
    alt client exists
        ClientsMap-->>Globals: Some(Client)
        Globals-->>WmCtx: mutable Client reference
        WmCtx->>Client: read snap_status
        WmCtx->>WmCtx: compute row, col
        WmCtx->>WmCtx: look up new_snap in SNAP_MATRIX
        alt snap_status == SnapPosition.None and client.is_floating
            WmCtx->>Client: set float_geo = geo
        end
        WmCtx->>Client: set snap_status = new_snap
        WmCtx->>Client: read monitor_id and previous snap_status
        WmCtx-->>User: return with monitor_id and previous snap_status
    else no client
        ClientsMap-->>Globals: None
        Globals-->>WmCtx: None
        WmCtx-->>User: early return
    end
Loading

Class diagram for client map access refactor

classDiagram
    class Globals {
        +HashMap<WindowId, Client> clients
    }

    class CoreCtx {
        +Globals g
        +g_mut() Globals
    }

    class WmCtx {
        +Globals g
        +g() Globals
        +g_mut() Globals
        +client(win WindowId) Client
    }

    class Client {
        +i32 border_width
        +MonitorId monitor_id
        +Rect geo
        +Rect float_geo
        +bool is_floating
        +bool issticky
        +u32 tags
        +SnapPosition snap_status
    }

    class Rect {
        +i32 x
        +i32 y
        +i32 w
        +i32 h
    }

    class SnapPosition {
        <<enum>>
        None
        Other
    }

    class SnapDir {
        <<enum>>
        Left
        Right
        Up
        Down
    }

    class TogglesModule {
        +set_border_width(core CoreCtx, win WindowId, width i32)
    }

    class FloatingSnapModule {
        +change_snap(ctx WmCtx, win WindowId, direction SnapDir)
    }

    class ScratchpadModule {
        +scratchpad_toggle(ctx WmCtx, name Option~str~)
        +scratchpad_status(g Globals, name str) String
        +scratchpad_hide_name(ctx WmCtx, name str)
    }

    class X11LifecycleModule {
        +configure_client_border(g Globals, x11_runtime X11Runtime, w WindowId, mon_monitor_rect Rect, borderpx i32, is_monocle bool)
    }

    class SystrayModule {
        +update_systray_icon_state(core CoreCtx, icon_win WindowId, systray Option~Systray~)
    }

    Globals "1" o-- "*" Client : clients
    CoreCtx "1" o-- "1" Globals : g
    WmCtx "1" o-- "1" Globals : g

    TogglesModule ..> CoreCtx
    TogglesModule ..> Client

    FloatingSnapModule ..> WmCtx
    FloatingSnapModule ..> Client
    FloatingSnapModule ..> SnapPosition
    FloatingSnapModule ..> SnapDir

    ScratchpadModule ..> WmCtx
    ScratchpadModule ..> Globals
    ScratchpadModule ..> Client

    X11LifecycleModule ..> Globals
    X11LifecycleModule ..> Client
    X11LifecycleModule ..> Rect

    SystrayModule ..> CoreCtx
    SystrayModule ..> Client
Loading

File-Level Changes

Change Details Files
Refactor set_border_width to use a single mutable client lookup and update geometry from the same reference.
  • Remove separate immutable and mutable lookups of the same client from the clients map.
  • Compute old border width, delta, and updated geometry within one get_mut block.
  • Return early if the client does not exist instead of falling through.
rust/src/toggles.rs
Refactor change_snap to operate on a single mutable client reference and carry both monitor and snap status out of the block.
  • Replace initial read-only client() call with direct mutable access to clients via g_mut().clients.get_mut.
  • Compute new snap position and update snap_status in the same block that saves float geometry when first snapping.
  • Return a tuple of (monitor_id, previous_snap_status) from the get_mut block and early-return when the client is missing.
rust/src/floating/snap.rs
Simplify scratchpad helpers to avoid double lookups and use more idiomatic option handling.
  • In scratchpad_hide_name, reuse a single mutable client reference to read monitor_id and update issticky and tags.
  • In scratchpad_toggle, early-return when the scratchpad client is missing instead of using map/unwrap_or on the clients map.
  • In scratchpad_status, replace map(...).unwrap_or(false) with is_some_and to compute visibility based on issticky.
rust/src/scratchpad.rs
Update configure_client_border to use one mutable client reference for both geometry-based border calculation and border mutation.
  • Replace initial Option mapping and unwrap_or default with an early-return on missing client and direct field access on client.geo and client.is_floating.
  • Compute border_width using the live client geometry and monocle state before mutating client.border_width.
  • Avoid a second get_mut by reusing the existing mutable client binding for the write.
rust/src/backend/x11/lifecycle.rs
Adjust systray icon state update to use a mutable client lookup rather than an immutable one when inspecting tags.
  • Change clients.get to clients.get_mut so that future state changes on the client could be made without a second lookup.
  • Maintain early-return behavior when the icon client is absent and preserve existing tags/has_systray logic.
rust/src/systray.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

All five modules consolidate client state mutations by replacing separate immutable-then-mutable access patterns with direct get_mut retrieval and guard-based early returns, unifying how client references are retrieved and modified across lifecycle, snap, scratchpad, systray, and toggle functionality.

Changes

Cohort / File(s) Summary
X11 Lifecycle Client Retrieval
rust/src/backend/x11/lifecycle.rs
Switched to guard-based get_mut with early return; consolidated border width logic to operate directly on client instance instead of separate cached tuple lookups.
Floating Window Snap Management
rust/src/floating/snap.rs
Unified mutable client retrieval; consolidated status variable usage throughout snap computation and application, eliminating redundant re-access patterns.
Scratchpad Operations
rust/src/scratchpad.rs
Standardized client access using get_mut and is_some_and patterns across hide_name, toggle, and status functions for consistency and direct in-place mutations.
Systray State Updates
rust/src/systray.rs
Changed from immutable get to mutable get_mut within update_systray_icon_state to enable reading and mutating the same client object in a single scope.
Window Toggle Styling
rust/src/toggles.rs
Refactored set_border_width to inline client borrow, compute updates before geometry calculations, and eliminate redundant separate fetch and intermediate state operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A rabbit hops with glee,
get_mut sets the mutations free,
No double-checks, no needless care,
Guards ensure clients are there!
Clean code makes this bunny's heart sing,
Refactoring—the sweetest spring! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main refactoring objective: eliminating redundant client map lookups across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/double-client-lookups-12851667626280172314

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

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • In floating/snap.rs::change_snap, the new (monitor_id, snap_status) binding now keeps snap_status as the old value (status) instead of the newly computed new_snap; double-check any later use of snap_status in this function to ensure it still represents the intended state (you may want a separate old_snap_status variable instead).
  • In places like systray.rs::update_systray_icon_state, you now use clients.get_mut even though the client is only read; consider keeping get for read-only access to avoid unnecessarily broad mutable borrows and keep future borrow interactions simpler.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `floating/snap.rs::change_snap`, the new `(monitor_id, snap_status)` binding now keeps `snap_status` as the *old* value (`status`) instead of the newly computed `new_snap`; double-check any later use of `snap_status` in this function to ensure it still represents the intended state (you may want a separate `old_snap_status` variable instead).
- In places like `systray.rs::update_systray_icon_state`, you now use `clients.get_mut` even though the client is only read; consider keeping `get` for read-only access to avoid unnecessarily broad mutable borrows and keep future borrow interactions simpler.

## Individual Comments

### Comment 1
<location path="rust/src/scratchpad.rs" line_range="172-175" />
<code_context>
-        .get(&found)
-        .map(|c| c.issticky)
-        .unwrap_or(false);
+    let Some(client) = ctx.g().clients.get(&found) else {
+        return;
+    };
+    let is_sticky = client.issticky;

     if is_sticky {
</code_context>
<issue_to_address>
**question (bug_risk):** Early-return on missing client changes behavior compared to treating it as non-sticky.

Previously, a missing client was treated as `is_sticky == false`, so the code would continue into the non-sticky path (show/create scratchpad). With the early return, a missing client now results in no action. If that behavior change is intended, please call it out in the PR description; if not, consider preserving the old semantics (e.g., keep `unwrap_or(false)` and only early-return when a client reference is strictly required).
</issue_to_address>

### Comment 2
<location path="rust/src/backend/x11/lifecycle.rs" line_range="204-213" />
<code_context>
-        .unwrap_or((false, 0, 0));

-    let border_width = if !isfloating
+    let Some(client) = g.clients.get_mut(&w) else {
+        return;
+    };
+
+    let border_width = if !client.is_floating
         && is_monocle
-        && client_width > mon_monitor_rect.w - 30
-        && client_height > mon_monitor_rect.h - 30 - bar_height
+        && client.geo.w > mon_monitor_rect.w - 30
+        && client.geo.h > mon_monitor_rect.h - 30 - bar_height
     {
         0
     } else {
         borderpx
     };

-    if let Some(client) = g.clients.get_mut(&w) {
-        client.border_width = border_width;
-    }
+    client.border_width = border_width;
</code_context>
<issue_to_address>
**question:** Early return when the client is missing skips any X11-side border configuration for that window.

Previously, a missing client fell back to `(false, 0, 0)` and still allowed a configure request to reach X11. Now, missing clients cause an early return, so X11 is never updated for that window. If there are cases where X11 still has the window but it has been removed from `g.clients`, this may leave borders out of sync. Please confirm we never call `configure_client_border` in that situation, or add explicit handling if we do.
</issue_to_address>

### Comment 3
<location path="rust/src/systray.rs" line_range="143-144" />
<code_context>

     let (current_tags, _has_systray) = {
-        if let Some(client) = core.g.clients.get(&icon_win) {
+        if let Some(client) = core.g.clients.get_mut(&icon_win) {
             (client.tags, systray.is_some())
         } else {
             return;
</code_context>
<issue_to_address>
**suggestion:** Use an immutable borrow instead of `get_mut` when only reading `tags`.

Since `client` is only read here, `get` is enough and avoids an unnecessary mutable borrow on `clients`, which keeps borrowing more flexible for any future code in this scope that may need mutable access.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread rust/src/scratchpad.rs
Comment on lines +172 to +175
let Some(client) = ctx.g().clients.get(&found) else {
return;
};
let is_sticky = client.issticky;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

question (bug_risk): Early-return on missing client changes behavior compared to treating it as non-sticky.

Previously, a missing client was treated as is_sticky == false, so the code would continue into the non-sticky path (show/create scratchpad). With the early return, a missing client now results in no action. If that behavior change is intended, please call it out in the PR description; if not, consider preserving the old semantics (e.g., keep unwrap_or(false) and only early-return when a client reference is strictly required).

Comment on lines +204 to 213
let Some(client) = g.clients.get_mut(&w) else {
return;
};

let border_width = if !client.is_floating
&& is_monocle
&& client_width > mon_monitor_rect.w - 30
&& client_height > mon_monitor_rect.h - 30 - bar_height
&& client.geo.w > mon_monitor_rect.w - 30
&& client.geo.h > mon_monitor_rect.h - 30 - bar_height
{
0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

question: Early return when the client is missing skips any X11-side border configuration for that window.

Previously, a missing client fell back to (false, 0, 0) and still allowed a configure request to reach X11. Now, missing clients cause an early return, so X11 is never updated for that window. If there are cases where X11 still has the window but it has been removed from g.clients, this may leave borders out of sync. Please confirm we never call configure_client_border in that situation, or add explicit handling if we do.

Comment thread rust/src/systray.rs
Comment on lines +143 to 144
if let Some(client) = core.g.clients.get_mut(&icon_win) {
(client.tags, systray.is_some())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Use an immutable borrow instead of get_mut when only reading tags.

Since client is only read here, get is enough and avoids an unnecessary mutable borrow on clients, which keeps borrowing more flexible for any future code in this scope that may need mutable access.

@paperbenni paperbenni merged commit bff5521 into main Mar 9, 2026
2 of 3 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 8, 2026
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