Skip to content

Commit

Permalink
Add World::get_resource_or_init as an alternative to `World::get_re…
Browse files Browse the repository at this point in the history
…source_or_insert_with` (#15758)

# Objective

If a `Resource` implements `FromWorld` or `Default`, it's nicer to be
able to write:

```rust
let foo = world.get_resource_or_init::<Foo>();
```

Rather than:

```rust
let foo = world.get_resource_or_insert_with(Foo::default);
```

The latter is also not possible if a type implements `FromWorld` only,
and not `Default`.

## Solution

Added:

```rust
impl World {
    pub fn get_resource_or_init<R: Resource + FromWorld>(&mut self) -> Mut<'_, R>;
}
```

Turns out all current in-engine uses of `get_resource_or_insert_with`
are exactly the above, so they've also been replaced.

## Testing

- Added a doc-test.
- Also added a doc-test for `World::get_resource_or_insert_with`.
  • Loading branch information
ItsDoot authored Oct 9, 2024
1 parent 88d9ead commit b4071ca
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 12 deletions.
4 changes: 2 additions & 2 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ impl Plugin for AssetPlugin {
{
let mut sources = app
.world_mut()
.get_resource_or_insert_with::<AssetSourceBuilders>(Default::default);
.get_resource_or_init::<AssetSourceBuilders>();
sources.init_default_source(
&self.file_path,
(!matches!(self.mode, AssetMode::Unprocessed))
Expand Down Expand Up @@ -519,7 +519,7 @@ impl AssetApp for App {
{
let mut sources = self
.world_mut()
.get_resource_or_insert_with(AssetSourceBuilders::default);
.get_resource_or_init::<AssetSourceBuilders>();
sources.insert(id, source);
}

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/event/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl EventRegistry {
// By initializing the resource here, we can be sure that it is present,
// and receive the correct, up-to-date `ComponentId` even if it was previously removed.
let component_id = world.init_resource::<Events<T>>();
let mut registry = world.get_resource_or_insert_with(Self::default);
let mut registry = world.get_resource_or_init::<Self>();
registry.event_updates.push(RegisteredEvent {
component_id,
previously_updated: false,
Expand Down Expand Up @@ -84,7 +84,7 @@ impl EventRegistry {
/// Removes an event from the world and it's associated [`EventRegistry`].
pub fn deregister_events<T: Event>(world: &mut World) {
let component_id = world.init_resource::<Events<T>>();
let mut registry = world.get_resource_or_insert_with(Self::default);
let mut registry = world.get_resource_or_init::<Self>();
registry
.event_updates
.retain(|e| e.component_id != component_id);
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/schedule/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ impl Schedule {
if self.graph.changed {
self.graph.initialize(world);
let ignored_ambiguities = world
.get_resource_or_insert_with::<Schedules>(Schedules::default)
.get_resource_or_init::<Schedules>()
.ignored_scheduling_ambiguities
.clone();
self.graph.update_schedule(
Expand Down
91 changes: 90 additions & 1 deletion crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2101,6 +2101,19 @@ impl World {

/// Gets a mutable reference to the resource of type `T` if it exists,
/// otherwise inserts the resource using the result of calling `func`.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Resource)]
/// struct MyResource(i32);
///
/// # let mut world = World::new();
/// let my_res = world.get_resource_or_insert_with(|| MyResource(10));
/// assert_eq!(my_res.0, 10);
/// ```
#[inline]
#[track_caller]
pub fn get_resource_or_insert_with<R: Resource>(
Expand Down Expand Up @@ -2137,6 +2150,82 @@ impl World {
unsafe { data.with_type::<R>() }
}

/// Gets a mutable reference to the resource of type `T` if it exists,
/// otherwise initializes the resource by calling its [`FromWorld`]
/// implementation.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Resource)]
/// struct Foo(i32);
///
/// impl Default for Foo {
/// fn default() -> Self {
/// Self(15)
/// }
/// }
///
/// #[derive(Resource)]
/// struct MyResource(i32);
///
/// impl FromWorld for MyResource {
/// fn from_world(world: &mut World) -> Self {
/// let foo = world.get_resource_or_init::<Foo>();
/// Self(foo.0 * 2)
/// }
/// }
///
/// # let mut world = World::new();
/// let my_res = world.get_resource_or_init::<MyResource>();
/// assert_eq!(my_res.0, 30);
/// ```
#[track_caller]
pub fn get_resource_or_init<R: Resource + FromWorld>(&mut self) -> Mut<'_, R> {
#[cfg(feature = "track_change_detection")]
let caller = Location::caller();
let change_tick = self.change_tick();
let last_change_tick = self.last_change_tick();

let component_id = self.components.register_resource::<R>();
if self
.storages
.resources
.get(component_id)
.map_or(true, |data| !data.is_present())
{
let value = R::from_world(self);
OwningPtr::make(value, |ptr| {
// SAFETY: component_id was just initialized and corresponds to resource of type R.
unsafe {
self.insert_resource_by_id(
component_id,
ptr,
#[cfg(feature = "track_change_detection")]
caller,
);
}
});
}

// SAFETY: The resource was just initialized if it was empty.
let data = unsafe {
self.storages
.resources
.get_mut(component_id)
.debug_checked_unwrap()
};
// SAFETY: The resource must be present, as we would have inserted it if it was empty.
let data = unsafe {
data.get_mut(last_change_tick, change_tick)
.debug_checked_unwrap()
};
// SAFETY: The underlying type of the resource is `R`.
unsafe { data.with_type::<R>() }
}

/// Gets an immutable reference to the non-send resource of the given type, if it exists.
///
/// # Panics
Expand Down Expand Up @@ -3230,7 +3319,7 @@ impl World {
///
/// The `Schedules` resource will be initialized if it does not already exist.
pub fn add_schedule(&mut self, schedule: Schedule) {
let mut schedules = self.get_resource_or_insert_with(Schedules::default);
let mut schedules = self.get_resource_or_init::<Schedules>();
schedules.insert(schedule);
}

Expand Down
8 changes: 3 additions & 5 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,10 @@ impl AppGizmoBuilder for App {
}

self.world_mut()
.get_resource_or_insert_with::<GizmoConfigStore>(Default::default)
.get_resource_or_init::<GizmoConfigStore>()
.register::<Config>();

let mut handles = self
.world_mut()
.get_resource_or_insert_with::<LineGizmoHandles>(Default::default);
let mut handles = self.world_mut().get_resource_or_init::<LineGizmoHandles>();

handles.list.insert(TypeId::of::<Config>(), None);
handles.strip.insert(TypeId::of::<Config>(), None);
Expand Down Expand Up @@ -288,7 +286,7 @@ impl AppGizmoBuilder for App {
self.init_gizmo_group::<Config>();

self.world_mut()
.get_resource_or_insert_with::<GizmoConfigStore>(Default::default)
.get_resource_or_init::<GizmoConfigStore>()
.insert(config, group);

self
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_state/src/state/transitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ pub(crate) fn internal_apply_state_transition<S: States>(
/// Runs automatically when using `App` to insert states, but needs to
/// be added manually in other situations.
pub fn setup_state_transitions_in_world(world: &mut World) {
let mut schedules = world.get_resource_or_insert_with(Schedules::default);
let mut schedules = world.get_resource_or_init::<Schedules>();
if schedules.contains(StateTransition) {
return;
}
Expand Down

0 comments on commit b4071ca

Please sign in to comment.