Skip to content

Jmq/rwlock device state#80

Merged
jmquigs merged 2 commits into
masterfrom
jmq/rwlock-device-state
May 15, 2026
Merged

Jmq/rwlock device state#80
jmquigs merged 2 commits into
masterfrom
jmq/rwlock-device-state

Conversation

@jmquigs
Copy link
Copy Markdown
Owner

@jmquigs jmquigs commented May 15, 2026

No description provided.

claude added 2 commits May 7, 2026 22:40
Continues removing static muts. DEVICE_STATE was a `*mut DeviceState`
guarded by a separate marker `RwLock<()>` (DEVICE_STATE_LOCK), which
was used inconsistently and often skipped entirely. This collapses the
two into a single `RwLock<DeviceStatePtr>`, where DeviceStatePtr is a
Send/Sync wrapper around the raw pointer, so every read/write goes
through the same lock.

API changes in the device_state crate:
- `dev_state()` and `dev_state_d3d11_nolock()` are gone. Both used to
  hand out a `&'static mut`, which can't be reconciled with a guard
  lifetime, so every call site was migrated.
- `dev_state_write()` / `dev_state_read()` return `Option<(guard,
  &/&mut DeviceState)>`.
- `dev_state_d3d11_write()` and `dev_state_d3d11_read()` keep their
  shape, but `_read` now returns `&` instead of `&mut` (the previous
  `_read` was unsound).
- All accessors fail safely on lock poisoning by logging and returning
  None rather than panicking, so a poison can't take the game down.

Re-entrancy: `std::sync::RwLock` is not reentrant, so several call
sites had to be reworked to drop the guard before invoking real D3D
fns (which can re-enter our hooks on the same thread). Notably:

- D3D9 `hook_present`, `hook_release`, `hook_set_texture`,
  `hook_set_stream_source`, `hook_reset`, `hook_draw_indexed_primitive`,
  `hook_create_device`, `hook_CreateTexture`, `hook_UpdateTexture`,
  and `create_d3d9` now extract the real fn pointer under a brief
  read guard, drop the guard, then invoke. The release path also
  threads `ref_count` updates through scoped write guards.
- `HookDirect3D11Context` and friends now derive Copy so
  `get_hook_context` returns a copy and callers don't need to hold
  the dev_state lock to invoke the real fn pointers.
- DX11 `hook_draw_indexed` was restructured so dev_state guards are
  acquired only briefly: the geom check / vb_state snapshot under a
  read guard, mod work outside any guard, and metrics under a final
  write guard. `ensure_vb_checksum_dx11`, `hook_snapshot::take`, and
  `with_dev_ptr` are no longer called with the lock held.
- `init_device_state_once` now mutates the pointer through the write
  guard rather than via static mut. Tests serialize on a separate
  `TEST_DEV_STATE_LOCK` because the new init path takes the dev_state
  write lock internally.
JMQ Note:

This merge includes the device state RWLock.  Previously it was
just a static mut and there was the potential for undefined behavior
since multiple parts of the code were potentially creating &mut's to
it at the same time.  Inside an RWLock, this can't happen.

Performance testing was not completely definitive but I couldn't
pinpoint any real slowdown from this.  For future work,
possibly consider parking lot
RwLock as an optimization - but I did try it briefly and did not see any
real speedup.

This required a conflict resolution between the refill code which claude
describes hereafter:

The conflict was between this branch's RwLock-based dev_state access
pattern and master's new "needs refill for layout" mod reload logic
(#77). Resolution preserves the RwLock pattern (lock acquired/dropped
in narrow scopes) while integrating the refill logic: we precompute
the current input layout's semantic mask via a brief read lock before
calling check_and_render_mod, then pass scalars into the closure so it
no longer needs the dev_state lock.

needs_refill_for_layout was refactored to accept an Option<SemanticMask>
instead of taking a reference to the layouts hashmap, so callers can
look up the mask once under their read guard rather than holding the
lock across the closure.

The post_mod_check NotRenderedButLoadRequested arm now also calls
mod_load::reset_for_reload from master, while keeping the rwlock
pattern of reading current_input_layout via a brief dev_state guard
before taking the LOADED_MODS lock.
@jmquigs jmquigs force-pushed the jmq/rwlock-device-state branch from ae90dc2 to 3a7b1b5 Compare May 15, 2026 01:50
@jmquigs jmquigs merged commit 4561d20 into master May 15, 2026
1 check passed
@jmquigs jmquigs deleted the jmq/rwlock-device-state branch May 15, 2026 01:56
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.

2 participants