Skip to content

refactor: split central state, engine-switch fixes, SpatialState (0.90.201–0.90.209) - #14

Merged
Fi3w0 merged 35 commits into
masterfrom
refactor/state-init
Oct 28, 2026
Merged

Fi3w0 merged 35 commits into
masterfrom
refactor/state-init

Conversation

@Fi3w0

@Fi3w0 Fi3w0 commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Covers architecture-plan steps 5–7 plus two bug fixes. Source goes from 0.90.200 to 0.90.209. Every commit is signed, and each refactor commit is pure relocation or a mechanical rewrite, with the evidence noted per commit.

Behavior changes

  • Classic fullscreen deadlock fixed (f929041, .201). Leaving fullscreen on a tiled window called the layout from inside with_pending_state, so the same surface mutex was locked twice and the compositor hung. The tiled size is now worked out before the closure. The regression test fails before the fix and passes after.
  • Scratchpads survive Ocean (92b549f, .208). Switching to Ocean turned scratchpad workspace 0 into a reef exactly on top of workspace 1, and a named scratchpad into a reef at x = i32::MAX. The return then renumbered workspaces nondeterministically and turned scratchpads into ordinary visible workspaces. Scratchpad trees and their unpinned floaters now stay parked in workspace storage while Ocean runs, and come back as they were.
  • Floating stacking order is kept on every engine switch (92b549f). Migration used to move floaters in HashMap order.

Refactor, no behavior change

  • Step 5:
    • state.rs (about 16k lines) becomes state/mod.rs plus 13 domain modules: init, ipc, config_reload, workspace, render, focus, scroll, session, effects, physics, ocean, groups_depth and spatial.
    • All 351 Smallvil methods and every field are byte-identical to f929041.
    • Required private helpers gain only pub(super), and the commits list each one.
    • Explicit imports replace use super::* in every child module (d0ea005).
  • Step 6: a spatial invariants table, plus real-protocol transition tests on two outputs (407645c): Classic→Ocean→Scroll→Classic→Scroll→Ocean→Classic with tiled, floating, pinned, grouped and fullscreen windows.
  • Step 7:
    • SpatialOwnership/WorkspaceProjection axes (0f96644).
    • SpatialState { engine } as the single live engine value; config.spatial_engine is now only the file's request (c782b28).
    • A named hit-testing axis (4b2bd74).

Verification

  • Gates: fmt, 712 locked all-feature/all-target tests, strict Clippy with all and default features, and a release build all pass.
  • Nested runtime matrix with IPC subscribers: 61 observations, 0 differences against the post-fix baseline at every step.
  • Native, two monitors:
    • .201 fullscreen and .209 both pass a startup probe and 12 WM + 4 client fullscreen round trips, with state restored.
    • On .209, a live Ocean round trip kept the scratchpad parked and restored the desktop exactly.

Known, not changed here: an Ocean round trip resets each Scroll strip's view offset, and Ocean→Classic pulls off-screen floaters on-screen. Both are tracked separately.

Fi3w0 added 30 commits October 28, 2026 02:57
Resolve the restored tiled size before with_pending_state holds Smithay's
surface lock. BSP layout reads client minimum hints via with_states and
otherwise tries to acquire that same mutex on the event-loop thread.

Confirmed with all-thread GDB traces from the hung unstripped nested build.
A two-client real-protocol regression fails on the previous code with a
bounded watchdog; WM toggles and client fullscreen requests now restore the
tiled size and remain responsive. Bump to 0.90.201.

Validation: fmt, 709 all-feature/all-target tests, strict Clippy and release
build; minimized GTK reproduction and two 61-observation nested runs.
The repeated runs compare identically; the sole difference from the original
baseline is successful Classic unfullscreen. Native activation is not run.
Move Smallvil::new and init_wayland_listener verbatim into the approved
initialization domain. Rename state.rs to state/mod.rs; retain the struct,
shared types, remaining methods, free helpers and tests there.

Move initialization-only imports into the child module. No method or field
visibility changes, forwarding methods, behavior changes or formatting of
method bodies. Method source matches the previous file byte for byte.

