Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion src/entity/entity_set/entity_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ pub(super) enum EntitySetInner<'a, E: Entity> {
Difference(Box<EntitySet<'a, E>>, Box<EntitySet<'a, E>>),
}

impl<'a, E: Entity> Clone for EntitySet<'a, E> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

impl<'a, E: Entity> Clone for EntitySetInner<'a, E> {
fn clone(&self) -> Self {
match self {
Self::Source(source) => Self::Source(source.clone()),
Self::Union(left, right) => Self::Union(left.clone(), right.clone()),
Self::Intersection(sets) => Self::Intersection(sets.clone()),
Self::Difference(left, right) => Self::Difference(left.clone(), right.clone()),
}
}
}

impl<'a, E: Entity> Default for EntitySet<'a, E> {
fn default() -> Self {
Self::empty()
Expand Down Expand Up @@ -315,10 +332,11 @@ mod tests {
use super::*;
use crate::entity::ContextEntitiesExt;
use crate::hashing::IndexSet;
use crate::{define_entity, define_property, Context};
use crate::{define_derived_property, define_entity, define_property, Context};

define_entity!(Person);
define_property!(struct Age(u8), Person);
define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));

fn finite_set(ids: &[usize]) -> IndexSet<EntityId<Person>> {
ids.iter()
Expand Down Expand Up @@ -500,6 +518,28 @@ mod tests {
assert!(!law.contains(EntityId::<Person>::new(4)));
}

#[test]
fn clone_preserves_composite_expression_behavior() {
let a = finite_set(&[1, 2, 3, 4]);
let b = finite_set(&[3, 4, 5]);
let c = finite_set(&[2]);

let set = as_entity_set(&a)
.difference(as_entity_set(&c))
.union(as_entity_set(&b));
let cloned = set.clone();

for value in 0..7 {
let entity_id = EntityId::<Person>::new(value);
assert_eq!(set.contains(entity_id), cloned.contains(entity_id));
}

assert_eq!(
set.into_iter().collect::<Vec<_>>(),
cloned.into_iter().collect::<Vec<_>>()
);
}

#[test]
fn population_zero_is_empty() {
let es = EntitySet::from_source(SourceSet::<Person>::Population(0));
Expand Down Expand Up @@ -535,4 +575,41 @@ mod tests {
.difference(EntitySet::from_source(SourceSet::Entity(EntityId::new(1))));
assert_eq!(composed.try_len(), None);
}

#[test]
fn clone_preserves_unindexed_concrete_property_query_results() {
let mut context = Context::new();
let p1 = context.add_entity((Age(10),)).unwrap();
let p2 = context.add_entity((Age(10),)).unwrap();
let _p3 = context.add_entity((Age(11),)).unwrap();

let set = context.query::<Person, _>((Age(10),));
assert_eq!(set.try_len(), None);
let cloned = set.clone();

let mut iter = set.into_iter();
assert_eq!(iter.next(), Some(p1));
assert_eq!(iter.collect::<Vec<_>>(), vec![p2]);

assert!(cloned.contains(p1));
assert!(cloned.contains(p2));
assert_eq!(cloned.into_iter().collect::<Vec<_>>(), vec![p1, p2]);
}

#[test]
fn clone_preserves_unindexed_derived_property_query_results() {
let mut context = Context::new();
let _p1 = context.add_entity((Age(64),)).unwrap();
let p2 = context.add_entity((Age(65),)).unwrap();
let p3 = context.add_entity((Age(90),)).unwrap();

let set = context.query::<Person, _>((Senior(true),));
assert_eq!(set.try_len(), None);
let cloned = set.clone();

assert!(set.contains(p2));
assert!(set.contains(p3));
assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![p2, p3]);
assert_eq!(cloned.into_iter().collect::<Vec<_>>(), vec![p2, p3]);
}
}
48 changes: 48 additions & 0 deletions src/entity/entity_set/source_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ pub(crate) trait AbstractPropertySource<'a, E: Entity>:
/// Identity of the logical property query represented by this source.
fn id(&self) -> PropertySourceId;

/// Clone this type-erased source, preserving its current cursor state.
fn clone_box(&self) -> BxPropertySource<'a, E>;

/// A test that `entity_id` is contained in the (abstractly
/// defined) set. This operation is very efficient.
fn contains(&self, entity_id: EntityId<E>) -> bool;
Expand Down Expand Up @@ -111,6 +114,18 @@ impl<'a, E: Entity, P: Property<E>> DerivedPropertySource<'a, E, P> {
}
}

impl<'a, E: Entity, P: Property<E>> Clone for DerivedPropertySource<'a, E, P> {
fn clone(&self) -> Self {
Self {
context: self.context,
value: self.value,
next_index: self.next_index,
population_size: self.population_size,
_phantom: PhantomData,
}
}
}

impl<'a, E: Entity, P: Property<E>> AbstractPropertySource<'a, E>
for DerivedPropertySource<'a, E, P>
{
Expand All @@ -125,6 +140,10 @@ impl<'a, E: Entity, P: Property<E>> AbstractPropertySource<'a, E>
P::compute_derived(self.context, entity_id) == self.value
}

fn clone_box(&self) -> BxPropertySource<'a, E> {
Box::new((*self).clone())
}

fn sort_key(&self) -> (usize, u8) {
(self.context.get_entity_count::<E>(), 6)
}
Expand Down Expand Up @@ -189,6 +208,19 @@ impl<'a, E: Entity, P: Property<E>> ConcretePropertySource<'a, E, P> {
}
}

impl<'a, E: Entity, P: Property<E>> Clone for ConcretePropertySource<'a, E, P> {
fn clone(&self) -> Self {
Self {
values: self.values,
value: self.value,
next_index: self.next_index,
is_default_value: self.is_default_value,
population_size: self.population_size,
_phantom: PhantomData,
}
}
}

impl<'a, E: Entity, P: Property<E>> AbstractPropertySource<'a, E>
for ConcretePropertySource<'a, E, P>
{
Expand All @@ -209,6 +241,10 @@ impl<'a, E: Entity, P: Property<E>> AbstractPropertySource<'a, E>
}
}

fn clone_box(&self) -> BxPropertySource<'a, E> {
Box::new((*self).clone())
}

fn sort_key(&self) -> (usize, u8) {
let upper = if !self.is_default_value {
self.values.len()
Expand Down Expand Up @@ -261,6 +297,18 @@ pub(crate) enum SourceSet<'a, E: Entity> {
PropertySet(BxPropertySource<'a, E>),
}

impl<'a, E: Entity> Clone for SourceSet<'a, E> {
fn clone(&self) -> Self {
match self {
Self::Empty => Self::Empty,
Self::Population(population) => Self::Population(*population),
Self::Entity(entity_id) => Self::Entity(*entity_id),
Self::IndexSet(index_set) => Self::IndexSet(index_set),
Self::PropertySet(source) => Self::PropertySet(source.clone_box()),
}
}
}

impl<'a, E: Entity> PartialEq for SourceSet<'a, E> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
Expand Down
Loading