diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 97cdcee0824b7..c3a2dc18f918f 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -125,7 +125,11 @@ derive_more = { version = "1", default-features = false, features = [ ] } nonmax = { version = "0.5", default-features = false } arrayvec = { version = "0.7.4", default-features = false, optional = true } -smallvec = { version = "1", features = ["union", "const_generics"] } +smallvec = { version = "1.13", features = [ + "union", + "const_generics", + "const_new", +] } indexmap = { version = "2.5.0", default-features = false } variadics_please = { version = "1.1", default-features = false } tracing = { version = "0.1", default-features = false, optional = true } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 01e3713ad624d..e6924953c3082 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1,68 +1,31 @@ use crate::component::ComponentId; +use crate::storage::SortedVecSet; use crate::storage::SparseSetIndex; use crate::world::World; use alloc::{format, string::String, vec, vec::Vec}; -use core::{fmt, fmt::Debug, marker::PhantomData}; +use core::{fmt::Debug, marker::PhantomData}; use derive_more::From; use disqualified::ShortName; -use fixedbitset::FixedBitSet; use thiserror::Error; -/// A wrapper struct to make Debug representations of [`FixedBitSet`] easier -/// to read, when used to store [`SparseSetIndex`]. -/// -/// Instead of the raw integer representation of the `FixedBitSet`, the list of -/// `T` valid for [`SparseSetIndex`] is shown. -/// -/// Normal `FixedBitSet` `Debug` output: -/// ```text -/// read_and_writes: FixedBitSet { data: [ 160 ], length: 8 } -/// ``` -/// -/// Which, unless you are a computer, doesn't help much understand what's in -/// the set. With `FormattedBitSet`, we convert the present set entries into -/// what they stand for, it is much clearer what is going on: -/// ```text -/// read_and_writes: [ ComponentId(5), ComponentId(7) ] -/// ``` -struct FormattedBitSet<'a, T: SparseSetIndex> { - bit_set: &'a FixedBitSet, - _marker: PhantomData, -} - -impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { - fn new(bit_set: &'a FixedBitSet) -> Self { - Self { - bit_set, - _marker: PhantomData, - } - } -} - -impl<'a, T: SparseSetIndex + Debug> Debug for FormattedBitSet<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list() - .entries(self.bit_set.ones().map(T::get_sparse_set_index)) - .finish() - } -} +const ACCESS_SMALL_VEC_SIZE: usize = 8; /// Tracks read and write access to specific elements in a collection. /// /// Used internally to ensure soundness during system initialization and execution. /// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions. -#[derive(Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub struct Access { /// All accessed components, or forbidden components if /// `Self::component_read_and_writes_inverted` is set. - component_read_and_writes: FixedBitSet, + component_read_and_writes: SortedVecSet, /// All exclusively-accessed components, or components that may not be /// exclusively accessed if `Self::component_writes_inverted` is set. - component_writes: FixedBitSet, + component_writes: SortedVecSet, /// All accessed resources. - resource_read_and_writes: FixedBitSet, + resource_read_and_writes: SortedVecSet, /// The exclusively-accessed resources. - resource_writes: FixedBitSet, + resource_writes: SortedVecSet, /// Is `true` if this component can read all components *except* those /// present in `Self::component_read_and_writes`. component_read_and_writes_inverted: bool, @@ -76,7 +39,7 @@ pub struct Access { /// If this is true, then `reads_all` must also be true. writes_all_resources: bool, // Components that are not accessed, but whose presence in an archetype affect query results. - archetypal: FixedBitSet, + archetypal: SortedVecSet, marker: PhantomData, } @@ -111,7 +74,7 @@ impl Clone for Access { self.archetypal.clone_from(&source.archetypal); } } - +/* impl Debug for Access { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Access") @@ -142,7 +105,7 @@ impl Debug for Access { .finish() } } - +*/ impl Default for Access { fn default() -> Self { Self::new() @@ -157,18 +120,18 @@ impl Access { writes_all_resources: false, component_read_and_writes_inverted: false, component_writes_inverted: false, - component_read_and_writes: FixedBitSet::new(), - component_writes: FixedBitSet::new(), - resource_read_and_writes: FixedBitSet::new(), - resource_writes: FixedBitSet::new(), - archetypal: FixedBitSet::new(), + component_read_and_writes: SortedVecSet::::new(), + component_writes: SortedVecSet::::new(), + resource_read_and_writes: SortedVecSet::::new(), + resource_writes: SortedVecSet::::new(), + archetypal: SortedVecSet::::new(), marker: PhantomData, } } fn add_component_sparse_set_index_read(&mut self, index: usize) { if !self.component_read_and_writes_inverted { - self.component_read_and_writes.grow_and_insert(index); + self.component_read_and_writes.insert(index); } else if index < self.component_read_and_writes.len() { self.component_read_and_writes.remove(index); } @@ -176,7 +139,7 @@ impl Access { fn add_component_sparse_set_index_write(&mut self, index: usize) { if !self.component_writes_inverted { - self.component_writes.grow_and_insert(index); + self.component_writes.insert(index); } else if index < self.component_writes.len() { self.component_writes.remove(index); } @@ -198,20 +161,19 @@ impl Access { /// Adds access to the resource given by `index`. pub fn add_resource_read(&mut self, index: T) { self.resource_read_and_writes - .grow_and_insert(index.sparse_set_index()); + .insert(index.sparse_set_index()); } /// Adds exclusive access to the resource given by `index`. pub fn add_resource_write(&mut self, index: T) { self.resource_read_and_writes - .grow_and_insert(index.sparse_set_index()); - self.resource_writes - .grow_and_insert(index.sparse_set_index()); + .insert(index.sparse_set_index()); + self.resource_writes.insert(index.sparse_set_index()); } fn remove_component_sparse_set_index_read(&mut self, index: usize) { if self.component_read_and_writes_inverted { - self.component_read_and_writes.grow_and_insert(index); + self.component_read_and_writes.insert(index); } else if index < self.component_read_and_writes.len() { self.component_read_and_writes.remove(index); } @@ -219,7 +181,7 @@ impl Access { fn remove_component_sparse_set_index_write(&mut self, index: usize) { if self.component_writes_inverted { - self.component_writes.grow_and_insert(index); + self.component_writes.insert(index); } else if index < self.component_writes.len() { self.component_writes.remove(index); } @@ -261,7 +223,7 @@ impl Access { /// /// [`Has`]: crate::query::Has pub fn add_archetypal(&mut self, index: T) { - self.archetypal.grow_and_insert(index.sparse_set_index()); + self.archetypal.insert(index.sparse_set_index()); } /// Returns `true` if this can access the component given by `index`. @@ -274,7 +236,7 @@ impl Access { /// Returns `true` if this can access any component. pub fn has_any_component_read(&self) -> bool { - self.component_read_and_writes_inverted || !self.component_read_and_writes.is_clear() + self.component_read_and_writes_inverted || !self.component_read_and_writes.is_empty() } /// Returns `true` if this can exclusively access the component given by `index`. @@ -284,7 +246,7 @@ impl Access { /// Returns `true` if this accesses any component mutably. pub fn has_any_component_write(&self) -> bool { - self.component_writes_inverted || !self.component_writes.is_clear() + self.component_writes_inverted || !self.component_writes.is_empty() } /// Returns `true` if this can access the resource given by `index`. @@ -297,7 +259,7 @@ impl Access { /// Returns `true` if this can access any resource. pub fn has_any_resource_read(&self) -> bool { - self.reads_all_resources || !self.resource_read_and_writes.is_clear() + self.reads_all_resources || !self.resource_read_and_writes.is_empty() } /// Returns `true` if this can exclusively access the resource given by `index`. @@ -307,7 +269,7 @@ impl Access { /// Returns `true` if this accesses any resource mutably. pub fn has_any_resource_write(&self) -> bool { - self.writes_all_resources || !self.resource_writes.is_clear() + self.writes_all_resources || !self.resource_writes.is_empty() } /// Returns `true` if this accesses any resources or components. @@ -377,13 +339,13 @@ impl Access { /// Returns `true` if this has access to all components (i.e. `EntityRef`). #[inline] pub fn has_read_all_components(&self) -> bool { - self.component_read_and_writes_inverted && self.component_read_and_writes.is_clear() + self.component_read_and_writes_inverted && self.component_read_and_writes.is_empty() } /// Returns `true` if this has write access to all components (i.e. `EntityMut`). #[inline] pub fn has_write_all_components(&self) -> bool { - self.component_writes_inverted && self.component_writes.is_clear() + self.component_writes_inverted && self.component_writes.is_empty() } /// Returns `true` if this has access to all resources (i.e. `EntityRef`). @@ -448,15 +410,8 @@ impl Access { .difference_with(&other.component_read_and_writes); } (false, true) => { - // We have to grow here because the new bits are going to get flipped to 1. - self.component_read_and_writes.grow( - self.component_read_and_writes - .len() - .max(other.component_read_and_writes.len()), - ); - self.component_read_and_writes.toggle_range(..); self.component_read_and_writes - .intersect_with(&other.component_read_and_writes); + .reverse_difference_with(&other.component_read_and_writes); } (false, false) => { self.component_read_and_writes @@ -477,15 +432,8 @@ impl Access { .difference_with(&other.component_writes); } (false, true) => { - // We have to grow here because the new bits are going to get flipped to 1. - self.component_writes.grow( - self.component_writes - .len() - .max(other.component_writes.len()), - ); - self.component_writes.toggle_range(..); self.component_writes - .intersect_with(&other.component_writes); + .reverse_difference_with(&other.component_writes); } (false, false) => { self.component_writes.union_with(&other.component_writes); @@ -666,7 +614,7 @@ impl Access { } fn get_component_conflicts(&self, other: &Access) -> AccessConflicts { - let mut conflicts = FixedBitSet::new(); + let mut conflicts = SortedVecSet::::new(); // We have a conflict if we write and they read or write, or if they // write and we read or write. @@ -691,12 +639,12 @@ impl Access { ] { // There's no way that I can see to do this without a temporary. // Neither CNF nor DNF allows us to avoid one. - let temp_conflicts: FixedBitSet = + let temp_conflicts: SortedVecSet = match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { (true, true) => return AccessConflicts::All, - (false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(), - (true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(), - (false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(), + (false, true) => lhs_writes.difference(rhs_reads_and_writes).into(), + (true, false) => rhs_reads_and_writes.difference(lhs_writes).into(), + (false, false) => lhs_writes.intersection(rhs_reads_and_writes).into(), }; conflicts.union_with(&temp_conflicts); } @@ -715,38 +663,44 @@ impl Access { if other.writes_all_resources { return AccessConflicts::All; } - conflicts.extend(other.resource_writes.ones()); + conflicts.union_with(&other.resource_writes); } if other.reads_all_resources { if self.writes_all_resources { return AccessConflicts::All; } - conflicts.extend(self.resource_writes.ones()); + conflicts.union_with(&self.resource_writes); } if self.writes_all_resources { - conflicts.extend(other.resource_read_and_writes.ones()); + conflicts.union_with(&other.resource_read_and_writes); } if other.writes_all_resources { - conflicts.extend(self.resource_read_and_writes.ones()); + conflicts.union_with(&self.resource_read_and_writes); } - conflicts.extend( - self.resource_writes - .intersection(&other.resource_read_and_writes), + conflicts.union_with( + &self + .resource_writes + .intersection(&other.resource_read_and_writes) + .into(), ); - conflicts.extend( - self.resource_read_and_writes - .intersection(&other.resource_writes), + + conflicts.union_with( + &self + .resource_read_and_writes + .intersection(&other.resource_writes) + .into(), ); + AccessConflicts::Individual(conflicts) } /// Returns the indices of the resources this has access to. pub fn resource_reads_and_writes(&self) -> impl Iterator + '_ { self.resource_read_and_writes - .ones() + .iter() .map(T::get_sparse_set_index) } @@ -759,7 +713,7 @@ impl Access { /// Returns the indices of the resources this has exclusive access to. pub fn resource_writes(&self) -> impl Iterator + '_ { - self.resource_writes.ones().map(T::get_sparse_set_index) + self.resource_writes.iter().map(T::get_sparse_set_index) } /// Returns the indices of the components that this has an archetypal access to. @@ -771,7 +725,7 @@ impl Access { /// /// [`Has`]: crate::query::Has pub fn archetypal(&self) -> impl Iterator + '_ { - self.archetypal.ones().map(T::get_sparse_set_index) + self.archetypal.iter().map(T::get_sparse_set_index) } /// Returns an iterator over the component IDs and their [`ComponentAccessKind`]. @@ -815,7 +769,7 @@ impl Access { }); } - let reads_and_writes = self.component_read_and_writes.ones().map(|index| { + let reads_and_writes = self.component_read_and_writes.iter().map(|index| { let sparse_index = T::get_sparse_set_index(index); if self.component_writes.contains(index) { @@ -827,7 +781,7 @@ impl Access { let archetypal = self .archetypal - .ones() + .iter() .filter(|&index| { !self.component_writes.contains(index) && !self.component_read_and_writes.contains(index) @@ -893,7 +847,7 @@ impl ComponentAccessKind { #[derive(Debug, Eq, PartialEq)] pub struct FilteredAccess { pub(crate) access: Access, - pub(crate) required: FixedBitSet, + pub(crate) required: SortedVecSet, // An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With, With)>`. // Filters like `(With, Or<(With, Without)>` are expanded into `Or<((With, With), (With, Without))>`. pub(crate) filter_sets: Vec>, @@ -936,7 +890,7 @@ pub enum AccessConflicts { /// Conflict is for all indices All, /// There is a conflict for a subset of indices - Individual(FixedBitSet), + Individual(SortedVecSet), } impl AccessConflicts { @@ -946,7 +900,7 @@ impl AccessConflicts { *s = AccessConflicts::All; } (AccessConflicts::Individual(this), AccessConflicts::Individual(other)) => { - this.extend(other.ones()); + this.union_with(other); } _ => {} } @@ -963,7 +917,7 @@ impl AccessConflicts { match self { AccessConflicts::All => String::new(), AccessConflicts::Individual(indices) => indices - .ones() + .iter() .map(|index| { format!( "{}", @@ -982,13 +936,17 @@ impl AccessConflicts { /// An [`AccessConflicts`] which represents the absence of any conflict pub(crate) fn empty() -> Self { - Self::Individual(FixedBitSet::new()) + Self::Individual(SortedVecSet::::new()) } } impl From> for AccessConflicts { fn from(value: Vec) -> Self { - Self::Individual(value.iter().map(T::sparse_set_index).collect()) + let mut conflicts = SortedVecSet::new(); + for index in value.iter().map(|a| T::sparse_set_index(a)) { + conflicts.insert(index); + } + Self::Individual(conflicts) } } @@ -998,7 +956,7 @@ impl FilteredAccess { pub fn matches_everything() -> Self { Self { access: Access::default(), - required: FixedBitSet::default(), + required: SortedVecSet::::default(), filter_sets: vec![AccessFilters::default()], } } @@ -1008,7 +966,7 @@ impl FilteredAccess { pub fn matches_nothing() -> Self { Self { access: Access::default(), - required: FixedBitSet::default(), + required: SortedVecSet::::default(), filter_sets: Vec::new(), } } @@ -1050,7 +1008,7 @@ impl FilteredAccess { } fn add_required(&mut self, index: T) { - self.required.grow_and_insert(index.sparse_set_index()); + self.required.insert(index.sparse_set_index()); } /// Adds a `With` filter: corresponds to a conjunction (AND) operation. @@ -1059,7 +1017,7 @@ impl FilteredAccess { /// Adding `AND With` via this method transforms it into the equivalent of `Or<((With, With), (With, With))>`. pub fn and_with(&mut self, index: T) { for filter in &mut self.filter_sets { - filter.with.grow_and_insert(index.sparse_set_index()); + filter.with.insert(index.sparse_set_index()); } } @@ -1069,7 +1027,7 @@ impl FilteredAccess { /// Adding `AND Without` via this method transforms it into the equivalent of `Or<((With, Without), (With, Without))>`. pub fn and_without(&mut self, index: T) { for filter in &mut self.filter_sets { - filter.without.grow_and_insert(index.sparse_set_index()); + filter.without.insert(index.sparse_set_index()); } } @@ -1185,14 +1143,14 @@ impl FilteredAccess { pub fn with_filters(&self) -> impl Iterator + '_ { self.filter_sets .iter() - .flat_map(|f| f.with.ones().map(T::get_sparse_set_index)) + .flat_map(|f| f.with.iter().map(T::get_sparse_set_index)) } /// Returns the indices of the elements that this access filters out. pub fn without_filters(&self) -> impl Iterator + '_ { self.filter_sets .iter() - .flat_map(|f| f.without.ones().map(T::get_sparse_set_index)) + .flat_map(|f| f.without.iter().map(T::get_sparse_set_index)) } /// Returns true if the index is used by this `FilteredAccess` in any way @@ -1206,10 +1164,10 @@ impl FilteredAccess { } } -#[derive(Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub(crate) struct AccessFilters { - pub(crate) with: FixedBitSet, - pub(crate) without: FixedBitSet, + pub(crate) with: SortedVecSet, + pub(crate) without: SortedVecSet, _index_type: PhantomData, } @@ -1229,20 +1187,11 @@ impl Clone for AccessFilters { } } -impl Debug for AccessFilters { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AccessFilters") - .field("with", &FormattedBitSet::::new(&self.with)) - .field("without", &FormattedBitSet::::new(&self.without)) - .finish() - } -} - impl Default for AccessFilters { fn default() -> Self { Self { - with: FixedBitSet::default(), - without: FixedBitSet::default(), + with: SortedVecSet::::default(), + without: SortedVecSet::::default(), _index_type: PhantomData, } } @@ -1424,9 +1373,9 @@ mod tests { access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess, FilteredAccessSet, UnboundedAccessError, }; + use crate::storage::SortedVecSet; use alloc::{vec, vec::Vec}; use core::marker::PhantomData; - use fixedbitset::FixedBitSet; fn create_sample_access() -> Access { let mut access = Access::::default(); @@ -1454,8 +1403,8 @@ mod tests { fn create_sample_access_filters() -> AccessFilters { let mut access_filters = AccessFilters::::default(); - access_filters.with.grow_and_insert(3); - access_filters.without.grow_and_insert(5); + access_filters.with.insert(3); + access_filters.without.insert(5); access_filters } @@ -1528,8 +1477,8 @@ mod tests { let original: AccessFilters = create_sample_access_filters(); let mut cloned = AccessFilters::::default(); - cloned.with.grow_and_insert(1); - cloned.without.grow_and_insert(2); + cloned.with.insert(1); + cloned.without.insert(2); cloned.clone_from(&original); @@ -1683,13 +1632,13 @@ mod tests { // The resulted access is expected to represent `Or<((With, With, With), (With, With, With, Without))>`. expected.filter_sets = vec![ AccessFilters { - with: FixedBitSet::with_capacity_and_blocks(3, [0b111]), - without: FixedBitSet::default(), + with: SortedVecSet::from_vec(vec![0, 1, 2]), + without: SortedVecSet::new(), _index_type: PhantomData, }, AccessFilters { - with: FixedBitSet::with_capacity_and_blocks(4, [0b1011]), - without: FixedBitSet::with_capacity_and_blocks(5, [0b10000]), + with: SortedVecSet::from_vec(vec![0, 1, 3]), + without: SortedVecSet::from_vec(vec![4]), _index_type: PhantomData, }, ]; diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e9a00f4646e1f..d5c51e80d9185 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -551,7 +551,7 @@ impl QueryState { let potential_archetypes = self .component_access .required - .ones() + .iter() .filter_map(|idx| { let component_id = ComponentId::get_sparse_set_index(idx); world @@ -663,11 +663,11 @@ impl QueryState { pub fn matches_component_set(&self, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { self.component_access.filter_sets.iter().any(|set| { set.with - .ones() + .iter() .all(|index| set_contains_id(ComponentId::get_sparse_set_index(index))) && set .without - .ones() + .iter() .all(|index| !set_contains_id(ComponentId::get_sparse_set_index(index))) }) } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 584e621bf8a39..ce0f0adf57bc2 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1376,7 +1376,7 @@ impl ScheduleGraph { match access_a.get_conflicts(access_b) { AccessConflicts::Individual(conflicts) => { let conflicts: Vec<_> = conflicts - .ones() + .iter() .map(ComponentId::get_sparse_set_index) .filter(|id| !ignored_ambiguities.contains(id)) .collect(); diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2a5a5f184e649..0584b1d361b11 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -23,11 +23,13 @@ mod blob_array; mod blob_vec; mod resource; +mod sorted_vec_set; mod sparse_set; mod table; mod thin_array_ptr; pub use resource::*; +pub use sorted_vec_set::*; pub use sparse_set::*; pub use table::*; diff --git a/crates/bevy_ecs/src/storage/sorted_vec_set.rs b/crates/bevy_ecs/src/storage/sorted_vec_set.rs new file mode 100644 index 0000000000000..95060213b01b7 --- /dev/null +++ b/crates/bevy_ecs/src/storage/sorted_vec_set.rs @@ -0,0 +1,318 @@ +use bevy_platform::prelude::Vec; +use core::cmp::Ordering; +use smallvec::SmallVec; + +/// Stores a sorted list of indices with quick implementation for union, difference, intersection. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SortedVecSet(SmallVec<[usize; N]>); + +impl IntoIterator for SortedVecSet { + type Item = usize; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Default for SortedVecSet { + fn default() -> Self { + Self::new() + } +} + +impl SortedVecSet { + /// Construct an empty vector + pub(crate) const fn new() -> Self { + Self(SmallVec::new_const()) + } + + /// Construct a new `SortedSmallVec` from a `Vec`. + /// + /// Elements are copied and put in a sorted order if the original `Vec` isn't ordered. + /// Duplicates are removed. + #[cfg_attr(not(test), expect(dead_code, reason = "only used in tests"))] + pub(crate) fn from_vec(vec: Vec) -> Self { + let mut small_vec = SmallVec::from_vec(vec); + small_vec.sort(); + small_vec.dedup(); + Self(small_vec) + } + + /// Returns an iterator yielding all `usize`s from start to end. + pub(crate) fn iter(&self) -> impl Iterator + '_ { + self.0.iter().copied() + } + + /// Insert the value if it's not already present in the vector. + /// Maintains a sorted order. + pub(crate) fn insert(&mut self, index: usize) { + match self.0.binary_search(&index) { + // element already present in the vector + Ok(_) => {} + Err(pos) => { + self.0.insert(pos, index); + } + } + } + + /// Removes a value if it's present in the vector + pub(crate) fn remove(&mut self, index: usize) { + if let Ok(pos) = self.0.binary_search(&index) { + self.0.remove(pos); + } + } + + /// Returns true if the vector contains the value. + pub(crate) fn contains(&self, index: usize) -> bool { + self.0.binary_search(&index).is_ok() + } + + /// Returns true if the vector is empty. + pub(crate) fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Empties the contents of the vector + pub(crate) fn clear(&mut self) { + self.0.clear(); + } + + /// Returns the number of elements in the vector. + pub(crate) fn len(&self) -> usize { + self.0.len() + } + + /// In-place difference of two `SortedVecSet`s. + /// + /// The difference of `a` and `b` is the elements of `a` which are not in `b`. + pub(crate) fn difference_with(&mut self, other: &Self) { + let mut j = 0; + self.0.retain(|current| { + // Advance past any smaller elements in other + while j < other.len() && other.0[j] < *current { + j += 1; + } + // It's only in the difference if it's not in other, + // and this is the only place in other it could be + j == other.len() || other.0[j] != *current + }); + } + + /// In-place difference between two `SortedVecSet`s. + /// + /// The reverse difference between `a` and `b` is the elements of `b` which are not in `a`. + /// It is equivalent to `A = B.difference(A).into()`. + pub(crate) fn reverse_difference_with(&mut self, other: &Self) { + let mut temp = other.clone(); + temp.difference_with(self); + self.0 = temp.0; + } + + /// In-place intersection between two `SortedVecSet`s. + pub(crate) fn intersect_with(&mut self, other: &Self) { + let mut j = 0; + self.0.retain(|current| { + // Advance past any smaller elements in other + while j < other.len() && other.0[j] < *current { + j += 1; + } + // It's only in the intersection if it's in other, + // and this is the only place in other it could be + j < other.len() && other.0[j] == *current + }); + } + + /// In-place union between two `SortedVecSet`s. + pub(crate) fn union_with(&mut self, other: &Self) { + let mut i = 0; + let mut j = 0; + while i < self.len() && j < other.len() { + match self.0[i].cmp(&other.0[j]) { + Ordering::Less => i += 1, + Ordering::Greater => { + self.0.insert(i, other.0[j]); + j += 1; + } + Ordering::Equal => { + i += 1; + j += 1; + } + } + } + while j < other.len() { + self.0.push(other.0[j]); + j += 1; + } + } + + /// Returns the elements that are in both `self` and `other`. + pub(crate) fn intersection<'a>(&'a self, other: &'a Self) -> Intersection<'a, N> { + Intersection { + this: self, + other, + i: 0, + j: 0, + } + } + + /// Return the elements that are in `self` but not in `other`. + pub(crate) fn difference<'a>(&'a self, other: &'a Self) -> Difference<'a, N> { + Difference { + this: self, + other, + i: 0, + j: 0, + } + } + + /// Returns true if the two vectors have no common elements. + pub(crate) fn is_disjoint(&self, other: &Self) -> bool { + self.intersection(other).next().is_none() + } + + /// Returns true if all the elements in `self` are also in `other`. + pub(crate) fn is_subset(&self, other: &Self) -> bool { + self.difference(other).next().is_none() + } +} + +/// Intersection between `this` and `other` sorted vectors. +pub(crate) struct Intersection<'a, const N: usize> { + this: &'a SortedVecSet, + other: &'a SortedVecSet, + i: usize, + j: usize, +} + +impl<'a, const N: usize> Iterator for Intersection<'a, N> { + type Item = usize; + + // We assume that both self and other are sorted and contain no duplicates + // Returns items in sorted order without duplicates + fn next(&mut self) -> Option { + while self.i < self.this.len() && self.j < self.other.len() { + let val_a = self.this.0[self.i]; + let val_b = self.other.0[self.j]; + match val_a.cmp(&val_b) { + Ordering::Equal => { + self.i += 1; + self.j += 1; + return Some(val_a); + } + Ordering::Less => { + self.i += 1; + } + Ordering::Greater => { + self.j += 1; + } + } + } + None + } +} + +impl<'a, const N: usize> From> for SortedVecSet { + fn from(intersection: Intersection<'a, N>) -> Self { + SortedVecSet(SmallVec::from_iter(intersection)) + } +} + +/// Difference between `this` and `other` sorted vector sets. this - other. +pub(crate) struct Difference<'a, const N: usize> { + this: &'a SortedVecSet, + other: &'a SortedVecSet, + i: usize, + j: usize, +} + +impl<'a, const N: usize> Iterator for Difference<'a, N> { + type Item = usize; + + // We assume that both self and other are sorted and contain no duplicates + // Returns items in sorted order without duplicates + fn next(&mut self) -> Option { + while self.i < self.this.len() && self.j < self.other.len() { + let val_a = self.this.0[self.i]; + let val_b = self.other.0[self.j]; + match val_a.cmp(&val_b) { + Ordering::Equal => { + self.i += 1; + self.j += 1; + } + Ordering::Less => { + self.i += 1; + return Some(val_a); + } + Ordering::Greater => { + self.j += 1; + } + } + } + if self.i < self.this.len() { + let val_a = self.this.0[self.i]; + self.i += 1; + return Some(val_a); + } + None + } +} + +impl<'a, const N: usize> From> for SortedVecSet { + fn from(difference: Difference<'a, N>) -> Self { + SortedVecSet(SmallVec::from_iter(difference)) + } +} + +#[cfg(test)] +mod tests { + use crate::storage::SortedVecSet; + use bevy_platform::prelude::{vec, Vec}; + + #[test] + fn insert_and_remove() { + let mut set = SortedVecSet::<8>::new(); + set.insert(1); + set.insert(3); + set.insert(3); + assert_eq!(set, SortedVecSet::<8>::from_vec(vec![1, 3])); + set.insert(5); + set.insert(2); + assert_eq!(set, SortedVecSet::<8>::from_vec(vec![1, 2, 3, 5])); + set.remove(4); + set.remove(3); + set.remove(3); + assert_eq!(set, SortedVecSet::<8>::from_vec(vec![1, 2, 5])); + } + + #[test] + fn set_operations() { + let set_1 = SortedVecSet::<8>::from_vec(vec![1, 2, 5, 6]); + let set_2 = SortedVecSet::<8>::from_vec(vec![2, 3, 6, 7, 8, 9]); + + let difference: Vec = set_1.difference(&set_2).collect(); + assert_eq!(difference, vec![1, 5]); + let difference: Vec = set_2.difference(&set_1).collect(); + assert_eq!(difference, vec![3, 7, 8, 9]); + + let intersection: Vec = set_1.intersection(&set_2).collect(); + assert_eq!(intersection, vec![2, 6]); + let intersection: Vec = set_2.intersection(&set_1).collect(); + assert_eq!(intersection, vec![2, 6]); + + let mut set_3 = SortedVecSet::<8>::from_vec(vec![3, 4, 5, 6]); + set_3.difference_with(&SortedVecSet::<8>::from_vec(vec![4, 5, 8, 9])); + assert_eq!(set_3, SortedVecSet::<8>::from_vec(vec![3, 6])); + + let mut set_4 = SortedVecSet::<8>::from_vec(vec![2, 5, 8, 9]); + set_4.intersect_with(&SortedVecSet::<8>::from_vec(vec![2, 3, 4, 8, 9])); + assert_eq!(set_4, SortedVecSet::<8>::from_vec(vec![2, 8, 9])); + + let mut set_5 = SortedVecSet::<8>::from_vec(vec![2, 7, 9, 10, 11]); + set_5.union_with(&SortedVecSet::<8>::from_vec(vec![3, 4, 9, 11])); + assert_eq!( + set_5, + SortedVecSet::<8>::from_vec(vec![2, 3, 4, 7, 9, 10, 11]) + ); + } +}