Validation: fmt, 709 locked all-feature/all-target tests and strict Clippy.
Move the approved emit_state_change_events, ipc_seen_state, emit_ipc_event,
flush_ipc_subscribers, arm_ipc_flush_timer and remove_ipc_subscriber methods
verbatim into a child impl Smallvil. Keep IPC fields, shared types and tests
in state/mod.rs; has_lua_handler stays with configuration coordination.

No visibility or import changes. Existing pub(crate) entry points retain
their visibility; ipc_seen_state and remove_ipc_subscriber remain private.
No behavior changes, forwarding methods or method-body formatting.

Validation: source reconstruction is byte-identical, fmt, 709 locked
all-feature/all-target tests and strict Clippy pass.
Move the nine approved methods verbatim to state/config_reload.rs and retain
fields, shared types and tests in state/mod.rs. Remove the final method's
separator when joining the remaining impl; no method-body formatting.

Required visibility change: has_lua_handler becomes pub(super) because the
IPC child module calls it. Other method and all field visibility stays as-is.
No new accessors, ownership, behavior changes or dependencies.

Validation: source identity checks, fmt, 709 locked all-feature/all-target
tests and strict Clippy pass. Full batch runtime/release checks follow.
Move the 23 approved methods to state/workspace.rs without changing bodies,
fields, shared types, tests or ownership. No forwarding methods or dependencies.

Required private-to-pub(super) changes: resolve_window_rules_on_workspace,
workspace_of_surface and apply_workspace_switch have callers in the remaining
state impl or other approved child modules. All other visibility stays as-is.

Validation: source identity/reconstruction, fmt, 709 locked all-feature/
all-target tests and strict Clippy pass. Batch runtime/release checks follow.
Move the 21 approved methods verbatim to state/render.rs; keep fields, shared
types, free helpers and tests in state/mod.rs. Preserve method attributes and
all existing call paths, without forwarding methods or dependencies.

Required private-to-pub(super) changes: placement_visual_sample,
rendered_rect_on_output, request_surface_redraws and placement_physical_rect.
The remaining state impl still calls these helpers. No other visibility changes.

Validation: source identity/reconstruction, fmt, 709 locked all-feature/
all-target tests and strict Clippy pass. Batch runtime/release checks follow.
Move all 57 approved methods with unchanged bodies/docs to state/focus.rs.
Keep fields, shared types and tests in state/mod.rs. No new forwarding API,
ownership changes, dependencies or behavior changes.

Required private-to-pub(super) changes: release_popup_grab,
refocus_after_hide and neighbor_in_direction, for existing state/workspace
callers. All other method and field visibility stays unchanged.

Validation: byte-identity/reconstruction, 709 locked all-feature/all-target
tests and strict Clippy pass. The widened neighbor signature needs a line
wrap; apply that formatting in its own following commit, not in this move.
Move the 19 approved methods with unchanged bodies/docs into state/scroll.rs.
Keep fields, shared types, free helpers and tests in state/mod.rs. No accessors,
forwarding API, ownership changes, dependencies or behavior changes.

Required private-to-pub(super) changes: queue_scroll_pointer_refresh,
scroll_axis_step, scroll_focus_target, anchor_scroll_floaters,
rescue_scroll_floaters, floating_bounds and scroll_columns_changed, because
remaining state or the focus child calls them. No other visibility changes.

Validation: source identity/reconstruction, fmt, 709 locked all-feature/
all-target tests and strict Clippy pass. Batch runtime/release checks follow.
Move the 26 approved methods verbatim to state/session.rs, retaining existing
accessibility feature guards and all bodies/docs. Fields, shared types, free
helpers and tests remain in state/mod.rs. No new ownership or forwarding API.

Required private-to-pub(super) changes: lock_focus_target,
handle_client_disconnect and schedule_accessibility_sync, for existing calls
from focus, init and render child modules. All other visibility stays as-is.

Validation: source identity/reconstruction, fmt, 709 locked all-feature/
all-target tests and strict Clippy pass. Batch/default-feature checks follow.
Move the 40 approved methods to state/effects.rs with
unchanged bodies, docs, attributes and feature guards. Keep fields, shared
types, free helpers and tests in the parent. No forwarding API, dependency
or behavior changes.

Required private-to-pub(super) changes for existing callers:
layer_dim_amount, queue_shader_failure, window_render_alpha_for,
rounding_config_for_surface, rounding_applies,
rounding_or_cascade_shader_possible, borders_possible,
animated_borders_possible, surface_has_animated_border,
window_border_element, shadows_possible, window_shadow_element.

