Skip to content

Commit d3aaf55

Browse files
matiqo15alice-i-cecilejames7132pablo-lua
authored andcommitted
Add methods iter_resources and iter_resources_mut (bevyengine#12829)
# 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>
1 parent e964849 commit d3aaf55

File tree

1 file changed

+269
-0
lines changed
  • crates/bevy_ecs/src/world

1 file changed

+269
-0
lines changed

crates/bevy_ecs/src/world/mod.rs

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2136,6 +2136,209 @@ impl World {
21362136
}
21372137
}
21382138

2139+
/// Iterates over all resources in the world.
2140+
///
2141+
/// The returned iterator provides lifetimed, but type-unsafe pointers. Actually reading the contents
2142+
/// of each resource will require the use of unsafe code.
2143+
///
2144+
/// # Examples
2145+
///
2146+
/// ## Printing the size of all resources
2147+
///
2148+
/// ```
2149+
/// # use bevy_ecs::prelude::*;
2150+
/// # #[derive(Resource)]
2151+
/// # struct A(u32);
2152+
/// # #[derive(Resource)]
2153+
/// # struct B(u32);
2154+
/// #
2155+
/// # let mut world = World::new();
2156+
/// # world.insert_resource(A(1));
2157+
/// # world.insert_resource(B(2));
2158+
/// let mut total = 0;
2159+
/// for (info, _) in world.iter_resources() {
2160+
/// println!("Resource: {}", info.name());
2161+
/// println!("Size: {} bytes", info.layout().size());
2162+
/// total += info.layout().size();
2163+
/// }
2164+
/// println!("Total size: {} bytes", total);
2165+
/// # assert_eq!(total, std::mem::size_of::<A>() + std::mem::size_of::<B>());
2166+
/// ```
2167+
///
2168+
/// ## Dynamically running closures for resources matching specific `TypeId`s
2169+
///
2170+
/// ```
2171+
/// # use bevy_ecs::prelude::*;
2172+
/// # use std::collections::HashMap;
2173+
/// # use std::any::TypeId;
2174+
/// # use bevy_ptr::Ptr;
2175+
/// # #[derive(Resource)]
2176+
/// # struct A(u32);
2177+
/// # #[derive(Resource)]
2178+
/// # struct B(u32);
2179+
/// #
2180+
/// # let mut world = World::new();
2181+
/// # world.insert_resource(A(1));
2182+
/// # world.insert_resource(B(2));
2183+
/// #
2184+
/// // In this example, `A` and `B` are resources. We deliberately do not use the
2185+
/// // `bevy_reflect` crate here to showcase the low-level [`Ptr`] usage. You should
2186+
/// // probably use something like `ReflectFromPtr` in a real-world scenario.
2187+
///
2188+
/// // Create the hash map that will store the closures for each resource type
2189+
/// let mut closures: HashMap<TypeId, Box<dyn Fn(&Ptr<'_>)>> = HashMap::new();
2190+
///
2191+
/// // Add closure for `A`
2192+
/// closures.insert(TypeId::of::<A>(), Box::new(|ptr| {
2193+
/// // SAFETY: We assert ptr is the same type of A with TypeId of A
2194+
/// let a = unsafe { &ptr.deref::<A>() };
2195+
/// # assert_eq!(a.0, 1);
2196+
/// // ... do something with `a` here
2197+
/// }));
2198+
///
2199+
/// // Add closure for `B`
2200+
/// closures.insert(TypeId::of::<B>(), Box::new(|ptr| {
2201+
/// // SAFETY: We assert ptr is the same type of B with TypeId of B
2202+
/// let b = unsafe { &ptr.deref::<B>() };
2203+
/// # assert_eq!(b.0, 2);
2204+
/// // ... do something with `b` here
2205+
/// }));
2206+
///
2207+
/// // Iterate all resources, in order to run the closures for each matching resource type
2208+
/// for (info, ptr) in world.iter_resources() {
2209+
/// let Some(type_id) = info.type_id() else {
2210+
/// // It's possible for resources to not have a `TypeId` (e.g. non-Rust resources
2211+
/// // dynamically inserted via a scripting language) in which case we can't match them.
2212+
/// continue;
2213+
/// };
2214+
///
2215+
/// let Some(closure) = closures.get(&type_id) else {
2216+
/// // No closure for this resource type, skip it.
2217+
/// continue;
2218+
/// };
2219+
///
2220+
/// // Run the closure for the resource
2221+
/// closure(&ptr);
2222+
/// }
2223+
/// ```
2224+
#[inline]
2225+
pub fn iter_resources(&self) -> impl Iterator<Item = (&ComponentInfo, Ptr<'_>)> {
2226+
self.storages
2227+
.resources
2228+
.iter()
2229+
.filter_map(|(component_id, data)| {
2230+
// SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with it's ID.
2231+
let component_info = unsafe {
2232+
self.components
2233+
.get_info(component_id)
2234+
.debug_checked_unwrap()
2235+
};
2236+
Some((component_info, data.get_data()?))
2237+
})
2238+
}
2239+
2240+
/// Mutably iterates over all resources in the world.
2241+
///
2242+
/// The returned iterator provides lifetimed, but type-unsafe pointers. Actually reading from or writing
2243+
/// to the contents of each resource will require the use of unsafe code.
2244+
///
2245+
/// # Example
2246+
///
2247+
/// ```
2248+
/// # use bevy_ecs::prelude::*;
2249+
/// # use bevy_ecs::change_detection::MutUntyped;
2250+
/// # use std::collections::HashMap;
2251+
/// # use std::any::TypeId;
2252+
/// # #[derive(Resource)]
2253+
/// # struct A(u32);
2254+
/// # #[derive(Resource)]
2255+
/// # struct B(u32);
2256+
/// #
2257+
/// # let mut world = World::new();
2258+
/// # world.insert_resource(A(1));
2259+
/// # world.insert_resource(B(2));
2260+
/// #
2261+
/// // In this example, `A` and `B` are resources. We deliberately do not use the
2262+
/// // `bevy_reflect` crate here to showcase the low-level `MutUntyped` usage. You should
2263+
/// // probably use something like `ReflectFromPtr` in a real-world scenario.
2264+
///
2265+
/// // Create the hash map that will store the mutator closures for each resource type
2266+
/// let mut mutators: HashMap<TypeId, Box<dyn Fn(&mut MutUntyped<'_>)>> = HashMap::new();
2267+
///
2268+
/// // Add mutator closure for `A`
2269+
/// mutators.insert(TypeId::of::<A>(), Box::new(|mut_untyped| {
2270+
/// // Note: `MutUntyped::as_mut()` automatically marks the resource as changed
2271+
/// // for ECS change detection, and gives us a `PtrMut` we can use to mutate the resource.
2272+
/// // SAFETY: We assert ptr is the same type of A with TypeId of A
2273+
/// let a = unsafe { &mut mut_untyped.as_mut().deref_mut::<A>() };
2274+
/// # a.0 += 1;
2275+
/// // ... mutate `a` here
2276+
/// }));
2277+
///
2278+
/// // Add mutator closure for `B`
2279+
/// mutators.insert(TypeId::of::<B>(), Box::new(|mut_untyped| {
2280+
/// // SAFETY: We assert ptr is the same type of B with TypeId of B
2281+
/// let b = unsafe { &mut mut_untyped.as_mut().deref_mut::<B>() };
2282+
/// # b.0 += 1;
2283+
/// // ... mutate `b` here
2284+
/// }));
2285+
///
2286+
/// // Iterate all resources, in order to run the mutator closures for each matching resource type
2287+
/// for (info, mut mut_untyped) in world.iter_resources_mut() {
2288+
/// let Some(type_id) = info.type_id() else {
2289+
/// // It's possible for resources to not have a `TypeId` (e.g. non-Rust resources
2290+
/// // dynamically inserted via a scripting language) in which case we can't match them.
2291+
/// continue;
2292+
/// };
2293+
///
2294+
/// let Some(mutator) = mutators.get(&type_id) else {
2295+
/// // No mutator closure for this resource type, skip it.
2296+
/// continue;
2297+
/// };
2298+
///
2299+
/// // Run the mutator closure for the resource
2300+
/// mutator(&mut mut_untyped);
2301+
/// }
2302+
/// # assert_eq!(world.resource::<A>().0, 2);
2303+
/// # assert_eq!(world.resource::<B>().0, 3);
2304+
/// ```
2305+
#[inline]
2306+
pub fn iter_resources_mut(&mut self) -> impl Iterator<Item = (&ComponentInfo, MutUntyped<'_>)> {
2307+
self.storages
2308+
.resources
2309+
.iter()
2310+
.filter_map(|(component_id, data)| {
2311+
// SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with it's ID.
2312+
let component_info = unsafe {
2313+
self.components
2314+
.get_info(component_id)
2315+
.debug_checked_unwrap()
2316+
};
2317+
let (ptr, ticks) = data.get_with_ticks()?;
2318+
2319+
// SAFETY:
2320+
// - We have exclusive access to the world, so no other code can be aliasing the `TickCells`
2321+
// - We only hold one `TicksMut` at a time, and we let go of it before getting the next one
2322+
let ticks = unsafe {
2323+
TicksMut::from_tick_cells(
2324+
ticks,
2325+
self.last_change_tick(),
2326+
self.read_change_tick(),
2327+
)
2328+
};
2329+
2330+
let mut_untyped = MutUntyped {
2331+
// SAFETY:
2332+
// - We have exclusive access to the world, so no other code can be aliasing the `Ptr`
2333+
// - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one
2334+
value: unsafe { ptr.assert_unique() },
2335+
ticks,
2336+
};
2337+
2338+
Some((component_info, mut_untyped))
2339+
})
2340+
}
2341+
21392342
/// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists.
21402343
/// The returned pointer must not be used to modify the resource, and must not be
21412344
/// dereferenced after the immutable borrow of the [`World`] ends.
@@ -2554,6 +2757,12 @@ mod tests {
25542757
#[derive(Resource)]
25552758
struct TestResource(u32);
25562759

2760+
#[derive(Resource)]
2761+
struct TestResource2(String);
2762+
2763+
#[derive(Resource)]
2764+
struct TestResource3;
2765+
25572766
#[test]
25582767
fn get_resource_by_id() {
25592768
let mut world = World::new();
@@ -2594,6 +2803,66 @@ mod tests {
25942803
assert_eq!(resource.0, 43);
25952804
}
25962805

2806+
#[test]
2807+
fn iter_resources() {
2808+
let mut world = World::new();
2809+
world.insert_resource(TestResource(42));
2810+
world.insert_resource(TestResource2("Hello, world!".to_string()));
2811+
world.insert_resource(TestResource3);
2812+
world.remove_resource::<TestResource3>();
2813+
2814+
let mut iter = world.iter_resources();
2815+
2816+
let (info, ptr) = iter.next().unwrap();
2817+
assert_eq!(info.name(), std::any::type_name::<TestResource>());
2818+
// SAFETY: We know that the resource is of type `TestResource`
2819+
assert_eq!(unsafe { ptr.deref::<TestResource>().0 }, 42);
2820+
2821+
let (info, ptr) = iter.next().unwrap();
2822+
assert_eq!(info.name(), std::any::type_name::<TestResource2>());
2823+
assert_eq!(
2824+
// SAFETY: We know that the resource is of type `TestResource2`
2825+
unsafe { &ptr.deref::<TestResource2>().0 },
2826+
&"Hello, world!".to_string()
2827+
);
2828+
2829+
assert!(iter.next().is_none());
2830+
}
2831+
2832+
#[test]
2833+
fn iter_resources_mut() {
2834+
let mut world = World::new();
2835+
world.insert_resource(TestResource(42));
2836+
world.insert_resource(TestResource2("Hello, world!".to_string()));
2837+
world.insert_resource(TestResource3);
2838+
world.remove_resource::<TestResource3>();
2839+
2840+
let mut iter = world.iter_resources_mut();
2841+
2842+
let (info, mut mut_untyped) = iter.next().unwrap();
2843+
assert_eq!(info.name(), std::any::type_name::<TestResource>());
2844+
// SAFETY: We know that the resource is of type `TestResource`
2845+
unsafe {
2846+
mut_untyped.as_mut().deref_mut::<TestResource>().0 = 43;
2847+
};
2848+
2849+
let (info, mut mut_untyped) = iter.next().unwrap();
2850+
assert_eq!(info.name(), std::any::type_name::<TestResource2>());
2851+
// SAFETY: We know that the resource is of type `TestResource2`
2852+
unsafe {
2853+
mut_untyped.as_mut().deref_mut::<TestResource2>().0 = "Hello, world?".to_string();
2854+
};
2855+
2856+
assert!(iter.next().is_none());
2857+
std::mem::drop(iter);
2858+
2859+
assert_eq!(world.resource::<TestResource>().0, 43);
2860+
assert_eq!(
2861+
world.resource::<TestResource2>().0,
2862+
"Hello, world?".to_string()
2863+
);
2864+
}
2865+
25972866
#[test]
25982867
fn custom_resource_with_layout() {
25992868
static DROP_COUNT: AtomicU32 = AtomicU32::new(0);

0 commit comments

Comments
 (0)