Require &mut self for World::increment_change_tick (#14459)

# Objective

The method `World::increment_change_tick` currently takes `&self` as the
method receiver, which is semantically strange. Even though the interior
mutability is sound, the existence of this method is strange since we
tend to think of `&World` as being a read-only snapshot of a world, not
an aliasable reference to a world with mutability. For those purposes,
we have `UnsafeWorldCell`.

## Solution

Change the method signature to take `&mut self`. Use exclusive access to
remove the need for atomic adds, which makes the method slightly more
efficient. Redirect users to [`UnsafeWorldCell::increment_change_tick`]
if they need to increment the world's change tick from an aliased
context.

In practice I don't think there will be many breakages, if any. In cases
where you need to call `increment_change_tick`, you usually already have
either `&mut World` or `UnsafeWorldCell`.

---

## Migration Guide

The method `World::increment_change_tick` now requires `&mut self`
instead of `&self`. If you need to call this method but do not have
mutable access to the world, consider using
`world.as_unsafe_world_cell_readonly().increment_change_tick()`, which
does the same thing, but is less efficient than the method on `World`
due to requiring atomic synchronization.

```rust
fn my_system(world: &World) {
    // Before
    world.increment_change_tick();

    // After
    world.as_unsafe_world_cell_readonly().increment_change_tick();
}
```
This commit is contained in:
Joseph 2024-07-24 05:42:28 -07:00 committed by GitHub
parent 8dc6ccfbe7
commit 218f78157d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 14 additions and 6 deletions

View file

@ -124,9 +124,7 @@ where
let out = self.func.run(world, input, params); let out = self.func.run(world, input, params);
world.flush(); world.flush();
let change_tick = world.change_tick.get_mut(); self.system_meta.last_run = world.increment_change_tick();
self.system_meta.last_run.set(*change_tick);
*change_tick = change_tick.wrapping_add(1);
out out
}) })

View file

@ -2063,9 +2063,16 @@ impl World {
} }
/// Increments the world's current change tick and returns the old value. /// Increments the world's current change tick and returns the old value.
///
/// If you need to call this method, but do not have `&mut` access to the world,
/// consider using [`as_unsafe_world_cell_readonly`](Self::as_unsafe_world_cell_readonly)
/// to obtain an [`UnsafeWorldCell`] and calling [`increment_change_tick`](UnsafeWorldCell::increment_change_tick) on that.
/// Note that this *can* be done in safe code, despite the name of the type.
#[inline] #[inline]
pub fn increment_change_tick(&self) -> Tick { pub fn increment_change_tick(&mut self) -> Tick {
let prev_tick = self.change_tick.fetch_add(1, Ordering::AcqRel); let change_tick = self.change_tick.get_mut();
let prev_tick = *change_tick;
*change_tick = change_tick.wrapping_add(1);
Tick::new(prev_tick) Tick::new(prev_tick)
} }

View file

@ -276,7 +276,10 @@ impl<'w> UnsafeWorldCell<'w> {
pub fn increment_change_tick(self) -> Tick { pub fn increment_change_tick(self) -> Tick {
// SAFETY: // SAFETY:
// - we only access world metadata // - we only access world metadata
unsafe { self.world_metadata() }.increment_change_tick() let change_tick = unsafe { &self.world_metadata().change_tick };
// NOTE: We can used a relaxed memory ordering here, since nothing
// other than the atomic value itself is relying on atomic synchronization
Tick::new(change_tick.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
} }
/// Provides unchecked access to the internal data stores of the [`World`]. /// Provides unchecked access to the internal data stores of the [`World`].