diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs new file mode 100644 index 00000000000000..e5f99ae6ed8cbf --- /dev/null +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -0,0 +1,54 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +// We use usize here because that is the largest `Atomic` we want to require +/// A unique identifier for a [`super::World`]. +pub struct WorldId(usize); + +/// The next [`WorldId`]. +static MAX_WORLD_ID: AtomicUsize = AtomicUsize::new(0); + +impl WorldId { + /// Create a new, unique [`WorldId`] + /// + /// # Panics + /// If [`usize::MAX`] [`WorldId`]s have been created + // This could be kept crate private, but external crates may wish to create some for their tests + pub fn new() -> Self { + let id = MAX_WORLD_ID + // We use `Relaxed` here since this atomic only needs to be consistent with itself + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |val| { + val.checked_add(1) + }) + .expect("More `bevy` `World`s have been created than is supported"); + WorldId(id) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn world_ids_unique() { + let ids = std::iter::repeat_with(WorldId::new) + .take(50) + .collect::>(); + for (i, &id1) in ids.iter().enumerate() { + // For the first element, i is 0 - so skip 1 + for &id2 in ids.iter().skip(i + 1) { + assert_ne!(id1, id2, "WorldIds should not repeat"); + } + } + } + + // We cannot use this test as-is, as it causes other tests to panic due to using the same atomic variable. + // #[test] + // #[should_panic] + // fn panic_on_overflow() { + // MAX_WORLD_ID.store(usize::MAX - 50, Ordering::Relaxed); + // std::iter::repeat_with(WorldId::new) + // .take(500) + // .for_each(|_| ()); + // } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4b855cd35082a6..33633d62a6211b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -22,28 +22,12 @@ use crate::{ use std::{ any::TypeId, fmt, - sync::atomic::{AtomicU32, AtomicUsize, Ordering}, + sync::atomic::{AtomicU32, Ordering}, }; -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -// We use usize here because that is the largest `Atomic` we want to require -pub struct WorldId(usize); - -/// The next [`WorldId`]. This will panic on overflow -static MAX_WORLD_ID: AtomicUsize = AtomicUsize::new(0); - -impl Default for WorldId { - fn default() -> Self { - let id = MAX_WORLD_ID - // We use `Relaxed` here since this atomic only needs to be consistent with itself - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |val| { - val.checked_add(1) - }) - .expect("More `bevy` `World`s have been created than is supported"); - WorldId(id) - } -} +mod identifier; +pub use identifier::WorldId; /// [World] stores and exposes operations on [entities](Entity), [components](Component), /// and their associated metadata. /// Each [Entity] has a set of components. Each component can have up to one instance of each @@ -67,7 +51,7 @@ pub struct World { impl Default for World { fn default() -> Self { Self { - id: Default::default(), + id: WorldId::new(), entities: Default::default(), components: Default::default(), archetypes: Default::default(), @@ -86,12 +70,17 @@ impl Default for World { impl World { /// Creates a new empty [World] + /// # Panics + /// + /// If [`usize::MAX`] [`World`]s have been created. + /// This guarantee allows System Parameters to safely uniquely identify a [`World`], + /// since its [`WorldId`] is unique #[inline] pub fn new() -> World { World::default() } - /// Retrieves this world's unique ID + /// Retrieves this [`World`]'s unique ID #[inline] pub fn id(&self) -> WorldId { self.id