Validation: source identity/reconstruction, 709 locked all-feature/all-target
tests and strict Clippy pass. Any needed formatting follows separately; full
batch release/default-feature/runtime checks follow the remaining moves.
Move the 34 approved methods to state/physics.rs with
unchanged bodies, docs, attributes and feature guards. Keep fields, shared
types, free helpers and tests in the parent. No forwarding API, dependency
or behavior changes.

Required private-to-pub(super) changes for existing callers:
interactive_motion_half_life, sway_enabled_for_surface,
sync_float_physics_bodies, currents_enabled, cascade_liquid_applies,
start_window_move_animation, focus_window_with_ripple,
retile_with_viscosity.

Validation: source identity/reconstruction, 709 locked all-feature/all-target
tests and strict Clippy pass. Any needed formatting follows separately; full
batch release/default-feature/runtime checks follow the remaining moves.
Move the 18 approved methods to state/ocean.rs with
unchanged bodies, docs, attributes and feature guards. Keep fields, shared
types, free helpers and tests in the parent. No forwarding API, dependency
or behavior changes.

Required private-to-pub(super) changes for existing callers:
topmost_ocean_surface, caustics_active, retile_ocean,
migrate_classic_to_ocean, migrate_ocean_to_classic, ocean_focus_target.

Validation: source identity/reconstruction, 709 locked all-feature/all-target
tests and strict Clippy pass. Any needed formatting follows separately; full
batch release/default-feature/runtime checks follow the remaining moves.
Move the 36 approved methods to state/groups_depth.rs with
unchanged bodies, docs, attributes and feature guards. Keep fields, shared
types, free helpers and tests in the parent. No forwarding API, dependency
or behavior changes.

Required private-to-pub(super) changes for existing callers:
depth_live_alpha, poll_depth_background, custom_shader_last_stage,
group_activate_tab, toggle_column_tabbed, restore_all_depth_deck_windows.

Validation: source identity/reconstruction, 709 locked all-feature/all-target
tests and strict Clippy pass. Any needed formatting follows separately; full
batch release/default-feature/runtime checks follow the remaining moves.
Move the 60 approved methods to state/spatial.rs with
unchanged bodies, docs, attributes and feature guards. Keep fields, shared
types, free helpers and tests in the parent. No forwarding API, dependency
or behavior changes.

Remove the emptied parent impl and relocate its spatial-migration section
marker beside the unchanged migration methods.

Required private-to-pub(super) changes for existing callers:
enforce_floating_size_constraints_with_rule, is_floating,
float_body_context, placement_input_rect, translate_floating_window,
window_at_layout_position, window_surface_elements, retile_filtered,
move_window_to_output, spatial_is_tiled, spatial_tiled_window,
spatial_tiles_share_owner, allocate_ui_node_id.

Validation: source identity/reconstruction, 709 locked all-feature/all-target
tests and strict Clippy pass. Any needed formatting follows separately; full
batch release/default-feature/runtime checks follow the remaining moves.
Replace `use super::*` in the 13 state child modules with explicit
imports: smithay/std/crate paths each module actually uses, plus
`use super::{...}` for the parent's fields' types and free helpers.
Two accessibility-only imports in session.rs are gated on that feature.

state/mod.rs drops the imports only its children used; the tests module
keeps `use super::*` and gains RenderElementStates, which only tests use.

Everything after the import blocks is byte-identical to ba8b06a. No
visibility, ownership or behavior changes.

Validation: fmt, 709 all-feature/all-target tests, strict Clippy with
all and default features, all-feature release build; clean checks with
screencast-only and accessibility-only features. Nested matrix with
subscribers: 61 observations, 0 differences vs .206 and the fixed
baseline.
New real-protocol test on two simulated outputs: tiled windows on two
workspaces and both outputs, a floating window, a pinned window, a tab
group and a fullscreen window go Classic -> Ocean -> Scroll -> Classic
-> Scroll -> Ocean -> Classic. After every step each window has exactly
one spatial owner, and focus, pin, fullscreen output, group membership
and floating state are unchanged. Classic <-> Scroll leaves membership
identical, and every return from Ocean restores the starting workspace
membership and floating rectangle exactly.

