mirror of
https://github.com/bevyengine/bevy
synced 2024-12-30 06:53:13 +00:00
d1bd46d45e
# Objective After merging retained rendering world #15320, we now have a good way of creating a link between worlds (*HIYAA intensifies*). This means that `get_or_spawn` is no longer necessary for that function. Entity should be opaque as the warning above `get_or_spawn` says. This is also part of #15459. I'm deprecating `get_or_spawn_batch` in a different PR in order to keep the PR small in size. ## Solution Deprecate `get_or_spawn` and replace it with `get_entity` in most contexts. If it's possible to query `&RenderEntity`, then the entity is synced and `render_entity.id()` is initialized in the render world. ## Migration Guide If you are given an `Entity` and you want to do something with it, use `Commands.entity(...)` or `World.entity(...)`. If instead you want to spawn something use `Commands.spawn(...)` or `World.spawn(...)`. If you are not sure if an entity exists, you can always use `get_entity` and match on the `Option<...>` that is returned. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2354 lines
71 KiB
Rust
2354 lines
71 KiB
Rust
// FIXME(11590): remove this once the lint is fixed
|
|
#![allow(unsafe_op_in_unsafe_fn)]
|
|
#![doc = include_str!("../README.md")]
|
|
// `rustdoc_internals` is needed for `#[doc(fake_variadics)]`
|
|
#![allow(internal_features)]
|
|
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
|
|
#![allow(unsafe_code)]
|
|
#![doc(
|
|
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
|
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
|
)]
|
|
|
|
#[cfg(target_pointer_width = "16")]
|
|
compile_error!("bevy_ecs cannot safely compile for a 16-bit platform.");
|
|
|
|
extern crate alloc;
|
|
|
|
pub mod archetype;
|
|
pub mod batching;
|
|
pub mod bundle;
|
|
pub mod change_detection;
|
|
pub mod component;
|
|
pub mod entity;
|
|
pub mod event;
|
|
pub mod identifier;
|
|
pub mod intern;
|
|
pub mod label;
|
|
pub mod observer;
|
|
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 traversal;
|
|
pub mod world;
|
|
|
|
pub use bevy_ptr as ptr;
|
|
|
|
/// The ECS prelude.
|
|
///
|
|
/// This includes the most common types in this crate, re-exported for your convenience.
|
|
pub mod prelude {
|
|
#[doc(hidden)]
|
|
pub use crate::{
|
|
bundle::Bundle,
|
|
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
|
|
component::Component,
|
|
entity::{Entity, EntityMapper},
|
|
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
|
observer::{Observer, Trigger},
|
|
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
|
removal_detection::RemovedComponents,
|
|
schedule::{
|
|
apply_deferred, common_conditions::*, Condition, IntoSystemConfigs, IntoSystemSet,
|
|
IntoSystemSetConfigs, Schedule, Schedules, SystemSet,
|
|
},
|
|
system::{
|
|
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
|
|
NonSend, NonSendMut, ParallelCommands, ParamSet, Populated, Query, ReadOnlySystem, Res,
|
|
ResMut, Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder,
|
|
SystemParamFunction, WithParamWarnPolicy,
|
|
},
|
|
world::{
|
|
Command, EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
|
|
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
|
|
},
|
|
};
|
|
|
|
#[doc(hidden)]
|
|
#[cfg(feature = "bevy_reflect")]
|
|
pub use crate::reflect::{
|
|
AppTypeRegistry, ReflectComponent, ReflectFromWorld, ReflectResource,
|
|
};
|
|
|
|
#[doc(hidden)]
|
|
#[cfg(feature = "reflect_functions")]
|
|
pub use crate::reflect::AppFunctionRegistry;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate as bevy_ecs;
|
|
use crate::component::{RequiredComponents, RequiredComponentsError};
|
|
use crate::{
|
|
bundle::Bundle,
|
|
change_detection::Ref,
|
|
component::{Component, ComponentId},
|
|
entity::Entity,
|
|
prelude::Or,
|
|
query::{Added, Changed, FilteredAccess, QueryFilter, With, Without},
|
|
system::Resource,
|
|
world::{EntityMut, EntityRef, Mut, World},
|
|
};
|
|
use alloc::{sync::Arc, vec};
|
|
use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut};
|
|
use bevy_tasks::{ComputeTaskPool, TaskPool};
|
|
use bevy_utils::HashSet;
|
|
use core::{
|
|
any::TypeId,
|
|
marker::PhantomData,
|
|
num::NonZero,
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
use std::sync::Mutex;
|
|
|
|
#[derive(Component, Resource, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
|
struct A(usize);
|
|
#[derive(Component, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
|
struct B(usize);
|
|
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)]
|
|
struct C;
|
|
|
|
#[allow(dead_code)]
|
|
#[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);
|
|
}
|
|
}
|
|
|
|
// TODO: The compiler says the Debug and Clone are removed during dead code analysis. Investigate.
|
|
#[allow(dead_code)]
|
|
#[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, Hash, 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.register_component::<TableStored>(),
|
|
world.register_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).take::<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.register_component::<A>(),
|
|
world.register_component::<TableStored>(),
|
|
world.register_component::<SparseStored>(),
|
|
world.register_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).take::<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.register_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).take::<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)>()
|
|
.iter(&world)
|
|
.for_each(|(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::<HashSet<_>>();
|
|
assert!(ents.contains(&(e, A(123))));
|
|
assert!(ents.contains(&(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 = HashSet::new();
|
|
world
|
|
.query::<(Entity, &A)>()
|
|
.iter(&world)
|
|
.for_each(|(e, &i)| {
|
|
results.insert((e, i));
|
|
});
|
|
assert!(results.contains(&(e, A(123))));
|
|
assert!(results.contains(&(f, A(456))));
|
|
}
|
|
|
|
#[test]
|
|
fn par_for_each_dense() {
|
|
ComputeTaskPool::get_or_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::get_or_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>>()
|
|
.iter(&world)
|
|
.for_each(|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>>()
|
|
.iter(&world)
|
|
.for_each(|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::<HashSet<_>>();
|
|
assert!(ents.contains(&(e, None, A(123))));
|
|
assert!(ents.contains(&(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::<HashSet<_>>();
|
|
assert_eq!(
|
|
ents,
|
|
HashSet::from([(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::<HashSet<_>>(),
|
|
HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))])
|
|
);
|
|
assert_eq!(world.entity_mut(e1).take::<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::<HashSet<_>>(),
|
|
HashSet::from([(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::<HashSet<_>>(),
|
|
HashSet::from([(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).take::<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).take::<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).take::<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<_>>(),
|
|
&[],
|
|
"clearing trackers clears removals"
|
|
);
|
|
assert_eq!(
|
|
world.removed::<SparseStored>().collect::<Vec<_>>(),
|
|
&[],
|
|
"clearing trackers clears removals"
|
|
);
|
|
assert_eq!(
|
|
world.removed::<B>().collect::<Vec<_>>(),
|
|
&[],
|
|
"clearing 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: QueryFilter>(world: &mut World) -> HashSet<Entity> {
|
|
world
|
|
.query_filtered::<Entity, F>()
|
|
.iter(world)
|
|
.collect::<HashSet<Entity>>()
|
|
}
|
|
|
|
assert_eq!(
|
|
get_filtered::<Changed<A>>(&mut world),
|
|
HashSet::from([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),
|
|
HashSet::from([e3, e1]),
|
|
"changed entities list should not change"
|
|
);
|
|
|
|
// 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),
|
|
HashSet::from([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),
|
|
HashSet::from([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),
|
|
HashSet::from([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), HashSet::from([e4]));
|
|
assert_eq!(get_filtered::<Added<A>>(&mut world), HashSet::from([e4]));
|
|
|
|
world.entity_mut(e4).insert(A(1));
|
|
assert_eq!(get_filtered::<Changed<A>>(&mut world), HashSet::from([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), HashSet::from([e4]));
|
|
assert_eq!(get_filtered::<Added<B>>(&mut world), HashSet::from([e4]));
|
|
assert_eq!(get_filtered::<Changed<B>>(&mut world), HashSet::from([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: QueryFilter>(world: &mut World) -> HashSet<Entity> {
|
|
world
|
|
.query_filtered::<Entity, F>()
|
|
.iter(world)
|
|
.collect::<HashSet<Entity>>()
|
|
}
|
|
|
|
assert_eq!(
|
|
get_filtered::<Changed<SparseStored>>(&mut world),
|
|
HashSet::from([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), HashSet::from([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),
|
|
HashSet::from([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),
|
|
HashSet::from([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),
|
|
HashSet::from([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),
|
|
HashSet::from([e4])
|
|
);
|
|
assert_eq!(
|
|
get_filtered::<Added<SparseStored>>(&mut world),
|
|
HashSet::from([e4])
|
|
);
|
|
|
|
world.entity_mut(e4).insert(A(1));
|
|
assert_eq!(
|
|
get_filtered::<Changed<SparseStored>>(&mut world),
|
|
HashSet::from([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),
|
|
HashSet::from([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_entities();
|
|
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() {
|
|
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::<(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 take() {
|
|
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::<HashSet<_>>();
|
|
assert_eq!(results, HashSet::from([(1, "1"), (2, "2"), (3, "3"),]));
|
|
|
|
let removed_bundle = world.entity_mut(e2).take::<(B, TableStored)>().unwrap();
|
|
assert_eq!(removed_bundle, (B(2), TableStored("2")));
|
|
|
|
let results = query
|
|
.iter(&world)
|
|
.map(|(a, b)| (a.0, b.0))
|
|
.collect::<HashSet<_>>();
|
|
assert_eq!(results, HashSet::from([(1, "1"), (3, "3"),]));
|
|
|
|
let mut a_query = world.query::<&A>();
|
|
let results = a_query.iter(&world).map(|a| a.0).collect::<HashSet<_>>();
|
|
assert_eq!(results, HashSet::from([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]
|
|
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 entity_ref_and_mut_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(EntityRef, &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_entity_ref_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(&mut A, EntityRef)>();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn entity_ref_and_entity_mut_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(EntityRef, EntityMut)>();
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn entity_mut_and_entity_mut_query_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(EntityMut, EntityMut)>();
|
|
}
|
|
|
|
#[test]
|
|
fn entity_ref_and_entity_ref_query_no_panic() {
|
|
let mut world = World::new();
|
|
world.query::<(EntityRef, EntityRef)>();
|
|
}
|
|
|
|
#[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_component_write(a_id);
|
|
expected.add_component_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.iter(&world_a).for_each(|_| {});
|
|
query.iter(&world_b).for_each(|_| {});
|
|
}
|
|
|
|
#[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 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::from_raw_and_generation(e2.index(), NonZero::<u32>::new(2).unwrap());
|
|
|
|
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"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn required_components() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component)]
|
|
#[require(Z(new_z))]
|
|
struct Y {
|
|
value: String,
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Z(u32);
|
|
|
|
impl Default for Y {
|
|
fn default() -> Self {
|
|
Self {
|
|
value: "hello".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn new_z() -> Z {
|
|
Z(7)
|
|
}
|
|
|
|
let mut world = World::new();
|
|
let id = world.spawn(X).id();
|
|
assert_eq!(
|
|
"hello",
|
|
world.entity(id).get::<Y>().unwrap().value,
|
|
"Y should have the default value"
|
|
);
|
|
assert_eq!(
|
|
7,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the value provided by the constructor defined in Y"
|
|
);
|
|
|
|
let id = world
|
|
.spawn((
|
|
X,
|
|
Y {
|
|
value: "foo".to_string(),
|
|
},
|
|
))
|
|
.id();
|
|
assert_eq!(
|
|
"foo",
|
|
world.entity(id).get::<Y>().unwrap().value,
|
|
"Y should have the manually provided value"
|
|
);
|
|
assert_eq!(
|
|
7,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the value provided by the constructor defined in Y"
|
|
);
|
|
|
|
let id = world.spawn((X, Z(8))).id();
|
|
assert_eq!(
|
|
"hello",
|
|
world.entity(id).get::<Y>().unwrap().value,
|
|
"Y should have the default value"
|
|
);
|
|
assert_eq!(
|
|
8,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the manually provided value"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn generic_required_components() {
|
|
#[derive(Component)]
|
|
#[require(Y<usize>)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y<T> {
|
|
value: T,
|
|
}
|
|
|
|
let mut world = World::new();
|
|
let id = world.spawn(X).id();
|
|
assert_eq!(
|
|
0,
|
|
world.entity(id).get::<Y<usize>>().unwrap().value,
|
|
"Y should have the default value"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn required_components_spawn_nonexistent_hooks() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Resource)]
|
|
struct A(usize);
|
|
|
|
#[derive(Resource)]
|
|
struct I(usize);
|
|
|
|
let mut world = World::new();
|
|
world.insert_resource(A(0));
|
|
world.insert_resource(I(0));
|
|
world
|
|
.register_component_hooks::<Y>()
|
|
.on_add(|mut world, _, _| world.resource_mut::<A>().0 += 1)
|
|
.on_insert(|mut world, _, _| world.resource_mut::<I>().0 += 1);
|
|
|
|
// Spawn entity and ensure Y was added
|
|
assert!(world.spawn(X).contains::<Y>());
|
|
|
|
assert_eq!(world.resource::<A>().0, 1);
|
|
assert_eq!(world.resource::<I>().0, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn required_components_insert_existing_hooks() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Resource)]
|
|
struct A(usize);
|
|
|
|
#[derive(Resource)]
|
|
struct I(usize);
|
|
|
|
let mut world = World::new();
|
|
world.insert_resource(A(0));
|
|
world.insert_resource(I(0));
|
|
world
|
|
.register_component_hooks::<Y>()
|
|
.on_add(|mut world, _, _| world.resource_mut::<A>().0 += 1)
|
|
.on_insert(|mut world, _, _| world.resource_mut::<I>().0 += 1);
|
|
|
|
// Spawn entity and ensure Y was added
|
|
assert!(world.spawn_empty().insert(X).contains::<Y>());
|
|
|
|
assert_eq!(world.resource::<A>().0, 1);
|
|
assert_eq!(world.resource::<I>().0, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn required_components_take_leaves_required() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
let mut world = World::new();
|
|
let e = world.spawn(X).id();
|
|
let _ = world.entity_mut(e).take::<X>().unwrap();
|
|
assert!(world.entity_mut(e).contains::<Y>());
|
|
}
|
|
|
|
#[test]
|
|
fn required_components_retain_keeps_required() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Z;
|
|
|
|
let mut world = World::new();
|
|
let e = world.spawn((X, Z)).id();
|
|
world.entity_mut(e).retain::<X>();
|
|
assert!(world.entity_mut(e).contains::<X>());
|
|
assert!(world.entity_mut(e).contains::<Y>());
|
|
assert!(!world.entity_mut(e).contains::<Z>());
|
|
}
|
|
|
|
#[test]
|
|
fn required_components_spawn_then_insert_no_overwrite() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y(usize);
|
|
|
|
let mut world = World::new();
|
|
let id = world.spawn((X, Y(10))).id();
|
|
world.entity_mut(id).insert(X);
|
|
|
|
assert_eq!(
|
|
10,
|
|
world.entity(id).get::<Y>().unwrap().0,
|
|
"Y should still have the manually provided value"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dynamic_required_components() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
let mut world = World::new();
|
|
let x_id = world.register_component::<X>();
|
|
|
|
let mut e = world.spawn_empty();
|
|
|
|
// SAFETY: x_id is a valid component id
|
|
bevy_ptr::OwningPtr::make(X, |ptr| unsafe {
|
|
e.insert_by_id(x_id, ptr);
|
|
});
|
|
|
|
assert!(e.contains::<Y>());
|
|
}
|
|
|
|
#[test]
|
|
fn remove_component_and_his_runtime_required_components() {
|
|
#[derive(Component)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Z;
|
|
|
|
#[derive(Component)]
|
|
struct V;
|
|
|
|
let mut world = World::new();
|
|
world.register_required_components::<X, Y>();
|
|
world.register_required_components::<Y, Z>();
|
|
|
|
let e = world.spawn((X, V)).id();
|
|
assert!(world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
//check that `remove` works as expected
|
|
world.entity_mut(e).remove::<X>();
|
|
assert!(!world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
world.entity_mut(e).insert(X);
|
|
assert!(world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
//remove `X` again and ensure that `Y` and `Z` was removed too
|
|
world.entity_mut(e).remove_with_requires::<X>();
|
|
assert!(!world.entity(e).contains::<X>());
|
|
assert!(!world.entity(e).contains::<Y>());
|
|
assert!(!world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
}
|
|
|
|
#[test]
|
|
fn remove_component_and_his_required_components() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
#[require(Z)]
|
|
struct Y;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Z;
|
|
|
|
#[derive(Component)]
|
|
struct V;
|
|
|
|
let mut world = World::new();
|
|
|
|
let e = world.spawn((X, V)).id();
|
|
assert!(world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
//check that `remove` works as expected
|
|
world.entity_mut(e).remove::<X>();
|
|
assert!(!world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
world.entity_mut(e).insert(X);
|
|
assert!(world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
//remove `X` again and ensure that `Y` and `Z` was removed too
|
|
world.entity_mut(e).remove_with_requires::<X>();
|
|
assert!(!world.entity(e).contains::<X>());
|
|
assert!(!world.entity(e).contains::<Y>());
|
|
assert!(!world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
}
|
|
|
|
#[test]
|
|
fn remove_bundle_and_his_required_components() {
|
|
#[derive(Component, Default)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Component, Default)]
|
|
#[require(W)]
|
|
struct Z;
|
|
|
|
#[derive(Component, Default)]
|
|
struct W;
|
|
|
|
#[derive(Component)]
|
|
struct V;
|
|
|
|
#[derive(Bundle, Default)]
|
|
struct TestBundle {
|
|
x: X,
|
|
z: Z,
|
|
}
|
|
|
|
let mut world = World::new();
|
|
let e = world.spawn((TestBundle::default(), V)).id();
|
|
|
|
assert!(world.entity(e).contains::<X>());
|
|
assert!(world.entity(e).contains::<Y>());
|
|
assert!(world.entity(e).contains::<Z>());
|
|
assert!(world.entity(e).contains::<W>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
|
|
world.entity_mut(e).remove_with_requires::<TestBundle>();
|
|
assert!(!world.entity(e).contains::<X>());
|
|
assert!(!world.entity(e).contains::<Y>());
|
|
assert!(!world.entity(e).contains::<Z>());
|
|
assert!(!world.entity(e).contains::<W>());
|
|
assert!(world.entity(e).contains::<V>());
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_required_components() {
|
|
// Same as `required_components` test but with runtime registration
|
|
|
|
#[derive(Component)]
|
|
struct X;
|
|
|
|
#[derive(Component)]
|
|
struct Y {
|
|
value: String,
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Z(u32);
|
|
|
|
impl Default for Y {
|
|
fn default() -> Self {
|
|
Self {
|
|
value: "hello".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut world = World::new();
|
|
|
|
world.register_required_components::<X, Y>();
|
|
world.register_required_components_with::<Y, Z>(|| Z(7));
|
|
|
|
let id = world.spawn(X).id();
|
|
|
|
assert_eq!(
|
|
"hello",
|
|
world.entity(id).get::<Y>().unwrap().value,
|
|
"Y should have the default value"
|
|
);
|
|
assert_eq!(
|
|
7,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the value provided by the constructor defined in Y"
|
|
);
|
|
|
|
let id = world
|
|
.spawn((
|
|
X,
|
|
Y {
|
|
value: "foo".to_string(),
|
|
},
|
|
))
|
|
.id();
|
|
assert_eq!(
|
|
"foo",
|
|
world.entity(id).get::<Y>().unwrap().value,
|
|
"Y should have the manually provided value"
|
|
);
|
|
assert_eq!(
|
|
7,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the value provided by the constructor defined in Y"
|
|
);
|
|
|
|
let id = world.spawn((X, Z(8))).id();
|
|
assert_eq!(
|
|
"hello",
|
|
world.entity(id).get::<Y>().unwrap().value,
|
|
"Y should have the default value"
|
|
);
|
|
assert_eq!(
|
|
8,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the manually provided value"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_required_components_override_1() {
|
|
#[derive(Component)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Component)]
|
|
struct Z(u32);
|
|
|
|
let mut world = World::new();
|
|
|
|
// - X requires Y with default constructor
|
|
// - Y requires Z with custom constructor
|
|
// - X requires Z with custom constructor (more specific than X -> Y -> Z)
|
|
world.register_required_components::<X, Y>();
|
|
world.register_required_components_with::<Y, Z>(|| Z(5));
|
|
world.register_required_components_with::<X, Z>(|| Z(7));
|
|
|
|
let id = world.spawn(X).id();
|
|
|
|
assert_eq!(
|
|
7,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the value provided by the constructor defined in X"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_required_components_override_2() {
|
|
// Same as `runtime_required_components_override_1` test but with different registration order
|
|
|
|
#[derive(Component)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Component)]
|
|
struct Z(u32);
|
|
|
|
let mut world = World::new();
|
|
|
|
// - X requires Y with default constructor
|
|
// - X requires Z with custom constructor (more specific than X -> Y -> Z)
|
|
// - Y requires Z with custom constructor
|
|
world.register_required_components::<X, Y>();
|
|
world.register_required_components_with::<X, Z>(|| Z(7));
|
|
world.register_required_components_with::<Y, Z>(|| Z(5));
|
|
|
|
let id = world.spawn(X).id();
|
|
|
|
assert_eq!(
|
|
7,
|
|
world.entity(id).get::<Z>().unwrap().0,
|
|
"Z should have the value provided by the constructor defined in X"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_required_components_existing_archetype() {
|
|
#[derive(Component)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
let mut world = World::new();
|
|
|
|
// Registering required components after the archetype has already been created should panic.
|
|
// This may change in the future.
|
|
world.spawn(X);
|
|
assert!(matches!(
|
|
world.try_register_required_components::<X, Y>(),
|
|
Err(RequiredComponentsError::ArchetypeExists(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_required_components_fail_with_duplicate() {
|
|
#[derive(Component)]
|
|
#[require(Y)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
let mut world = World::new();
|
|
|
|
// This should fail: Tried to register Y as a requirement for X, but the requirement already exists.
|
|
assert!(matches!(
|
|
world.try_register_required_components::<X, Y>(),
|
|
Err(RequiredComponentsError::DuplicateRegistration(_, _))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn required_components_inheritance_depth() {
|
|
// Test that inheritance depths are computed correctly for requirements.
|
|
//
|
|
// Requirements with `require` attribute:
|
|
//
|
|
// A -> B -> C
|
|
// 0 1
|
|
//
|
|
// Runtime requirements:
|
|
//
|
|
// X -> A -> B -> C
|
|
// 0 1 2
|
|
//
|
|
// X -> Y -> Z -> B -> C
|
|
// 0 1 2 3
|
|
|
|
#[derive(Component, Default)]
|
|
#[require(B)]
|
|
struct A;
|
|
|
|
#[derive(Component, Default)]
|
|
#[require(C)]
|
|
struct B;
|
|
|
|
#[derive(Component, Default)]
|
|
struct C;
|
|
|
|
#[derive(Component, Default)]
|
|
struct X;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Y;
|
|
|
|
#[derive(Component, Default)]
|
|
struct Z;
|
|
|
|
let mut world = World::new();
|
|
|
|
let a = world.register_component::<A>();
|
|
let b = world.register_component::<B>();
|
|
let c = world.register_component::<C>();
|
|
let y = world.register_component::<Y>();
|
|
let z = world.register_component::<Z>();
|
|
|
|
world.register_required_components::<X, A>();
|
|
world.register_required_components::<X, Y>();
|
|
world.register_required_components::<Y, Z>();
|
|
world.register_required_components::<Z, B>();
|
|
|
|
world.spawn(X);
|
|
|
|
let required_a = world.get_required_components::<A>().unwrap();
|
|
let required_b = world.get_required_components::<B>().unwrap();
|
|
let required_c = world.get_required_components::<C>().unwrap();
|
|
let required_x = world.get_required_components::<X>().unwrap();
|
|
let required_y = world.get_required_components::<Y>().unwrap();
|
|
let required_z = world.get_required_components::<Z>().unwrap();
|
|
|
|
/// Returns the component IDs and inheritance depths of the required components
|
|
/// in ascending order based on the component ID.
|
|
fn to_vec(required: &RequiredComponents) -> Vec<(ComponentId, u16)> {
|
|
let mut vec = required
|
|
.0
|
|
.iter()
|
|
.map(|(id, component)| (*id, component.inheritance_depth))
|
|
.collect::<Vec<_>>();
|
|
vec.sort_by_key(|(id, _)| *id);
|
|
vec
|
|
}
|
|
|
|
// Check that the inheritance depths are correct for each component.
|
|
assert_eq!(to_vec(required_a), vec![(b, 0), (c, 1)]);
|
|
assert_eq!(to_vec(required_b), vec![(c, 0)]);
|
|
assert_eq!(to_vec(required_c), vec![]);
|
|
assert_eq!(
|
|
to_vec(required_x),
|
|
vec![(a, 0), (b, 1), (c, 2), (y, 0), (z, 1)]
|
|
);
|
|
assert_eq!(to_vec(required_y), vec![(b, 1), (c, 2), (z, 0)]);
|
|
assert_eq!(to_vec(required_z), vec![(b, 0), (c, 1)]);
|
|
}
|
|
|
|
// These structs are primarily compilation tests to test the derive macros. Because they are
|
|
// never constructed, we have to manually silence the `dead_code` lint.
|
|
#[allow(dead_code)]
|
|
#[derive(Component)]
|
|
struct ComponentA(u32);
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Component)]
|
|
struct ComponentB(u32);
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Bundle)]
|
|
struct Simple(ComponentA);
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Bundle)]
|
|
struct Tuple(Simple, ComponentB);
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Bundle)]
|
|
struct Record {
|
|
field0: Simple,
|
|
field1: ComponentB,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Component, VisitEntities, VisitEntitiesMut)]
|
|
struct MyEntities {
|
|
entities: Vec<Entity>,
|
|
another_one: Entity,
|
|
maybe_entity: Option<Entity>,
|
|
#[visit_entities(ignore)]
|
|
something_else: String,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Component, VisitEntities, VisitEntitiesMut)]
|
|
struct MyEntitiesTuple(Vec<Entity>, Entity, #[visit_entities(ignore)] usize);
|
|
}
|