-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add World::iter_resources()
and World::iter_resources_mut()
methods
#12019
Conversation
@james-j-obrien, can I get your review / help with this? It very much feels like a sibling of the dynamic query work. |
@james-j-obrien Thanks! That makes me more confident with what I was doing. Updated the safety comments with the explanations. Edit: Added you as a co-author, if that's okay, since I really wouldn't have been able to verify this on my own (or even be able to fully reason about the safety of this without your explanations.) |
Co-authored-by: James O'Brien <james.obrien@drafly.net>
d2e9aca
to
e04a73e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code looks good, but I have a question:
It was already possible possible to get all resources from a World
in Bevy, just more complicated:
- Get the
AppTypeRegistry
resource - Get a
RwLockReadGuard<TypeRegistry>
fromAppTypeRegistry::read
- Filter the registrations by if they are a resource in the
World
registry_lock
.iter()
.filter(|registration| {
world
.components()
.get_resource_id(registration.type_id())
.is_some()
})
- You now have an iterator of
&TypeRegistration
s, which you can use to get references to the resources.
Aside from being simpler, how does this differ from the above solution?
Hmm honestly I wasn't aware you could do that, TIL. If I understand it correctly, besides being more easily discoverable and somewhat shorter to use, the API in this PR has the benefits of:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change currently assumes that Resources::iter
only returns present resources, when in practice it returns all resources ever initialized within the World, some of which may have been removed. The implementation here can be significantly simpler and not involve panics.
@@ -2796,4 +2987,63 @@ mod tests { | |||
let mut world = World::new(); | |||
world.spawn(()); | |||
} | |||
|
|||
#[test] | |||
fn iterate_resources() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need a test case to ensure that iteration doesn't panic if you remove a resource. Also for miri.
} | ||
|
||
#[test] | ||
fn iterate_resources_mut() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need a test case to ensure that iteration doesn't panic if you remove a resource. Also for miri.
self.storages.resources.iter().map(|(component_id, data)| { | ||
let component_info = self.components.get_info(component_id).unwrap_or_else(|| { | ||
panic!("ComponentInfo should exist for all resources in the world, but it does not for {:?}", component_id); | ||
}); | ||
let ptr = data.get_data().unwrap_or_else(|| { | ||
panic!( | ||
"When iterating all resources, resource of type {} was supposed to exist, but did not.", | ||
component_info.name() | ||
) | ||
}); | ||
(component_info, ptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.storages.resources.iter().map(|(component_id, data)| { | |
let component_info = self.components.get_info(component_id).unwrap_or_else(|| { | |
panic!("ComponentInfo should exist for all resources in the world, but it does not for {:?}", component_id); | |
}); | |
let ptr = data.get_data().unwrap_or_else(|| { | |
panic!( | |
"When iterating all resources, resource of type {} was supposed to exist, but did not.", | |
component_info.name() | |
) | |
}); | |
(component_info, ptr) | |
self.storages.resources.iter().filter_map(|(component_id, data)| { | |
Some((component_id, data.get_ptr()?) |
self.storages.resources.iter().map(|(component_id, data)| { | ||
let component_info = self.components.get_info(component_id).unwrap_or_else(|| { | ||
panic!("ComponentInfo should exist for all resources in the world, but it does not for {:?}", component_id); | ||
}); | ||
|
||
let (ptr, ticks) = data.get_with_ticks().unwrap_or_else(|| { | ||
panic!( | ||
"When iterating all resources, resource of type {} was supposed to exist, but did not.", | ||
component_info.name() | ||
) | ||
}); | ||
|
||
// SAFETY: | ||
// - We have exclusive access to the world, so no other code can be aliasing the `TickCells` | ||
// - We only hold one `TicksMut` at a time, and we let go of it before getting the next one | ||
let ticks = unsafe { | ||
TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()) | ||
}; | ||
|
||
let mut_untyped = MutUntyped { | ||
// SAFETY: | ||
// - We have exclusive access to the world, so no other code can be aliasing the `Ptr` | ||
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one | ||
value: unsafe { ptr.assert_unique() }, | ||
ticks, | ||
}; | ||
|
||
(component_info, mut_untyped) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.storages.resources.iter().map(|(component_id, data)| { | |
let component_info = self.components.get_info(component_id).unwrap_or_else(|| { | |
panic!("ComponentInfo should exist for all resources in the world, but it does not for {:?}", component_id); | |
}); | |
let (ptr, ticks) = data.get_with_ticks().unwrap_or_else(|| { | |
panic!( | |
"When iterating all resources, resource of type {} was supposed to exist, but did not.", | |
component_info.name() | |
) | |
}); | |
// SAFETY: | |
// - We have exclusive access to the world, so no other code can be aliasing the `TickCells` | |
// - We only hold one `TicksMut` at a time, and we let go of it before getting the next one | |
let ticks = unsafe { | |
TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()) | |
}; | |
let mut_untyped = MutUntyped { | |
// SAFETY: | |
// - We have exclusive access to the world, so no other code can be aliasing the `Ptr` | |
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one | |
value: unsafe { ptr.assert_unique() }, | |
ticks, | |
}; | |
(component_info, mut_untyped) | |
self.storages.resources.iter().filter_map(|(component_id, data)| { | |
let (ptr, ticks) = data.get_with_ticks()?; | |
// SAFETY: | |
// - We have exclusive access to the world, so no other code can be aliasing the `TickCells` | |
// - We only hold one `TicksMut` at a time, and we let go of it before getting the next one | |
let ticks = unsafe { | |
TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()) | |
}; | |
let mut_untyped = MutUntyped { | |
// SAFETY: | |
// - We have exclusive access to the world, so no other code can be aliasing the `Ptr` | |
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one | |
value: unsafe { ptr.assert_unique() }, | |
ticks, | |
}; | |
Some((component_info, mut_untyped)) |
# Objective - Closes #12019 - Related to #4955 - Useful for dev_tools and networking ## Solution - Create `World::iter_resources()` and `World::iter_resources_mut()` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: James Liu <contact@jamessliu.com> Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com>
# Objective - Closes bevyengine#12019 - Related to bevyengine#4955 - Useful for dev_tools and networking ## Solution - Create `World::iter_resources()` and `World::iter_resources_mut()` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: James Liu <contact@jamessliu.com> Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com>
Objective
World
;ComponentInfo
(e.g. for reflection) and also read (and optionally mutate) the resources directly.Solution
World::iter_resources()
andWorld::iter_resources_mut()
methods;impl Iterator<Item = (&ComponentInfo, Ptr<'_>)>
andimpl Iterator<Item = (&ComponentInfo, MutUntyped<'_>)>
, respectively.Note: My original implementation in the remote protocol branch also added methods for
!Send
resources, but as suggested by WrongShoe on discord, I'm holding off on that since #9122 is likely to land soon.Note 2: Related to #4955 (But much smaller/more constrained, this requires
World
access andunsafe
)Changelog
World::iter_resources()
andWorld::iter_resources_mut()
methods for iterating all resources dynamically at run time;