The child process runs with private XDG directories and a 10 s deadline,
the same isolation as the fullscreen regression test; floating_placement
fixture items become pub(super) so both tests share them.

710 tests, fmt and strict Clippy (all and default features) pass. No
production code changes.
Switching to Ocean turned scratchpad workspace 0 into a reef exactly on
top of workspace 1 and a named scratchpad into a reef at x = i32::MAX.
Switching back renumbered the overlapping reefs in no fixed order (a
normal tile could land on workspace 2), and the scratchpad windows came
back as ordinary visible workspaces.

Scratchpad trees and unpinned scratchpad floaters now stay in their
workspace storage while Ocean runs. Ocean renders neither, so they stay
hidden, and they come back exactly as they were. A shown scratchpad points
the camera at the workspace it was opened from. Pinned floaters still
migrate, since they show on every workspace.

Every engine change also lost the stacking order of floating windows:
both directions moved them in HashMap order, and Classic/Scroll remapped
them that way too. The switch now records the order (Space bottom to top,
or Ocean's front-to-back stack), drains Ocean floaters in stack order,
and pushes and remaps them in that order.

Layouts::drain_for_migration takes a keep predicate and unindexes only
the trees it drains.

New regression test (red on the old code, green three runs in a row):
six stacked floaters, a scratchpad and a named scratchpad through
Classic -> Ocean -> Classic -> Scroll -> Classic. 711 tests, fmt, strict
Clippy with all and default features; nested matrix with subscribers:
61 observations, 0 differences.
Fi3w0 added 5 commits October 28, 2026 05:59
SpatialEngine mixed two questions: who owns placement (numbered
workspaces or the Ocean world) and how a workspace shows its tiles
(Classic or Scroll). Add SpatialOwnership and WorkspaceProjection, with
SpatialEngine::ownership() and projection(), and rewrite every runtime
engine check to the axis it actually tests:

- == / != Ocean          -> ownership() == / != SpatialOwnership::Ocean
- == / != Scroll|Classic -> projection() == / != Some(...)
- uses_workspaces()      -> ownership() == Workspaces (method removed)
- Classic | Scroll => ..., Ocean => ... matches -> match ownership()
- migration dispatch and tide_workspace_value -> ownership()

Each rewrite is equivalent case by case; matches that need the whole
engine (names, the engine list in tests) keep SpatialEngine. The public
`engine = classic|scroll|ocean` config is unchanged.

711 tests, fmt, strict Clippy with all and default features pass.
config.spatial_engine was both the config file's request and the
running engine: migrate_spatial_engine wrote the live value back into
config, and reload patched new_config after a refused migration so the
two would not disagree.

New Smallvil::spatial: SpatialState { engine } holds the running engine,
and only migrate_spatial_engine changes it. Every runtime read (132
sites) goes through self.spatial.engine. config.spatial_engine now only
records what the file asks for: reload migrates whenever the request
differs from the running engine, and a refused migration leaves the
request in place instead of patching it. A later reload therefore still
retries, and after a live `engine:` action a reload still returns to
the file's engine, both as before.

Step 8 moves the workspace and Ocean ownership state into SpatialState.

New test: a migration refused for lack of an output keeps the request,
and the next reload of the same file performs it. 712 tests, fmt, strict
Clippy with all and default features; nested matrix with subscribers:
61 observations, 0 differences vs .208.
surface_under, window_under and fullscreen_surface_under did not split
on ownership: Classic finds windows under the pointer through Space,
while Scroll and Ocean go through render_placements, which carry their
view offsets. Add SpatialState::hit_tests_through_space() and use it at
those three checks. Same predicate, same behavior.

Step 7's planned per-operation dispatch otherwise does not fit the code,
and Fiw chose to shrink it: focus targeting already branches once at
the top, map_toplevel's Ocean checks are interleaved with shared logic,
and output removal is the same for every engine.

712 tests, fmt, strict Clippy with all and default features; nested
matrix with subscribers: 61 observations, 0 differences vs .208.
@Fi3w0
Fi3w0 merged commit 4939494 into master Oct 28, 2026
1 check passed
@Fi3w0
Fi3w0 deleted the refactor/state-init branch October 28, 2026 05:32
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