mirror of
https://github.com/bevyengine/bevy
synced 2024-12-22 19:13:08 +00:00
0c98f9a225
# Objective Fix #7584. ## Solution Add an abstraction for creating custom system combinators with minimal boilerplate. Use this to implement AND/OR combinators. Use this to simplify the implementation of `PipeSystem`. ## Example Feel free to bikeshed on the syntax. I chose the names `and_then`/`or_else` to emphasize the fact that these short-circuit, while I chose method syntax to empasize that the arguments are *not* treated equally. ```rust app.add_systems(( my_system.run_if(resource_exists::<R>().and_then(resource_equals(R(0)))), our_system.run_if(resource_exists::<R>().or_else(resource_exists::<S>())), )); ``` --- ## Todo - [ ] Decide on a syntax - [x] Write docs - [x] Write tests ## Changelog + Added the extension methods `.and_then(...)` and `.or_else(...)` to run conditions, which allows combining run conditions with short-circuiting behavior. + Added the trait `Combine`, which can be used with the new `CombinatorSystem` to create system combinators with custom behavior.
1741 lines
55 KiB
Rust
1741 lines
55 KiB
Rust
#![warn(clippy::undocumented_unsafe_blocks)]
|
|
#![doc = include_str!("../README.md")]
|
|
|
|
#[cfg(target_pointer_width = "16")]
|
|
compile_error!("bevy_ecs cannot safely compile for a 16-bit platform.");
|
|
|
|
pub mod archetype;
|
|
pub mod bundle;
|
|
pub mod change_detection;
|
|
pub mod component;
|
|
pub mod entity;
|
|
pub mod event;
|
|
pub mod query;
|
|
#[cfg(feature = "bevy_reflect")]
|
|
pub mod reflect;
|
|
pub mod removal_detection;
|
|
pub mod schedule;
|
|
pub mod storage;
|
|
pub mod system;
|
|
pub mod world;
|
|
|
|
use std::any::TypeId;
|
|
|
|
pub use bevy_ptr as ptr;
|
|
|
|
/// Most commonly used re-exported types.
|
|
pub mod prelude {
|
|
#[doc(hidden)]
|
|
#[allow(deprecated)]
|
|
pub use crate::query::ChangeTrackers;
|
|
#[doc(hidden)]
|
|
#[cfg(feature = "bevy_reflect")]
|
|
pub use crate::reflect::{ReflectComponent, ReflectResource};
|
|
#[doc(hidden)]
|
|
pub use crate::{
|
|
bundle::Bundle,
|
|
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
|
|
component::Component,
|
|
entity::Entity,
|
|
event::{Event, EventReader, EventWriter, Events},
|
|
query::{Added, AnyOf, Changed, Or, QueryState, With, Without},
|
|
removal_detection::RemovedComponents,
|
|
schedule::{
|
|
apply_state_transition, apply_system_buffers, common_conditions::*, Condition,
|
|
IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig,
|
|
IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, Schedules, State,
|
|
States, SystemSet,
|
|
},
|
|
system::{
|
|
adapter as system_adapter,
|
|
adapter::{dbg, error, ignore, info, unwrap, warn},
|
|
Commands, Deferred, In, IntoPipeSystem, IntoSystem, Local, NonSend, NonSendMut,
|
|
ParallelCommands, ParamSet, Query, Res, ResMut, Resource, System, SystemParamFunction,
|
|
},
|
|
world::{FromWorld, World},
|
|
};
|
|
}
|
|
|
|
pub use bevy_utils::all_tuples;
|
|
|
|
/// A specialized hashmap type with Key of `TypeId`
|
|
type TypeIdMap<V> = rustc_hash::FxHashMap<TypeId, V>;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate as bevy_ecs;
|
|
use crate::prelude::Or;
|
|
use crate::{
|
|
bundle::Bundle,
|
|
change_detection::Ref,
|
|
component::{Component, ComponentId},
|
|
entity::Entity,
|
|
query::{Added, Changed, FilteredAccess, ReadOnlyWorldQuery, With, Without},
|
|
system::Resource,
|
|
world::{Mut, World},
|
|
};
|
|
use bevy_tasks::{ComputeTaskPool, TaskPool};
|
|
use std::{
|
|
any::TypeId,
|
|
marker::PhantomData,
|
|
sync::{
|
|
atomic::{AtomicUsize, Ordering},
|
|
Arc, Mutex,
|
|
},
|
|
};
|
|
|
|
#[derive(Component, Resource, Debug, PartialEq, Eq, Clone, Copy)]
|
|
struct A(usize);
|
|
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)]
|
|
struct B(usize);
|
|
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)]
|
|
struct C;
|
|
|
|
#[derive(Default)]
|
|
struct NonSendA(usize, PhantomData<*mut ()>);
|
|
|
|
#[derive(Component, Clone, Debug)]
|
|
struct DropCk(Arc<AtomicUsize>);
|
|
impl DropCk {
|
|
fn new_pair() -> (Self, Arc<AtomicUsize>) {
|
|
let atomic = Arc::new(AtomicUsize::new(0));
|
|
(DropCk(atomic.clone()), atomic)
|
|
}
|
|
}
|
|
|
|
impl Drop for DropCk {
|
|
fn drop(&mut self) {
|
|
self.0.as_ref().fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
}
|
|
|
|
#[derive(Component, Clone, Debug)]
|
|
#[component(storage = "SparseSet")]
|
|
struct DropCkSparse(DropCk);
|
|
|
|
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
|
|
#[component(storage = "Table")]
|
|
struct TableStored(&'static str);
|
|
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
|
|
#[component(storage = "SparseSet")]
|
|
struct SparseStored(u32);
|
|
|
|
#[test]
|
|
fn random_access() {
|
|
let mut world = World::new();
|
|
|
|
let e = world.spawn((TableStored("abc"), SparseStored(123))).id();
|
|
let f = world
|
|
.spawn((TableStored("def"), SparseStored(456), A(1)))
|
|
.id();
|
|
assert_eq!(world.get::<TableStored>(e).unwrap().0, "abc");
|
|
assert_eq!(world.get::<SparseStored>(e).unwrap().0, 123);
|
|
assert_eq!(world.get::<TableStored>(f).unwrap().0, "def");
|
|
assert_eq!(world.get::<SparseStored>(f).unwrap().0, 456);
|
|
|
|
// test archetype get_mut()
|
|
world.get_mut::<TableStored>(e).unwrap().0 = "xyz";
|
|
assert_eq!(world.get::<TableStored>(e).unwrap().0, "xyz");
|
|
|
|
// test sparse set get_mut()
|
|
world.get_mut::<SparseStored>(f).unwrap().0 = 42;
|
|
assert_eq!(world.get::<SparseStored>(f).unwrap().0, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn bundle_derive() {
|
|
let mut world = World::new();
|
|
|
|
#[derive(Bundle, PartialEq, Debug)]
|
|
struct FooBundle {
|
|
x: TableStored,
|
|
y: SparseStored,
|
|
}
|
|
let mut ids = Vec::new();
|
|
<FooBundle as Bundle>::component_ids(
|
|
&mut world.components,
|
|
&mut world.storages,
|
|
&mut |id| {
|
|
ids.push(id);
|
|
},
|
|
);
|
|
|
|
assert_eq!(
|
|
ids,
|
|
&[
|
|
world.init_component::<TableStored>(),
|
|
world.init_component::<SparseStored>(),
|
|
]
|
|
);
|
|
|
|
let e1 = world
|
|
.spawn(FooBundle {
|
|
x: TableStored("abc"),
|
|
y: SparseStored(123),
|
|
})
|
|
.id();
|
|
let e2 = world
|
|
.spawn((TableStored("def"), SparseStored(456), A(1)))
|
|
.id();
|
|
assert_eq!(world.get::<TableStored>(e1).unwrap().0, "abc");
|
|
assert_eq!(world.get::<SparseStored>(e1).unwrap().0, 123);
|
|
assert_eq!(world.get::<TableStored>(e2).unwrap().0, "def");
|
|
assert_eq!(world.get::<SparseStored>(e2).unwrap().0, 456);
|
|
|
|
// test archetype get_mut()
|
|
world.get_mut::<TableStored>(e1).unwrap().0 = "xyz";
|
|
assert_eq!(world.get::<TableStored>(e1).unwrap().0, "xyz");
|
|
|
|
// test sparse set get_mut()
|
|
world.get_mut::<SparseStored>(e2).unwrap().0 = 42;
|
|
assert_eq!(world.get::<SparseStored>(e2).unwrap().0, 42);
|
|
|
|
assert_eq!(
|
|
world.entity_mut(e1).remove::<FooBundle>().unwrap(),
|
|
FooBundle {
|
|
x: TableStored("xyz"),
|
|
y: SparseStored(123),
|
|
}
|
|
);
|
|
|
|
#[derive(Bundle, PartialEq, Debug)]
|
|
struct NestedBundle {
|
|
a: A,
|
|
foo: FooBundle,
|
|
b: B,
|
|
}
|
|
|
|
let mut ids = Vec::new();
|
|
<NestedBundle as Bundle>::component_ids(
|
|
&mut world.components,
|
|
&mut world.storages,
|
|
&mut |id| {
|
|
ids.push(id);
|
|
},
|
|
);
|
|
|
|
assert_eq!(
|
|
ids,
|
|
&[
|
|
world.init_component::<A>(),
|
|
world.init_component::<TableStored>(),
|
|
world.init_component::<SparseStored>(),
|
|
world.init_component::<B>(),
|
|
]
|
|
);
|
|
|
|
let e3 = world
|
|
.spawn(NestedBundle {
|
|
a: A(1),
|
|
foo: FooBundle {
|
|
x: TableStored("ghi"),
|
|
y: SparseStored(789),
|
|
},
|
|
b: B(2),
|
|
})
|
|
.id();
|
|
|
|
assert_eq!(world.get::<TableStored>(e3).unwrap().0, "ghi");
|
|
assert_eq!(world.get::<SparseStored>(e3).unwrap().0, 789);
|
|
assert_eq!(world.get::<A>(e3).unwrap().0, 1);
|
|
assert_eq!(world.get::<B>(e3).unwrap().0, 2);
|
|
assert_eq!(
|
|
world.entity_mut(e3).remove::<NestedBundle>().unwrap(),
|
|
NestedBundle {
|
|
a: A(1),
|
|
foo: FooBundle {
|
|
x: TableStored("ghi"),
|
|
y: SparseStored(789),
|
|
},
|
|
b: B(2),
|
|
}
|
|
);
|
|
|
|
#[derive(Default, Component, PartialEq, Debug)]
|
|
struct Ignored;
|
|
|
|
#[derive(Bundle, PartialEq, Debug)]
|
|
struct BundleWithIgnored {
|
|
c: C,
|
|
#[bundle(ignore)]
|
|
ignored: Ignored,
|
|
}
|
|
|
|
let mut ids = Vec::new();
|
|
<BundleWithIgnored as Bundle>::component_ids(
|
|
&mut world.components,
|
|
&mut world.storages,
|
|
&mut |id| {
|
|
ids.push(id);
|
|
},
|
|
);
|
|
|
|
assert_eq!(ids, &[world.init_component::<C>(),]);
|
|
|
|
let e4 = world
|
|
.spawn(BundleWithIgnored {
|
|
c: C,
|
|
ignored: Ignored,
|
|
})
|
|
.id();
|
|
|
|
assert_eq!(world.get::<C>(e4).unwrap(), &C);
|
|
assert_eq!(world.get::<Ignored>(e4), None);
|
|
|
|
assert_eq!(
|
|
world.entity_mut(e4).remove::<BundleWithIgnored>().unwrap(),
|
|
BundleWithIgnored {
|
|
c: C,
|
|
ignored: Ignored,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn despawn_table_storage() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456))).id();
|
|
assert_eq!(world.entities.len(), 2);
|
|
assert!(world.despawn(e));
|
|
assert_eq!(world.entities.len(), 1);
|
|
assert!(world.get::<TableStored>(e).is_none());
|
|
assert!(world.get::<A>(e).is_none());
|
|
assert_eq!(world.get::<TableStored>(f).unwrap().0, "def");
|
|
assert_eq!(world.get::<A>(f).unwrap().0, 456);
|
|
}
|
|
|
|
#[test]
|
|
fn despawn_mixed_storage() {
|
|
let mut world = World::new();
|
|
|
|
let e = world.spawn((TableStored("abc"), SparseStored(123))).id();
|
|
let f = world.spawn((TableStored("def"), SparseStored(456))).id();
|
|
assert_eq!(world.entities.len(), 2);
|
|
assert!(world.despawn(e));
|
|
assert_eq!(world.entities.len(), 1);
|
|
assert!(world.get::<TableStored>(e).is_none());
|
|
assert!(world.get::<SparseStored>(e).is_none());
|
|
assert_eq!(world.get::<TableStored>(f).unwrap().0, "def");
|
|
assert_eq!(world.get::<SparseStored>(f).unwrap().0, 456);
|
|
}
|
|
|
|
#[test]
|
|
fn query_all() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456))).id();
|
|
|
|
let ents = world
|
|
.query::<(Entity, &A, &TableStored)>()
|
|
.iter(&world)
|
|
.map(|(e, &i, &s)| (e, i, s))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(
|
|
ents,
|
|
&[
|
|
(e, A(123), TableStored("abc")),
|
|
(f, A(456), TableStored("def"))
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn query_all_for_each() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456))).id();
|
|
|
|
let mut results = Vec::new();
|
|
world
|
|
.query::<(Entity, &A, &TableStored)>()
|
|
.for_each(&world, |(e, &i, &s)| results.push((e, i, s)));
|
|
assert_eq!(
|
|
results,
|
|
&[
|
|
(e, A(123), TableStored("abc")),
|
|
(f, A(456), TableStored("def"))
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn query_single_component() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456), B(1))).id();
|
|
let ents = world
|
|
.query::<(Entity, &A)>()
|
|
.iter(&world)
|
|
.map(|(e, &i)| (e, i))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(ents, &[(e, A(123)), (f, A(456))]);
|
|
}
|
|
|
|
#[test]
|
|
fn stateful_query_handles_new_archetype() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let mut query = world.query::<(Entity, &A)>();
|
|
|
|
let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::<Vec<_>>();
|
|
assert_eq!(ents, &[(e, A(123))]);
|
|
|
|
let f = world.spawn((TableStored("def"), A(456), B(1))).id();
|
|
let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::<Vec<_>>();
|
|
assert_eq!(ents, &[(e, A(123)), (f, A(456))]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_single_component_for_each() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456), B(1))).id();
|
|
let mut results = Vec::new();
|
|
world
|
|
.query::<(Entity, &A)>()
|
|
.for_each(&world, |(e, &i)| results.push((e, i)));
|
|
assert_eq!(results, &[(e, A(123)), (f, A(456))]);
|
|
}
|
|
|
|
#[test]
|
|
fn par_for_each_dense() {
|
|
ComputeTaskPool::init(TaskPool::default);
|
|
let mut world = World::new();
|
|
let e1 = world.spawn(A(1)).id();
|
|
let e2 = world.spawn(A(2)).id();
|
|
let e3 = world.spawn(A(3)).id();
|
|
let e4 = world.spawn((A(4), B(1))).id();
|
|
let e5 = world.spawn((A(5), B(1))).id();
|
|
let results = Arc::new(Mutex::new(Vec::new()));
|
|
world
|
|
.query::<(Entity, &A)>()
|
|
.par_iter(&world)
|
|
.for_each(|(e, &A(i))| {
|
|
results.lock().unwrap().push((e, i));
|
|
});
|
|
results.lock().unwrap().sort();
|
|
assert_eq!(
|
|
&*results.lock().unwrap(),
|
|
&[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn par_for_each_sparse() {
|
|
ComputeTaskPool::init(TaskPool::default);
|
|
let mut world = World::new();
|
|
let e1 = world.spawn(SparseStored(1)).id();
|
|
let e2 = world.spawn(SparseStored(2)).id();
|
|
let e3 = world.spawn(SparseStored(3)).id();
|
|
let e4 = world.spawn((SparseStored(4), A(1))).id();
|
|
let e5 = world.spawn((SparseStored(5), A(1))).id();
|
|
let results = Arc::new(Mutex::new(Vec::new()));
|
|
world
|
|
.query::<(Entity, &SparseStored)>()
|
|
.par_iter(&world)
|
|
.for_each(|(e, &SparseStored(i))| results.lock().unwrap().push((e, i)));
|
|
results.lock().unwrap().sort();
|
|
assert_eq!(
|
|
&*results.lock().unwrap(),
|
|
&[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn query_missing_component() {
|
|
let mut world = World::new();
|
|
world.spawn((TableStored("abc"), A(123)));
|
|
world.spawn((TableStored("def"), A(456)));
|
|
assert!(world.query::<(&B, &A)>().iter(&world).next().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn query_sparse_component() {
|
|
let mut world = World::new();
|
|
world.spawn((TableStored("abc"), A(123)));
|
|
let f = world.spawn((TableStored("def"), A(456), B(1))).id();
|
|
let ents = world
|
|
.query::<(Entity, &B)>()
|
|
.iter(&world)
|
|
.map(|(e, &b)| (e, b))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(ents, &[(f, B(1))]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_filter_with() {
|
|
let mut world = World::new();
|
|
world.spawn((A(123), B(1)));
|
|
world.spawn(A(456));
|
|
let result = world
|
|
.query_filtered::<&A, With<B>>()
|
|
.iter(&world)
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(result, vec![A(123)]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_filter_with_for_each() {
|
|
let mut world = World::new();
|
|
world.spawn((A(123), B(1)));
|
|
world.spawn(A(456));
|
|
|
|
let mut results = Vec::new();
|
|
world
|
|
.query_filtered::<&A, With<B>>()
|
|
.for_each(&world, |i| results.push(*i));
|
|
assert_eq!(results, vec![A(123)]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_filter_with_sparse() {
|
|
let mut world = World::new();
|
|
|
|
world.spawn((A(123), SparseStored(321)));
|
|
world.spawn(A(456));
|
|
let result = world
|
|
.query_filtered::<&A, With<SparseStored>>()
|
|
.iter(&world)
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(result, vec![A(123)]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_filter_with_sparse_for_each() {
|
|
let mut world = World::new();
|
|
|
|
world.spawn((A(123), SparseStored(321)));
|
|
world.spawn(A(456));
|
|
let mut results = Vec::new();
|
|
world
|
|
.query_filtered::<&A, With<SparseStored>>()
|
|
.for_each(&world, |i| results.push(*i));
|
|
assert_eq!(results, vec![A(123)]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_filter_without() {
|
|
let mut world = World::new();
|
|
world.spawn((A(123), B(321)));
|
|
world.spawn(A(456));
|
|
let result = world
|
|
.query_filtered::<&A, Without<B>>()
|
|
.iter(&world)
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(result, vec![A(456)]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_optional_component_table() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456), B(1))).id();
|
|
// this should be skipped
|
|
world.spawn(TableStored("abc"));
|
|
let ents = world
|
|
.query::<(Entity, Option<&B>, &A)>()
|
|
.iter(&world)
|
|
.map(|(e, b, &i)| (e, b.copied(), i))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(ents, &[(e, None, A(123)), (f, Some(B(1)), A(456))]);
|
|
}
|
|
|
|
#[test]
|
|
fn query_optional_component_sparse() {
|
|
let mut world = World::new();
|
|
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world
|
|
.spawn((TableStored("def"), A(456), SparseStored(1)))
|
|
.id();
|
|
// this should be skipped
|
|
// world.spawn(SparseStored(1));
|
|
let ents = world
|
|
.query::<(Entity, Option<&SparseStored>, &A)>()
|
|
.iter(&world)
|
|
.map(|(e, b, &i)| (e, b.copied(), i))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(
|
|
ents,
|
|
&[(e, None, A(123)), (f, Some(SparseStored(1)), A(456))]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn query_optional_component_sparse_no_match() {
|
|
let mut world = World::new();
|
|
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
let f = world.spawn((TableStored("def"), A(456))).id();
|
|
// // this should be skipped
|
|
world.spawn(TableStored("abc"));
|
|
let ents = world
|
|
.query::<(Entity, Option<&SparseStored>, &A)>()
|
|
.iter(&world)
|
|
.map(|(e, b, &i)| (e, b.copied(), i))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(ents, &[(e, None, A(123)), (f, None, A(456))]);
|
|
}
|
|
|
|
#[test]
|
|
fn add_remove_components() {
|
|
let mut world = World::new();
|
|
let e1 = world.spawn((A(1), B(3), TableStored("abc"))).id();
|
|
let e2 = world.spawn((A(2), B(4), TableStored("xyz"))).id();
|
|
|
|
assert_eq!(
|
|
world
|
|
.query::<(Entity, &A, &B)>()
|
|
.iter(&world)
|
|
.map(|(e, &i, &b)| (e, i, b))
|
|
.collect::<Vec<_>>(),
|
|
&[(e1, A(1), B(3)), (e2, A(2), B(4))]
|
|
);
|
|
|
|
assert_eq!(world.entity_mut(e1).remove::<A>(), Some(A(1)));
|
|
assert_eq!(
|
|
world
|
|
.query::<(Entity, &A, &B)>()
|
|
.iter(&world)
|
|
.map(|(e, &i, &b)| (e, i, b))
|
|
.collect::<Vec<_>>(),
|
|
&[(e2, A(2), B(4))]
|
|
);
|
|
assert_eq!(
|
|
world
|
|
.query::<(Entity, &B, &TableStored)>()
|
|
.iter(&world)
|
|
.map(|(e, &B(b), &TableStored(s))| (e, b, s))
|
|
.collect::<Vec<_>>(),
|
|
&[(e2, 4, "xyz"), (e1, 3, "abc")]
|
|
);
|
|
world.entity_mut(e1).insert(A(43));
|
|
assert_eq!(
|
|
world
|
|
.query::<(Entity, &A, &B)>()
|
|
.iter(&world)
|
|
.map(|(e, &i, &b)| (e, i, b))
|
|
.collect::<Vec<_>>(),
|
|
&[(e2, A(2), B(4)), (e1, A(43), B(3))]
|
|
);
|
|
world.entity_mut(e1).insert(C);
|
|
assert_eq!(
|
|
world
|
|
.query::<(Entity, &C)>()
|
|
.iter(&world)
|
|
.map(|(e, &f)| (e, f))
|
|
.collect::<Vec<_>>(),
|
|
&[(e1, C)]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn table_add_remove_many() {
|
|
let mut world = World::default();
|
|
#[cfg(miri)]
|
|
let (mut entities, to) = {
|
|
let to = 10;
|
|
(Vec::with_capacity(to), to)
|
|
};
|
|
#[cfg(not(miri))]
|
|
let (mut entities, to) = {
|
|
let to = 10_000;
|
|
(Vec::with_capacity(to), to)
|
|
};
|
|
|
|
for _ in 0..to {
|
|
entities.push(world.spawn(B(0)).id());
|
|
}
|
|
|
|
for (i, entity) in entities.iter().cloned().enumerate() {
|
|
world.entity_mut(entity).insert(A(i));
|
|
}
|
|
|
|
for (i, entity) in entities.iter().cloned().enumerate() {
|
|
assert_eq!(world.entity_mut(entity).remove::<A>(), Some(A(i)));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sparse_set_add_remove_many() {
|
|
let mut world = World::default();
|
|
|
|
let mut entities = Vec::with_capacity(1000);
|
|
for _ in 0..4 {
|
|
entities.push(world.spawn(A(2)).id());
|
|
}
|
|
|
|
for (i, entity) in entities.iter().cloned().enumerate() {
|
|
world.entity_mut(entity).insert(SparseStored(i as u32));
|
|
}
|
|
|
|
for (i, entity) in entities.iter().cloned().enumerate() {
|
|
assert_eq!(
|
|
world.entity_mut(entity).remove::<SparseStored>(),
|
|
Some(SparseStored(i as u32))
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn remove_missing() {
|
|
let mut world = World::new();
|
|
let e = world.spawn((TableStored("abc"), A(123))).id();
|
|
assert!(world.entity_mut(e).remove::<B>().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn spawn_batch() {
|
|
let mut world = World::new();
|
|
world.spawn_batch((0..100).map(|x| (A(x), TableStored("abc"))));
|
|
let values = world
|
|
.query::<&A>()
|
|
.iter(&world)
|
|
.map(|v| v.0)
|
|
.collect::<Vec<_>>();
|
|
let expected = (0..100).collect::<Vec<_>>();
|
|
assert_eq!(values, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn query_get() {
|
|
let mut world = World::new();
|
|
let a = world.spawn((TableStored("abc"), A(123))).id();
|
|
let b = world.spawn((TableStored("def"), A(456))).id();
|
|
let c = world.spawn((TableStored("ghi"), A(789), B(1))).id();
|
|
|
|
let mut i32_query = world.query::<&A>();
|
|
assert_eq!(i32_query.get(&world, a).unwrap().0, 123);
|
|
assert_eq!(i32_query.get(&world, b).unwrap().0, 456);
|
|
|
|
let mut i32_bool_query = world.query::<(&A, &B)>();
|
|
assert!(i32_bool_query.get(&world, a).is_err());
|
|
assert_eq!(i32_bool_query.get(&world, c).unwrap(), (&A(789), &B(1)));
|
|
assert!(world.despawn(a));
|
|
assert!(i32_query.get(&world, a).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn query_get_works_across_sparse_removal() {
|
|
// Regression test for: https://github.com/bevyengine/bevy/issues/6623
|
|
let mut world = World::new();
|
|
let a = world.spawn((TableStored("abc"), SparseStored(123))).id();
|
|
let b = world.spawn((TableStored("def"), SparseStored(456))).id();
|
|
let c = world
|
|
.spawn((TableStored("ghi"), SparseStored(789), B(1)))
|
|
.id();
|
|
|
|
let mut query = world.query::<&TableStored>();
|
|
assert_eq!(query.get(&world, a).unwrap(), &TableStored("abc"));
|
|
assert_eq!(query.get(&world, b).unwrap(), &TableStored("def"));
|
|
assert_eq!(query.get(&world, c).unwrap(), &TableStored("ghi"));
|
|
|
|
world.entity_mut(b).remove::<SparseStored>();
|
|
world.entity_mut(c).remove::<SparseStored>();
|
|
|
|
assert_eq!(query.get(&world, a).unwrap(), &TableStored("abc"));
|
|
assert_eq!(query.get(&world, b).unwrap(), &TableStored("def"));
|
|
assert_eq!(query.get(&world, c).unwrap(), &TableStored("ghi"));
|
|
}
|
|
|
|
#[test]
|
|
fn remove_tracking() {
|
|
let mut world = World::new();
|
|
|
|
let a = world.spawn((SparseStored(0), A(123))).id();
|
|
let b = world.spawn((SparseStored(1), A(123))).id();
|
|
|
|
world.entity_mut(a).despawn();
|
|
assert_eq!(
|
|
world.removed::<A>().collect::<Vec<_>>(),
|
|
&[a],
|
|
"despawning results in 'removed component' state for table components"
|
|
);
|
|
assert_eq!(
|
|
world.removed::<SparseStored>().collect::<Vec<_>>(),
|
|
&[a],
|
|
"despawning results in 'removed component' state for sparse set components"
|
|
);
|
|
|
|
world.entity_mut(b).insert(B(1));
|
|
assert_eq!(
|
|
world.removed::<A>().collect::<Vec<_>>(),
|
|
&[a],
|
|
"archetype moves does not result in 'removed component' state"
|
|
);
|
|
|
|
world.entity_mut(b).remove::<A>();
|
|
assert_eq!(
|
|
world.removed::<A>().collect::<Vec<_>>(),
|
|
&[a, b],
|
|
"removing a component results in a 'removed component' state"
|
|
);
|
|
|
|
world.clear_trackers();
|
|
assert_eq!(
|
|
world.removed::<A>().collect::<Vec<_>>(),
|
|
&[],
|
|
"clearning trackers clears removals"
|
|
);
|
|
assert_eq!(
|
|
world.removed::<SparseStored>().collect::<Vec<_>>(),
|
|
&[],
|
|
"clearning trackers clears removals"
|
|
);
|
|
assert_eq!(
|
|
world.removed::<B>().collect::<Vec<_>>(),
|
|
&[],
|
|
"clearning trackers clears removals"
|
|
);
|
|
|
|
// TODO: uncomment when world.clear() is implemented
|
|
// let c = world.spawn(("abc", 123)).id();
|
|
// let d = world.spawn(("abc", 123)).id();
|
|
// world.clear();
|
|
// assert_eq!(
|
|
// world.removed::<i32>(),
|
|
// &[c, d],
|
|
// "world clears result in 'removed component' states"
|
|
// );
|
|
// assert_eq!(
|
|
// world.removed::<&'static str>(),
|
|
// &[c, d, b],
|
|
// "world clears result in 'removed component' states"
|
|
// );
|
|
// assert_eq!(
|
|
// world.removed::<f64>(),
|
|
// &[b],
|
|
// "world clears result in 'removed component' states"
|
|
// );
|
|
}
|
|
|
|
#[test]
|
|
fn added_tracking() {
|
|
let mut world = World::new();
|
|
let a = world.spawn(A(123)).id();
|
|
|
|
assert_eq!(world.query::<&A>().iter(&world).count(), 1);
|
|
assert_eq!(
|
|
world.query_filtered::<(), Added<A>>().iter(&world).count(),
|
|
1
|
|
);
|
|
assert_eq!(world.query::<&A>().iter(&world).count(), 1);
|
|
assert_eq!(
|
|
world.query_filtered::<(), Added<A>>().iter(&world).count(),
|
|
1
|
|
);
|
|
assert!(world.query::<&A>().get(&world, a).is_ok());
|
|
assert!(world
|
|
.query_filtered::<(), Added<A>>()
|
|
.get(&world, a)
|
|
.is_ok());
|
|
assert!(world.query::<&A>().get(&world, a).is_ok());
|
|
assert!(world
|
|
.query_filtered::<(), Added<A>>()
|
|
.get(&world, a)
|
|
.is_ok());
|
|
|
|
world.clear_trackers();
|
|
|
|
assert_eq!(world.query::<&A>().iter(&world).count(), 1);
|
|
assert_eq!(
|
|
world.query_filtered::<(), Added<A>>().iter(&world).count(),
|
|
0
|
|
);
|
|
assert_eq!(world.query::<&A>().iter(&world).count(), 1);
|
|
assert_eq!(
|
|
world.query_filtered::<(), Added<A>>().iter(&world).count(),
|
|
0
|
|
);
|
|
assert!(world.query::<&A>().get(&world, a).is_ok());
|
|
assert!(world
|
|
.query_filtered::<(), Added<A>>()
|
|
.get(&world, a)
|
|
.is_err());
|
|
assert!(world.query::<&A>().get(&world, a).is_ok());
|
|
assert!(world
|
|
.query_filtered::<(), Added<A>>()
|
|
.get(&world, a)
|
|
.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn added_queries() {
|
|
let mut world = World::default();
|
|
let e1 = world.spawn(A(0)).id();
|
|
|
|
fn get_added<Com: Component>(world: &mut World) -> Vec<Entity> {
|
|
world
|
|
.query_filtered::<Entity, Added<Com>>()
|
|
.iter(world)
|
|
.collect::<Vec<Entity>>()
|
|
}
|
|
|
|
assert_eq!(get_added::<A>(&mut world), vec![e1]);
|
|
world.entity_mut(e1).insert(B(0));
|
|
assert_eq!(get_added::<A>(&mut world), vec![e1]);
|
|
assert_eq!(get_added::<B>(&mut world), vec![e1]);
|
|
|
|
world.clear_trackers();
|
|
assert!(get_added::<A>(&mut world).is_empty());
|
|
let e2 = world.spawn((A(1), B(1))).id();
|
|
assert_eq!(get_added::<A>(&mut world), vec![e2]);
|
|
assert_eq!(get_added::<B>(&mut world), vec![e2]);
|
|
|
|
let added = world
|
|
.query_filtered::<Entity, (Added<A>, Added<B>)>()
|
|
.iter(&world)
|
|
.collect::<Vec<Entity>>();
|
|
assert_eq!(added, vec![e2]);
|
|
}
|
|
|
|
#[test]
|
|
fn changed_trackers() {
|
|
let mut world = World::default();
|
|
let e1 = world.spawn((A(0), B(0))).id();
|
|
let e2 = world.spawn((A(0), B(0))).id();
|
|
let e3 = world.spawn((A(0), B(0))).id();
|
|
world.spawn((A(0), B(0)));
|
|
|
|
world.clear_trackers();
|
|
|
|
for (i, mut a) in world.query::<&mut A>().iter_mut(&mut world).enumerate() {
|
|
if i % 2 == 0 {
|
|
a.0 += 1;
|
|
}
|
|
}
|
|
|
|
fn get_filtered<F: ReadOnlyWorldQuery>(world: &mut World) -> Vec<Entity> {
|
|
world
|
|
.query_filtered::<Entity, F>()
|
|
.iter(world)
|
|
.collect::<Vec<Entity>>()
|
|
}
|
|
|
|
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e1, e3]);
|
|
|
|
// ensure changing an entity's archetypes also moves its changed state
|
|
world.entity_mut(e1).insert(C);
|
|
|
|
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)");
|
|
|
|
// spawning a new A entity should not change existing changed state
|
|
world.entity_mut(e1).insert((A(0), B(0)));
|
|
assert_eq!(
|
|
get_filtered::<Changed<A>>(&mut world),
|
|
vec![e3, e1],
|
|
"changed entities list should not change"
|
|
);
|
|
|
|
// removing an unchanged entity should not change changed state
|
|
assert!(world.despawn(e2));
|
|
assert_eq!(
|
|
get_filtered::<Changed<A>>(&mut world),
|
|
vec![e3, e1],
|
|
"changed entities list should not change"
|
|
);
|
|
|
|
// removing a changed entity should remove it from enumeration
|
|
assert!(world.despawn(e1));
|
|
assert_eq!(
|
|
get_filtered::<Changed<A>>(&mut world),
|
|
vec![e3],
|
|
"e1 should no longer be returned"
|
|
);
|
|
|
|
world.clear_trackers();
|
|
|
|
assert!(get_filtered::<Changed<A>>(&mut world).is_empty());
|
|
|
|
let e4 = world.spawn_empty().id();
|
|
|
|
world.entity_mut(e4).insert(A(0));
|
|
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]);
|
|
assert_eq!(get_filtered::<Added<A>>(&mut world), vec![e4]);
|
|
|
|
world.entity_mut(e4).insert(A(1));
|
|
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]);
|
|
|
|
world.clear_trackers();
|
|
|
|
// ensure inserting multiple components set changed state for all components and set added
|
|
// state for non existing components even when changing archetype.
|
|
world.entity_mut(e4).insert((A(0), B(0)));
|
|
|
|
assert!(get_filtered::<Added<A>>(&mut world).is_empty());
|
|
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]);
|
|
assert_eq!(get_filtered::<Added<B>>(&mut world), vec![e4]);
|
|
assert_eq!(get_filtered::<Changed<B>>(&mut world), vec![e4]);
|
|
}
|
|
|
|
#[test]
|
|
fn changed_trackers_sparse() {
|
|
let mut world = World::default();
|
|
let e1 = world.spawn(SparseStored(0)).id();
|
|
let e2 = world.spawn(SparseStored(0)).id();
|
|
let e3 = world.spawn(SparseStored(0)).id();
|
|
world.spawn(SparseStored(0));
|
|
|
|
world.clear_trackers();
|
|
|
|
for (i, mut a) in world
|
|
.query::<&mut SparseStored>()
|
|
.iter_mut(&mut world)
|
|
.enumerate()
|
|
{
|
|
if i % 2 == 0 {
|
|
a.0 += 1;
|
|
}
|
|
}
|
|
|
|
fn get_filtered<F: ReadOnlyWorldQuery>(world: &mut World) -> Vec<Entity> {
|
|
world
|
|
.query_filtered::<Entity, F>()
|
|
.iter(world)
|
|
.collect::<Vec<Entity>>()
|
|
}
|
|
|
|
assert_eq!(
|
|
get_filtered::<Changed<SparseStored>>(&mut world),
|
|
vec![e1, e3]
|
|
);
|
|
|
|
// ensure changing an entity's archetypes also moves its changed state
|
|
world.entity_mut(e1).insert(C);
|
|
|
|
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)");
|
|
|
|
// spawning a new SparseStored entity should not change existing changed state
|
|
world.entity_mut(e1).insert(SparseStored(0));
|
|
assert_eq!(
|
|
get_filtered::<Changed<SparseStored>>(&mut world),
|
|
vec![e3, e1],
|
|
"changed entities list should not change"
|
|
);
|
|
|
|
// removing an unchanged entity should not change changed state
|
|
assert!(world.despawn(e2));
|
|
assert_eq!(
|
|
get_filtered::<Changed<SparseStored>>(&mut world),
|
|
vec![e3, e1],
|
|
"changed entities list should not change"
|
|
);
|
|
|
|
// removing a changed entity should remove it from enumeration
|
|
assert!(world.despawn(e1));
|
|
assert_eq!(
|
|
get_filtered::<Changed<SparseStored>>(&mut world),
|
|
vec![e3],
|
|
"e1 should no longer be returned"
|
|
);
|
|
|
|
world.clear_trackers();
|
|
|
|
assert!(get_filtered::<Changed<SparseStored>>(&mut world).is_empty());
|
|
|
|
let e4 = world.spawn_empty().id();
|
|
|
|
world.entity_mut(e4).insert(SparseStored(0));
|
|
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]);
|
|
assert_eq!(get_filtered::<Added<SparseStored>>(&mut world), vec![e4]);
|
|
|
|
world.entity_mut(e4).insert(A(1));
|
|
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]);
|
|
|
|
world.clear_trackers();
|
|
|
|
// ensure inserting multiple components set changed state for all components and set added
|
|
// state for non existing components even when changing archetype.
|
|
world.entity_mut(e4).insert(SparseStored(0));
|
|
|
|
assert!(get_filtered::<Added<SparseStored>>(&mut world).is_empty());
|
|
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_spawn() {
|
|
let mut world = World::default();
|
|
let e = world.spawn_empty().id();
|
|
let mut e_mut = world.entity_mut(e);
|
|
e_mut.insert(A(0));
|
|
assert_eq!(e_mut.get::<A>().unwrap(), &A(0));
|
|
}
|
|
|
|
#[test]
|
|
fn reserve_and_spawn() {
|
|
let mut world = World::default();
|
|
let e = world.entities().reserve_entity();
|
|
world.flush();
|
|
let mut e_mut = world.entity_mut(e);
|
|
e_mut.insert(A(0));
|
|
assert_eq!(e_mut.get::<A>().unwrap(), &A(0));
|
|
}
|
|
|
|
#[test]
|
|
fn changed_query() {
|
|
let mut world = World::default();
|
|
let e1 = world.spawn((A(0), B(0))).id();
|
|
|
|
fn get_changed(world: &mut World) -> Vec<Entity> {
|
|
world
|
|
.query_filtered::<Entity, Changed<A>>()
|
|
.iter(world)
|
|
.collect::<Vec<Entity>>()
|
|
}
|
|
assert_eq!(get_changed(&mut world), vec![e1]);
|
|
world.clear_trackers();
|
|
assert_eq!(get_changed(&mut world), vec![]);
|
|
*world.get_mut(e1).unwrap() = A(1);
|
|
assert_eq!(get_changed(&mut world), vec![e1]);
|
|
}
|
|
|
|
#[test]
|
|
fn resource() {
|
|
use crate::system::Resource;
|
|
|
|
#[derive(Resource, PartialEq, Debug)]
|
|
struct Num(i32);
|
|
|
|
#[derive(Resource, PartialEq, Debug)]
|
|
struct BigNum(u64);
|
|
|
|
let mut world = World::default();
|
|
assert!(world.get_resource::<Num>().is_none());
|
|
assert!(!world.contains_resource::<Num>());
|
|
assert!(!world.is_resource_added::<Num>());
|
|
assert!(!world.is_resource_changed::<Num>());
|
|
|
|
world.insert_resource(Num(123));
|
|
let resource_id = world
|
|
.components()
|
|
.get_resource_id(TypeId::of::<Num>())
|
|
.unwrap();
|
|
let archetype_component_id = world.storages().resources.get(resource_id).unwrap().id();
|
|
|
|
assert_eq!(world.resource::<Num>().0, 123);
|
|
assert!(world.contains_resource::<Num>());
|
|
assert!(world.is_resource_added::<Num>());
|
|
assert!(world.is_resource_changed::<Num>());
|
|
|
|
world.insert_resource(BigNum(456));
|
|
assert_eq!(world.resource::<BigNum>().0, 456u64);
|
|
|
|
world.insert_resource(BigNum(789));
|
|
assert_eq!(world.resource::<BigNum>().0, 789);
|
|
|
|
{
|
|
let mut value = world.resource_mut::<BigNum>();
|
|
assert_eq!(value.0, 789);
|
|
value.0 = 10;
|
|
}
|
|
|
|
assert_eq!(
|
|
world.resource::<BigNum>().0,
|
|
10,
|
|
"resource changes are preserved"
|
|
);
|
|
|
|
assert_eq!(
|
|
world.remove_resource::<BigNum>(),
|
|
Some(BigNum(10)),
|
|
"removed resource has the correct value"
|
|
);
|
|
assert_eq!(
|
|
world.get_resource::<BigNum>(),
|
|
None,
|
|
"removed resource no longer exists"
|
|
);
|
|
assert_eq!(
|
|
world.remove_resource::<BigNum>(),
|
|
None,
|
|
"double remove returns nothing"
|
|
);
|
|
|
|
world.insert_resource(BigNum(1));
|
|
assert_eq!(
|
|
world.get_resource::<BigNum>(),
|
|
Some(&BigNum(1)),
|
|
"re-inserting resources works"
|
|
);
|
|
|
|
assert_eq!(
|
|
world.get_resource::<Num>(),
|
|
Some(&Num(123)),
|
|
"other resources are unaffected"
|
|
);
|
|
|
|
let current_resource_id = world
|
|
.components()
|
|
.get_resource_id(TypeId::of::<Num>())
|
|
.unwrap();
|
|
assert_eq!(
|
|
resource_id, current_resource_id,
|
|
"resource id does not change after removing / re-adding"
|
|
);
|
|
|
|
let current_archetype_component_id =
|
|
world.storages().resources.get(resource_id).unwrap().id();
|
|
|
|
assert_eq!(
|
|
archetype_component_id, current_archetype_component_id,
|
|
"resource archetype component id does not change after removing / re-adding"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn remove_intersection() {
|
|
let mut world = World::default();
|
|
let e1 = world.spawn((A(1), B(1), TableStored("a"))).id();
|
|
|
|
let mut e = world.entity_mut(e1);
|
|
assert_eq!(e.get::<TableStored>(), Some(&TableStored("a")));
|
|
assert_eq!(e.get::<A>(), Some(&A(1)));
|
|
assert_eq!(e.get::<B>(), Some(&B(1)));
|
|
assert_eq!(
|
|
e.get::<C>(),
|
|
None,
|
|
"C is not in the entity, so it should not exist"
|
|
);
|
|
|
|
e.remove_intersection::<(A, B, C)>();
|
|
assert_eq!(
|
|
e.get::<TableStored>(),
|
|
Some(&TableStored("a")),
|
|
"TableStored is not in the removed bundle, so it should exist"
|
|
);
|
|
assert_eq!(
|
|
e.get::<A>(),
|
|
None,
|
|
"Num is in the removed bundle, so it should not exist"
|
|
);
|
|
assert_eq!(
|
|
e.get::<B>(),
|
|
None,
|
|
"f64 is in the removed bundle, so it should not exist"
|
|
);
|
|
assert_eq!(
|
|
e.get::<C>(),
|
|
None,
|
|
"usize is in the removed bundle, so it should not exist"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn remove() {
|
|
let mut world = World::default();
|
|
world.spawn((A(1), B(1), TableStored("1")));
|
|
let e2 = world.spawn((A(2), B(2), TableStored("2"))).id();
|
|
world.spawn((A(3), B(3), TableStored("3")));
|
|
|
|
let mut query = world.query::<(&B, &TableStored)>();
|
|
let results = query
|
|
.iter(&world)
|
|
.map(|(a, b)| (a.0, b.0))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(results, vec![(1, "1"), (2, "2"), (3, "3"),]);
|
|
|
|
let removed_bundle = world.entity_mut(e2).remove::<(B, TableStored)>().unwrap();
|
|
assert_eq!(removed_bundle, (B(2), TableStored("2")));
|
|
|
|
let results = query
|
|
.iter(&world)
|
|
.map(|(a, b)| (a.0, b.0))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(results, vec![(1, "1"), (3, "3"),]);
|
|
|
|
let mut a_query = world.query::<&A>();
|
|
let results = a_query.iter(&world).map(|a| a.0).collect::<Vec<_>>();
|
|
assert_eq!(results, vec![1, 3, 2]);
|
|
|
|
let entity_ref = world.entity(e2);
|
|
assert_eq!(
|
|
entity_ref.get::<A>(),
|
|
Some(&A(2)),
|
|
"A is not in the removed bundle, so it should exist"
|
|
);
|
|
assert_eq!(
|
|
entity_ref.get::<B>(),
|
|
None,
|
|
"B is in the removed bundle, so it should not exist"
|
|
);
|
|
assert_eq!(
|
|
entity_ref.get::<TableStored>(),
|
|
None,
|
|
"TableStored is in the removed bundle, so it should not exist"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn non_send_resource() {
|
|
let mut world = World::default();
|
|
world.insert_non_send_resource(123i32);
|
|
world.insert_non_send_resource(456i64);
|
|
assert_eq!(*world.non_send_resource::<i32>(), 123);
|
|
assert_eq!(*world.non_send_resource_mut::<i64>(), 456);
|
|
}
|
|
|
|
#[test]
|
|
fn non_send_resource_points_to_distinct_data() {
|
|
let mut world = World::default();
|
|
world.insert_resource(A(123));
|
|
world.insert_non_send_resource(A(456));
|
|
assert_eq!(*world.resource::<A>(), A(123));
|
|
assert_eq!(*world.non_send_resource::<A>(), A(456));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn non_send_resource_panic() {
|
|
let mut world = World::default();
|
|
world.insert_non_send_resource(0i32);
|
|
std::thread::spawn(move || {
|
|
let _ = world.non_send_resource_mut::<i32>();
|
|
})
|
|
.join()
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[allow(deprecated)]
|
|
fn trackers_query() {
|
|
use crate::prelude::ChangeTrackers;
|
|
|
|
let mut world = World::default();
|
|
let e1 = world.spawn((A(0), B(0))).id();
|
|
world.spawn(B(0));
|
|
|
|
let mut trackers_query = world.query::<Option<ChangeTrackers<A>>>();
|
|
let trackers = trackers_query.iter(&world).collect::<Vec<_>>();
|
|
let a_trackers = trackers[0].as_ref().unwrap();
|
|
assert!(trackers[1].is_none());
|
|
assert!(a_trackers.is_added());
|
|
assert!(a_trackers.is_changed());
|
|
world.clear_trackers();
|
|
let trackers = trackers_query.iter(&world).collect::<Vec<_>>();
|
|
let a_trackers = trackers[0].as_ref().unwrap();
|
|
assert!(!a_trackers.is_added());
|
|
assert!(!a_trackers.is_changed());
|
|
*world.get_mut(e1).unwrap() = A(1);
|
|
let trackers = trackers_query.iter(&world).collect::<Vec<_>>();
|
|
let a_trackers = trackers[0].as_ref().unwrap();
|
|
assert!(!a_trackers.is_added());
|
|
assert!(a_trackers.is_changed());
|
|
}
|
|
|
|
#[test]
|
|
fn exact_size_query() {
|
|
let mut world = World::default();
|
|
world.spawn((A(0), B(0)));
|
|
world.spawn((A(0), B(0)));
|
|
world.spawn((A(0), B(0), C));
|
|
world.spawn(C);
|
|
|
|
let mut query = world.query::<(&A, &B)>();
|
|
assert_eq!(query.iter(&world).len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn duplicate_components_panic() {
|
|
let mut world = World::new();
|
|
world.spawn((A(1), A(2)));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn ref_and_mut_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(&A, &mut A)>();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn mut_and_ref_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(&mut A, &A)>();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn mut_and_mut_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(&mut A, &mut A)>();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn multiple_worlds_same_query_iter() {
|
|
let mut world_a = World::new();
|
|
let world_b = World::new();
|
|
let mut query = world_a.query::<&A>();
|
|
query.iter(&world_a);
|
|
query.iter(&world_b);
|
|
}
|
|
|
|
#[test]
|
|
fn query_filters_dont_collide_with_fetches() {
|
|
let mut world = World::new();
|
|
world.query_filtered::<&mut A, Changed<A>>();
|
|
}
|
|
|
|
#[test]
|
|
fn filtered_query_access() {
|
|
let mut world = World::new();
|
|
let query = world.query_filtered::<&mut A, Changed<B>>();
|
|
|
|
let mut expected = FilteredAccess::<ComponentId>::default();
|
|
let a_id = world.components.get_id(TypeId::of::<A>()).unwrap();
|
|
let b_id = world.components.get_id(TypeId::of::<B>()).unwrap();
|
|
expected.add_write(a_id);
|
|
expected.add_read(b_id);
|
|
assert!(
|
|
query.component_access.eq(&expected),
|
|
"ComponentId access from query fetch and query filter should be combined"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn multiple_worlds_same_query_get() {
|
|
let mut world_a = World::new();
|
|
let world_b = World::new();
|
|
let mut query = world_a.query::<&A>();
|
|
let _ = query.get(&world_a, Entity::from_raw(0));
|
|
let _ = query.get(&world_b, Entity::from_raw(0));
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn multiple_worlds_same_query_for_each() {
|
|
let mut world_a = World::new();
|
|
let world_b = World::new();
|
|
let mut query = world_a.query::<&A>();
|
|
query.for_each(&world_a, |_| {});
|
|
query.for_each(&world_b, |_| {});
|
|
}
|
|
|
|
#[test]
|
|
fn resource_scope() {
|
|
let mut world = World::default();
|
|
world.insert_resource(A(0));
|
|
world.resource_scope(|world: &mut World, mut value: Mut<A>| {
|
|
value.0 += 1;
|
|
assert!(!world.contains_resource::<A>());
|
|
});
|
|
assert_eq!(world.resource::<A>().0, 1);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(
|
|
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
|
|
)]
|
|
fn non_send_resource_drop_from_different_thread() {
|
|
let mut world = World::default();
|
|
world.insert_non_send_resource(NonSendA::default());
|
|
|
|
let thread = std::thread::spawn(move || {
|
|
// Dropping the non-send resource on a different thread
|
|
// Should result in a panic
|
|
drop(world);
|
|
});
|
|
|
|
if let Err(err) = thread.join() {
|
|
std::panic::resume_unwind(err);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn non_send_resource_drop_from_same_thread() {
|
|
let mut world = World::default();
|
|
world.insert_non_send_resource(NonSendA::default());
|
|
drop(world);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_overwrite_drop() {
|
|
let (dropck1, dropped1) = DropCk::new_pair();
|
|
let (dropck2, dropped2) = DropCk::new_pair();
|
|
let mut world = World::default();
|
|
world.spawn(dropck1).insert(dropck2);
|
|
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
|
|
assert_eq!(dropped2.load(Ordering::Relaxed), 0);
|
|
drop(world);
|
|
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
|
|
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_overwrite_drop_sparse() {
|
|
let (dropck1, dropped1) = DropCk::new_pair();
|
|
let (dropck2, dropped2) = DropCk::new_pair();
|
|
let mut world = World::default();
|
|
|
|
world
|
|
.spawn(DropCkSparse(dropck1))
|
|
.insert(DropCkSparse(dropck2));
|
|
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
|
|
assert_eq!(dropped2.load(Ordering::Relaxed), 0);
|
|
drop(world);
|
|
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
|
|
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn clear_entities() {
|
|
let mut world = World::default();
|
|
|
|
world.insert_resource(A(0));
|
|
world.spawn(A(1));
|
|
world.spawn(SparseStored(1));
|
|
|
|
let mut q1 = world.query::<&A>();
|
|
let mut q2 = world.query::<&SparseStored>();
|
|
|
|
assert_eq!(q1.iter(&world).len(), 1);
|
|
assert_eq!(q2.iter(&world).len(), 1);
|
|
assert_eq!(world.entities().len(), 2);
|
|
|
|
world.clear_entities();
|
|
|
|
assert_eq!(
|
|
q1.iter(&world).len(),
|
|
0,
|
|
"world should not contain table components"
|
|
);
|
|
assert_eq!(
|
|
q2.iter(&world).len(),
|
|
0,
|
|
"world should not contain sparse set components"
|
|
);
|
|
assert_eq!(
|
|
world.entities().len(),
|
|
0,
|
|
"world should not have any entities"
|
|
);
|
|
assert_eq!(
|
|
world.resource::<A>().0,
|
|
0,
|
|
"world should still contain resources"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_archetypal_size_hints() {
|
|
let mut world = World::default();
|
|
macro_rules! query_min_size {
|
|
($query:ty, $filter:ty) => {
|
|
world
|
|
.query_filtered::<$query, $filter>()
|
|
.iter(&world)
|
|
.size_hint()
|
|
.0
|
|
};
|
|
}
|
|
|
|
world.spawn((A(1), B(1), C));
|
|
world.spawn((A(1), C));
|
|
world.spawn((A(1), B(1)));
|
|
world.spawn((B(1), C));
|
|
world.spawn(A(1));
|
|
world.spawn(C);
|
|
assert_eq!(2, query_min_size![(), (With<A>, Without<B>)],);
|
|
assert_eq!(3, query_min_size![&B, Or<(With<A>, With<C>)>],);
|
|
assert_eq!(1, query_min_size![&B, (With<A>, With<C>)],);
|
|
assert_eq!(1, query_min_size![(&A, &B), With<C>],);
|
|
assert_eq!(4, query_min_size![&A, ()], "Simple Archetypal");
|
|
assert_eq!(4, query_min_size![Ref<A>, ()],);
|
|
// All the following should set minimum size to 0, as it's impossible to predict
|
|
// how many entities the filters will trim.
|
|
assert_eq!(0, query_min_size![(), Added<A>], "Simple Added");
|
|
assert_eq!(0, query_min_size![(), Changed<A>], "Simple Changed");
|
|
assert_eq!(0, query_min_size![(&A, &B), Changed<A>],);
|
|
assert_eq!(0, query_min_size![&A, (Changed<A>, With<B>)],);
|
|
assert_eq!(0, query_min_size![(&A, &B), Or<(Changed<A>, Changed<B>)>],);
|
|
}
|
|
|
|
#[test]
|
|
fn reserve_entities_across_worlds() {
|
|
let mut world_a = World::default();
|
|
let mut world_b = World::default();
|
|
|
|
let e1 = world_a.spawn(A(1)).id();
|
|
let e2 = world_a.spawn(A(2)).id();
|
|
let e3 = world_a.entities().reserve_entity();
|
|
world_a.flush();
|
|
|
|
let world_a_max_entities = world_a.entities().len();
|
|
world_b.entities.reserve_entities(world_a_max_entities);
|
|
world_b.entities.flush_as_invalid();
|
|
|
|
let e4 = world_b.spawn(A(4)).id();
|
|
assert_eq!(
|
|
e4,
|
|
Entity::new(3, 0),
|
|
"new entity is created immediately after world_a's max entity"
|
|
);
|
|
assert!(world_b.get::<A>(e1).is_none());
|
|
assert!(world_b.get_entity(e1).is_none());
|
|
|
|
assert!(world_b.get::<A>(e2).is_none());
|
|
assert!(world_b.get_entity(e2).is_none());
|
|
|
|
assert!(world_b.get::<A>(e3).is_none());
|
|
assert!(world_b.get_entity(e3).is_none());
|
|
|
|
world_b.get_or_spawn(e1).unwrap().insert(B(1));
|
|
assert_eq!(
|
|
world_b.get::<B>(e1),
|
|
Some(&B(1)),
|
|
"spawning into 'world_a' entities works"
|
|
);
|
|
|
|
world_b.get_or_spawn(e4).unwrap().insert(B(4));
|
|
assert_eq!(
|
|
world_b.get::<B>(e4),
|
|
Some(&B(4)),
|
|
"spawning into existing `world_b` entities works"
|
|
);
|
|
assert_eq!(
|
|
world_b.get::<A>(e4),
|
|
Some(&A(4)),
|
|
"spawning into existing `world_b` entities works"
|
|
);
|
|
|
|
let e4_mismatched_generation = Entity::new(3, 1);
|
|
assert!(
|
|
world_b.get_or_spawn(e4_mismatched_generation).is_none(),
|
|
"attempting to spawn on top of an entity with a mismatched entity generation fails"
|
|
);
|
|
assert_eq!(
|
|
world_b.get::<B>(e4),
|
|
Some(&B(4)),
|
|
"failed mismatched spawn doesn't change existing entity"
|
|
);
|
|
assert_eq!(
|
|
world_b.get::<A>(e4),
|
|
Some(&A(4)),
|
|
"failed mismatched spawn doesn't change existing entity"
|
|
);
|
|
|
|
let high_non_existent_entity = Entity::new(6, 0);
|
|
world_b
|
|
.get_or_spawn(high_non_existent_entity)
|
|
.unwrap()
|
|
.insert(B(10));
|
|
assert_eq!(
|
|
world_b.get::<B>(high_non_existent_entity),
|
|
Some(&B(10)),
|
|
"inserting into newly allocated high / non-continuous entity id works"
|
|
);
|
|
|
|
let high_non_existent_but_reserved_entity = Entity::new(5, 0);
|
|
assert!(
|
|
world_b.get_entity(high_non_existent_but_reserved_entity).is_none(),
|
|
"entities between high-newly allocated entity and continuous block of existing entities don't exist"
|
|
);
|
|
|
|
let reserved_entities = vec![
|
|
world_b.entities().reserve_entity(),
|
|
world_b.entities().reserve_entity(),
|
|
world_b.entities().reserve_entity(),
|
|
world_b.entities().reserve_entity(),
|
|
];
|
|
|
|
assert_eq!(
|
|
reserved_entities,
|
|
vec![
|
|
Entity::new(5, 0),
|
|
Entity::new(4, 0),
|
|
Entity::new(7, 0),
|
|
Entity::new(8, 0),
|
|
],
|
|
"space between original entities and high entities is used for new entity ids"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_or_spawn_batch() {
|
|
let mut world = World::default();
|
|
let e0 = world.spawn(A(0)).id();
|
|
let e1 = Entity::from_raw(1);
|
|
|
|
let values = vec![(e0, (B(0), C)), (e1, (B(1), C))];
|
|
|
|
world.insert_or_spawn_batch(values).unwrap();
|
|
|
|
assert_eq!(
|
|
world.get::<A>(e0),
|
|
Some(&A(0)),
|
|
"existing component was preserved"
|
|
);
|
|
assert_eq!(
|
|
world.get::<B>(e0),
|
|
Some(&B(0)),
|
|
"pre-existing entity received correct B component"
|
|
);
|
|
assert_eq!(
|
|
world.get::<B>(e1),
|
|
Some(&B(1)),
|
|
"new entity was spawned and received correct B component"
|
|
);
|
|
assert_eq!(
|
|
world.get::<C>(e0),
|
|
Some(&C),
|
|
"pre-existing entity received C component"
|
|
);
|
|
assert_eq!(
|
|
world.get::<C>(e1),
|
|
Some(&C),
|
|
"new entity was spawned and received C component"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_or_spawn_batch_invalid() {
|
|
let mut world = World::default();
|
|
let e0 = world.spawn(A(0)).id();
|
|
let e1 = Entity::from_raw(1);
|
|
let e2 = world.spawn_empty().id();
|
|
let invalid_e2 = Entity::new(e2.index(), 1);
|
|
|
|
let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))];
|
|
|
|
let result = world.insert_or_spawn_batch(values);
|
|
|
|
assert_eq!(
|
|
result,
|
|
Err(vec![invalid_e2]),
|
|
"e2 failed to be spawned or inserted into"
|
|
);
|
|
|
|
assert_eq!(
|
|
world.get::<A>(e0),
|
|
Some(&A(0)),
|
|
"existing component was preserved"
|
|
);
|
|
assert_eq!(
|
|
world.get::<B>(e0),
|
|
Some(&B(0)),
|
|
"pre-existing entity received correct B component"
|
|
);
|
|
assert_eq!(
|
|
world.get::<B>(e1),
|
|
Some(&B(1)),
|
|
"new entity was spawned and received correct B component"
|
|
);
|
|
assert_eq!(
|
|
world.get::<C>(e0),
|
|
Some(&C),
|
|
"pre-existing entity received C component"
|
|
);
|
|
assert_eq!(
|
|
world.get::<C>(e1),
|
|
Some(&C),
|
|
"new entity was spawned and received C component"
|
|
);
|
|
}
|
|
}
|