diff --git a/Cargo.toml b/Cargo.toml index e76ef521..397bf2c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,11 @@ name = "systems" path = "./examples/guide/systems.rs" required-features = ["std", "rayon"] +[[example]] +name = "springs" +path = "./examples/guide/springs.rs" +required-features = ["std"] + [[bench]] name = "benchmarks" harness = false diff --git a/examples/guide/relations.rs b/examples/guide/relations.rs index e449a4e9..c0d16552 100644 --- a/examples/guide/relations.rs +++ b/examples/guide/relations.rs @@ -1,22 +1,23 @@ -use flax::{ - components::{child_of, name}, - relation::RelationExt, - *, -}; +use flax::{components::name, relation::RelationExt, *}; use itertools::Itertools; use tracing_subscriber::{prelude::*, registry}; use tracing_tree::HierarchicalLayer; fn main() -> anyhow::Result<()> { registry().with(HierarchicalLayer::default()).init(); + basic()?; + exclusive()?; + Ok(()) +} + +fn basic() -> anyhow::Result<()> { + let mut world = World::new(); // ANCHOR: relation_basic component! { - spring_joint(other): f32 => [Debuggable], + child_of(id): (), } - let mut world = World::new(); - let parent = Entity::builder() .set(name(), "Parent".into()) .spawn(&mut world); @@ -30,7 +31,6 @@ fn main() -> anyhow::Result<()> { .set(name(), "Child2".into()) .set_default(child_of(parent)) .spawn(&mut world); - // ANCHOR_END: relation_basic // ANCHOR: many_to_many @@ -42,13 +42,12 @@ fn main() -> anyhow::Result<()> { tracing::info!("World: {world:#?}"); - // Connect child1 with two entities via springs of different strength - world.set(child1, spring_joint(child2), 1.5)?; - world.set(child1, spring_joint(parent2), 7.4)?; + // Give child1 yet one more parent + world.set(child1, child_of(parent2), ())?; tracing::info!( "Connections from child1({child1}): {:?}", - Query::new(relations_like(spring_joint)) + Query::new(relations_like(child_of)) .borrow(&world) .get(child1)? .collect_vec() @@ -56,35 +55,28 @@ fn main() -> anyhow::Result<()> { // ANCHOR_END: many_to_many // ANCHOR: query - - let children_of_parent = Query::new(entity_ids()) + // Mathes a relation exactly + let children_of_parent: Vec = Query::new(entity_ids()) .with(child_of(parent)) - .borrow(&world) - .iter() - .collect_vec(); + .collect_vec(&world); tracing::info!("Children: {children_of_parent:?}"); - let all_children = Query::new(entity_ids()) + // Matches a relation with any parent + let all_children: Vec = Query::new(entity_ids()) .filter(child_of.with_relation()) - .borrow(&world) - .iter() - .collect_vec(); + .collect_vec(&world); tracing::info!("Children: {all_children:?}"); let roots = Query::new(entity_ids()) .filter(child_of.without_relation()) - .borrow(&world) - .iter() - .collect_vec(); + .collect_vec(&world); tracing::info!("Roots: {roots:?}"); - // ANCHOR_END: query // ANCHOR: lifetime - tracing::info!( "has relation to: {parent2}: {}", world.has(child1, child_of(parent2)) @@ -105,3 +97,36 @@ fn main() -> anyhow::Result<()> { Ok(()) } + +fn exclusive() -> anyhow::Result<()> { + let mut world = World::new(); + + // ANCHOR: exclusive + component! { + child_of(parent): () => [ Exclusive ], + } + + let id1 = Entity::builder().spawn(&mut world); + let id2 = Entity::builder().spawn(&mut world); + + let id3 = Entity::builder() + .set_default(child_of(id1)) + .spawn(&mut world); + + let entity = world.entity_mut(id3).unwrap(); + + tracing::info!( + "relations of {id3}: {:?}", + entity.relations(child_of).map(|v| v.0).collect_vec() + ); + + world.set(id3, child_of(id2), ()).unwrap(); + + let entity = world.entity_mut(id3).unwrap(); + tracing::info!( + "relations of {id3}: {:?}", + entity.relations(child_of).map(|v| v.0).collect_vec() + ); + // ANCHOR_END: exclusive + Ok(()) +} diff --git a/examples/guide/springs.rs b/examples/guide/springs.rs new file mode 100644 index 00000000..dff38271 --- /dev/null +++ b/examples/guide/springs.rs @@ -0,0 +1,68 @@ +use flax::{component, components::name, entity_ids, Dfs, Entity, FetchExt, Query, World}; +use glam::{vec2, Vec2}; +use tracing_subscriber::{prelude::*, registry}; +use tracing_tree::HierarchicalLayer; + +fn main() { + registry().with(HierarchicalLayer::default()).init(); + + let mut world = World::new(); + + // ANCHOR: main + struct Spring { + strength: f32, + length: f32, + } + + impl Spring { + fn new(strength: f32, length: f32) -> Self { + Self { strength, length } + } + } + component! { + spring_joint(id): Spring, + position: Vec2, + } + + let id1 = Entity::builder() + .set(name(), "a".into()) + .set(position(), vec2(1.0, 4.0)) + .spawn(&mut world); + + // Connect id2 to id1 with a spring of strength 2.0 + let id2 = Entity::builder() + .set(name(), "b".into()) + .set(spring_joint(id1), Spring::new(2.0, 1.0)) + .set(position(), vec2(2.0, 0.0)) + .spawn(&mut world); + + let _id3 = Entity::builder() + .set(name(), "c".into()) + .set(spring_joint(id1), Spring::new(2.0, 3.0)) + .set(position(), vec2(2.0, 3.0)) + .spawn(&mut world); + + let _id4 = Entity::builder() + .set(name(), "d".into()) + .set(spring_joint(id2), Spring::new(5.0, 0.5)) + .set(position(), vec2(1.0, 0.0)) + .spawn(&mut world); + + let mut query = Query::new((entity_ids(), name().cloned(), position())) + .with_strategy(Dfs::new(spring_joint)); + + query + .borrow(&world) + .traverse(&None, |(id, name, &pos), strength, parent| { + if let (Some(spring), Some((parent_name, parent_pos))) = (strength, parent) { + let distance = pos.distance(*parent_pos) - spring.length; + let force = distance * spring.strength; + tracing::info!("spring acting with {force:.1}N between {parent_name} and {name}"); + } else { + tracing::info!(%id, name, "root"); + } + + Some((name, pos)) + }); + // ANCHOR_END: main +} diff --git a/guide/src/diving_deeper/dynamic_components.md b/guide/src/diving_deeper/dynamic_components.md index 832df937..6ac08e2c 100644 --- a/guide/src/diving_deeper/dynamic_components.md +++ b/guide/src/diving_deeper/dynamic_components.md @@ -43,5 +43,5 @@ entity as the generation is not needed. {{ #include ../../../examples/guide/dynamic_components.rs:relation }} ``` -When despawning either the relation component or object entity, the "parent", +When despawning either the relation component or target entity, the "parent", the component is removed from all entities. diff --git a/guide/src/fundamentals/relations.md b/guide/src/fundamentals/relations.md index 3565d6e6..25c3a90c 100644 --- a/guide/src/fundamentals/relations.md +++ b/guide/src/fundamentals/relations.md @@ -1,26 +1,31 @@ # Relations -A relation is a component which *links* to another `Entity`, similar to a foreign key in a database. +A relation is a component which *links* to another `Entity`, similar to a foreign key in a database. This can be used to construct different kinds of graphs and trees inside the ECS. -The linked entity is referred to as the `object` of a relation, while the entity the component is attached to is called the `subject`. +The links between entities are managed by the ECS itself and will always be valid, see [Lifetime](#lifetime). + +The linked entity is referred to as the `target` of a relation, while the entity the component is attached to is called the `subject`. This allows forming hierarchies such as *parent-child* relations for transforms and UI, as well as arbitrary graphs. -A relation is used as a *parameterized* component, which requires an `Entity` to be fully instantiated. +See the [`child_of`](https://docs.rs/flax/latest/flax/components/fn.child_of.html) relation for an example of a parent-child relation which uses the parent entity as the relation's *target*. Relations are most easily declared using the -[component](https://docs.rs/flax/latest/flax/macro.component.html) macro. +[component](https://docs.rs/flax/latest/flax/macro.component.html) macro, but can be constructed dynamically as well. See [dynamic_components](../diving_deeper/dynamic_components.md) + +For example, declaring a child relationship that connects to a parent can be done like so: ```rust {{ #include ../../../examples/guide/relations.rs:relation_basic }} ``` -Important to note is that the same `child_of` component with different `object` -arguments are distinct, and can as such exist on an entity at the same time, -allowing many-many relationships between entities; +The parameter to the component function determines the target entity of the relation. + +Since the value of the relation in this case is `()`, `set_default` can be used as a shorthand over `set` -There is no limitation of the number of relations an entity can have. As such, -an entity can have multiple relations to other entities, allowing for any kind of graphs inside the ecs. +Two relations of the same type but with different *targets* behave like two separate components and will not interfere. This allows having many-to-many relationships between entities, if so desired. + +This allows constructing many different kinds of graphs inside the ECS. ```rust {{ #include ../../../examples/guide/relations.rs:many_to_many }} @@ -36,12 +41,35 @@ See the [Graphs](../query/graphs.md) chapter in queries. ```rust {{ #include ../../../examples/guide/relations.rs:query }} ``` +## Associated values + +In addition to linking between entities, a relation can also store additional data just like a component. This can be used to create weighted graphs or storing other additional information such as physical joint parameters. + +Since relations behave like separate components, each value on a relation is specific to that link, and as such saves you the hassle of managing a separate list of values for each connection on an entity. + +The following shows a more complete example of how to traverse and calculate the forces between entities connected via springs using hook's law. + +```rust +{{ #include ../../../examples/guide/springs.rs:main }} +``` + +# Exclusive relations + +Relations can be declared as exclusive, which means that only one relation of that type can exist on an entity at a time. This is useful for cases where you want to have a single parent or outgoing connection. + +**Note**: This does not prevent multiple entities from referencing the same entity, but rather an entity referencing multiple entities. + +When a new relation is added to an entity, any existing relation of the same type will be removed. + +This is the case for the included [`child_of`](https://docs.rs/flax/latest/flax/components/fn.child_of.html) relation. + +```rust +{{ #include ../../../examples/guide/relations.rs:exclusive }} +``` ## Lifetime -When an entity is despawned, all relations to it present on other components -will be removed and dropped. As such, no entity will have a relation to an -entity which does not exist. +Relations are managed by the ECS and will automatically be cleaned up. When an entity is despawned all relations which reference it will be removed from the ECS. As such, a relation will never point to an invalid entity. ```rust {{ #include ../../../examples/guide/relations.rs:lifetime }} diff --git a/guide/src/introduction.md b/guide/src/introduction.md index 3b7bfd06..bb9d3cdd 100644 --- a/guide/src/introduction.md +++ b/guide/src/introduction.md @@ -17,7 +17,7 @@ and new functionality can be added to existing entities and components. In Flax, there are 3 fundamental building blocks. -[Entity](https://docs.rs/flax/latest/flax/struct.Entity.html). A unique identifier for the objects of the program. Has a managed lifecycle. +[Entity](https://docs.rs/flax/latest/flax/struct.Entity.html). A unique identifier for the entities of the program. Has a managed lifecycle. [Component](https://docs.rs/flax/latest/flax/struct.Component.html), data which can be added to an Entity. Has a unique Id, which works as the key for storing diff --git a/recipes.json b/recipes.json index 333954cd..e106ce6f 100644 --- a/recipes.json +++ b/recipes.json @@ -21,7 +21,7 @@ "kind": "term" }, "test-miri": { - "cmd": "cargo miri nextest run -j 8 --no-default-features --features std,serde,flume,derive" + "cmd": "cargo +nightly miri nextest run -j 8 --no-default-features --features std,serde,flume,derive" }, "doc": { "cmd": "cargo doc --all-features --open" diff --git a/src/archetypes.rs b/src/archetypes.rs index e34fd531..9648f3c1 100644 --- a/src/archetypes.rs +++ b/src/archetypes.rs @@ -215,6 +215,11 @@ impl Archetypes { let dst = self.get_mut(dst_id); dst.remove_link(component); } + + for (key, &dst_id) in &arch.outgoing { + self.get_mut(dst_id).incoming.remove(key); + } + self.gen = self.gen.wrapping_add(1); arch diff --git a/src/cascade/mod.rs b/src/cascade/mod.rs index 3250aa43..49b2d7e6 100644 --- a/src/cascade/mod.rs +++ b/src/cascade/mod.rs @@ -89,15 +89,15 @@ fn get_ordered_archetypes( }, } - // Find relations to other objects, and visit them as well + // Find relations to other targets, and visit them as well let relations = arch.relations_like(relation); let mut is_reachable = false; let mut is_root = true; for (key, _) in relations { is_root = false; - let parent = key.object.unwrap(); + let target = key.target.unwrap(); - let loc = world.location(parent).unwrap(); + let loc = world.location(target).unwrap(); // Part of the visited set if let Some(arch) = archetypes.get(&loc.arch_id) { if get_ordered_archetypes(world, archetypes, arch_id, arch, relation, visited, ordered) diff --git a/src/component.rs b/src/component.rs index fe66a502..197a0975 100644 --- a/src/component.rs +++ b/src/component.rs @@ -37,8 +37,8 @@ impl ComponentValue for T where T: Send + Sync + 'static {} #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ComponentKey { pub(crate) id: Entity, - /// The object entity if the component is a relation - pub(crate) object: Option, + /// The target entity if the component is a relation + pub(crate) target: Option, } #[cfg(feature = "serde")] @@ -49,7 +49,7 @@ impl Serialize for ComponentKey { { let mut seq = serializer.serialize_tuple_struct("ComponentId", 2)?; seq.serialize_field(&self.id)?; - seq.serialize_field(&self.object)?; + seq.serialize_field(&self.target)?; seq.end() } @@ -71,7 +71,7 @@ impl<'de> Deserialize<'de> for ComponentKey { ) -> smallvec::alloc::fmt::Result { write!( formatter, - "A tuple of a component id and optional relation object" + "A tuple of a component id and optional relation target" ) } @@ -82,11 +82,11 @@ impl<'de> Deserialize<'de> for ComponentKey { let id = seq .next_element()? .ok_or_else(|| Error::invalid_length(0, &self))?; - let object = seq + let target = seq .next_element()? .ok_or_else(|| Error::invalid_length(1, &self))?; - Ok(ComponentKey::new(id, object)) + Ok(ComponentKey::new(id, target)) } } @@ -98,17 +98,17 @@ impl ComponentKey { /// Returns true if the component is a relation #[inline] pub fn is_relation(&self) -> bool { - self.object.is_some() + self.target.is_some() } - pub(crate) fn new(id: Entity, object: Option) -> Self { - Self { id, object } + pub(crate) fn new(id: Entity, target: Option) -> Self { + Self { id, target } } #[inline] - /// Returns the object of the relation - pub fn object(&self) -> Option { - self.object + /// Returns the target of the relation + pub fn target(&self) -> Option { + self.target } #[inline] @@ -126,7 +126,7 @@ impl Display for ComponentKey { impl Debug for ComponentKey { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.object { + match self.target { Some(s) => write!(f, "{}({s})", self.id), None => Debug::fmt(&self.id, f), } @@ -136,9 +136,8 @@ impl Debug for ComponentKey { /// Type alias for a function which instantiates a component pub type ComponentFn = fn() -> Component; -/// Type alias for a function which instantiates a relation with the specified -/// object -pub type RelationFn = fn(object: Entity) -> Component; +/// Type alias for a function which instantiates a relation with the specified target +pub type RelationFn = fn(target: Entity) -> Component; crate::component! { pub(crate) dummy, @@ -175,8 +174,8 @@ impl Clone for Component { impl fmt::Debug for Component { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.key.object { - Some(object) => write!(f, "{}({}) {}", self.vtable.name, object, self.key.id()), + match self.key.target { + Some(target) => write!(f, "{}({}) {}", self.vtable.name, target, self.key.id()), None => write!(f, "{} {}", self.vtable.name, self.key.id), } } @@ -184,8 +183,8 @@ impl fmt::Debug for Component { impl Display for Component { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.key.object { - Some(object) => write!(f, "{}({}) {}", self.vtable.name, object, self.key.id()), + match self.key.target { + Some(target) => write!(f, "{}({}) {}", self.vtable.name, target, self.key.id()), None => write!(f, "{} {}", self.vtable.name, self.key.id), } } @@ -237,7 +236,7 @@ impl Component { } /// Get the component's base id. - /// This is the id without any relation object + /// This is the id without any relation target #[inline(always)] pub fn id(&self) -> Entity { self.key.id @@ -326,9 +325,9 @@ impl RelationExt for Component { self.key().id } - fn of(&self, object: Entity) -> Component { + fn of(&self, target: Entity) -> Component { Self { - key: ComponentKey::new(self.key().id, Some(object)), + key: ComponentKey::new(self.key().id, Some(target)), ..*self } } @@ -370,8 +369,8 @@ impl PartialEq for ComponentDesc { impl core::fmt::Debug for ComponentDesc { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self.key.object { - Some(object) => write!(f, "{}({}) {}", self.vtable.name, object, self.key.id()), + match self.key.target { + Some(target) => write!(f, "{}({}) {}", self.vtable.name, target, self.key.id()), None => write!(f, "{} {}", self.vtable.name, self.key.id), } } @@ -464,7 +463,7 @@ impl ComponentDesc { #[inline] pub(crate) fn is_relation(&self) -> bool { - self.key.object.is_some() + self.key.target.is_some() } pub(crate) fn get_meta(&self) -> ComponentBuffer { diff --git a/src/entity/mod.rs b/src/entity/mod.rs index de4796fd..41801b08 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -18,7 +18,6 @@ pub(crate) const DEFAULT_GEN: EntityGen = unsafe { EntityGen::new_unchecked(1) } #[derive(PartialOrd, Clone, Copy, PartialEq, Eq, Ord, Hash)] pub struct Entity { pub(crate) index: EntityIndex, - /// Object pub(crate) gen: EntityGen, pub(crate) kind: EntityKind, } diff --git a/src/fetch/mod.rs b/src/fetch/mod.rs index cae88e23..fabdec53 100644 --- a/src/fetch/mod.rs +++ b/src/fetch/mod.rs @@ -35,7 +35,7 @@ pub use map::Map; pub use maybe_mut::{MaybeMut, MutGuard}; pub use opt::*; pub use read_only::*; -pub use relations::{relations_like, Relations, RelationsIter}; +pub use relations::{nth_relation, relations_like, Relations, RelationsIter}; pub use satisfied::Satisfied; pub use source::Source; pub use transform::{Added, Modified, TransformFetch}; diff --git a/src/fetch/relations.rs b/src/fetch/relations.rs index 5f582f35..7ec3a933 100644 --- a/src/fetch/relations.rs +++ b/src/fetch/relations.rs @@ -8,10 +8,10 @@ use smallvec::SmallVec; use crate::{ archetype::{CellGuard, Slot}, - component::{dummy, ComponentValue}, - relation::RelationExt, + component::ComponentValue, + relation::{Relation, RelationExt}, system::{Access, AccessKind}, - Component, Entity, Fetch, FetchItem, + Entity, Fetch, FetchItem, }; use super::{FetchAccessData, FetchPrepareData, PreparedFetch}; @@ -19,7 +19,7 @@ use super::{FetchAccessData, FetchPrepareData, PreparedFetch}; /// Returns a list of relations of a specified type #[derive(Debug, Clone)] pub struct Relations { - component: Component, + relation: Relation, } impl<'w, T> Fetch<'w> for Relations @@ -33,8 +33,8 @@ where fn prepare(&self, data: FetchPrepareData<'w>) -> Option { let borrows: SmallVec<[_; 4]> = { data.arch - .relations_like(self.component.id()) - .map(|(desc, cell)| (desc.object.unwrap(), cell.borrow())) + .relations_like(self.relation.id()) + .map(|(desc, cell)| (desc.target.unwrap(), cell.borrow())) .collect() }; @@ -46,24 +46,20 @@ where } fn access(&self, data: FetchAccessData, dst: &mut Vec) { - let relation = self.component.key().id; - dst.extend(data.arch.cells().keys().filter_map(move |k| { - if k.object.is_some() && k.id == relation { - return Some(Access { - kind: AccessKind::Archetype { - id: data.arch_id, - component: *k, - }, - mutable: false, - }); - } - - None - })) + let relation = self.relation.id(); + let val = data.arch.relations_like(relation).map(|v| Access { + kind: AccessKind::Archetype { + id: data.arch_id, + component: *v.0, + }, + mutable: false, + }); + + dst.extend(val); } fn describe(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "relations({})", self.component.name()) + write!(f, "relations({})", self.relation) } } @@ -107,7 +103,7 @@ where } } -/// Iterates the relation object and data for the yielded query item +/// Iterates the relation targets and data for the yielded query item pub struct RelationsIter<'a, T> { borrows: slice::Iter<'a, (Entity, CellGuard<'a, [T]>)>, slot: Slot, @@ -123,9 +119,115 @@ impl<'a, T> Iterator for RelationsIter<'a, T> { } } -/// Query all relations of the specified kind +/// Query all relations of the specified kind. +/// +/// **Note**: This still matches if there are `0` relations. pub fn relations_like(relation: impl RelationExt) -> Relations { Relations { - component: relation.of(dummy()), + relation: relation.as_relation(), + } +} + +/// Query the nth relation of the specified kind. +/// +/// This is useful for [`Exclusive`](crate::metadata::Exclusive) relations where there is only one parent +/// +/// **Note**: Fails to match if there is no nth relation, prefer using [`opt`](crate::FetchExt::opt) for +/// optional relations. +pub fn nth_relation(relation: impl RelationExt, n: usize) -> NthRelation { + NthRelation { + relation: relation.as_relation(), + n, + } +} + +/// Returns the *nth* relation of a specified type +#[derive(Debug, Clone)] +pub struct NthRelation { + relation: Relation, + n: usize, +} + +impl<'w, T> Fetch<'w> for NthRelation +where + T: ComponentValue, +{ + const MUTABLE: bool = false; + + type Prepared = PreparedNthRelation<'w, T>; + + fn prepare(&self, data: FetchPrepareData<'w>) -> Option { + let borrow = data + .arch + .relations_like(self.relation.id) + .nth(self.n) + .map(|(desc, cell)| (desc.target.unwrap(), cell.borrow()))?; + + Some(PreparedNthRelation { borrow }) + } + + fn filter_arch(&self, _: FetchAccessData) -> bool { + true + } + + fn access(&self, data: FetchAccessData, dst: &mut Vec) { + let relation = self.relation.id; + let val = data + .arch + .relations_like(relation) + .nth(self.n) + .map(|v| Access { + kind: AccessKind::Archetype { + id: data.arch_id, + component: *v.0, + }, + mutable: false, + }); + + dst.extend(val); + } + + fn describe(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "relations({})", self.relation) + } +} + +impl<'q, T: ComponentValue> FetchItem<'q> for NthRelation { + type Item = (Entity, &'q T); +} + +#[doc(hidden)] +pub struct PreparedNthRelation<'a, T> { + borrow: (Entity, CellGuard<'a, [T]>), +} + +pub struct NthBatch<'a, T> { + borrow: *const (Entity, CellGuard<'a, [T]>), + slot: Slot, +} + +impl<'w, 'q, T> PreparedFetch<'q> for PreparedNthRelation<'w, T> +where + T: ComponentValue, +{ + type Item = (Entity, &'q T); + + type Chunk = NthBatch<'q, T>; + + unsafe fn create_chunk(&'q mut self, slice: crate::archetype::Slice) -> Self::Chunk { + NthBatch { + borrow: &self.borrow, + slot: slice.start, + } + } + + unsafe fn fetch_next(chunk: &mut Self::Chunk) -> Self::Item { + let slot = chunk.slot; + chunk.slot += 1; + + let (id, borrow) = unsafe { &*chunk.borrow }; + + let borrow = &borrow.get()[slot]; + (*id, borrow) } } diff --git a/src/fetch/source.rs b/src/fetch/source.rs index df91abbe..2d9de6d6 100644 --- a/src/fetch/source.rs +++ b/src/fetch/source.rs @@ -20,7 +20,7 @@ pub trait FetchSource { fn describe(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result; } -/// Selects the fetch value from the first parent/object of the specified relation +/// Selects the fetch value from the first target of the specified relation pub struct FromRelation { pub(crate) relation: Entity, pub(crate) name: &'static str, @@ -33,11 +33,11 @@ impl FetchSource for FromRelation { data: FetchAccessData<'a>, ) -> Option<(ArchetypeId, &'a Archetype, Option)> { for (key, _) in data.arch.relations_like(self.relation) { - let object = key.object().unwrap(); + let target = key.target.unwrap(); let loc = data .world - .location(object) + .location(target) .expect("Relation contains invalid entity"); let arch = data.world.archetypes.get(loc.arch_id); @@ -95,11 +95,11 @@ fn traverse_resolve<'a, 'w, Q: Fetch<'w>>( } for (key, _) in data.arch.relations_like(relation) { - let object = key.object().unwrap(); + let target = key.target.unwrap(); let loc = data .world - .location(object) + .location(target) .expect("Relation contains invalid entity"); let data = FetchAccessData { diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 3a21bafc..bc72fd34 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -166,7 +166,7 @@ gen_bitops! { Nothing[]; Or[T]; RemovedFilter[T]; - WithObject[]; + WithTarget[]; WithRelation[]; With[]; WithoutRelation[]; @@ -319,15 +319,15 @@ impl StaticFilter for Without { #[derive(Debug, Clone)] /// Yields all entities with the relation of the specified kind -pub(crate) struct WithObject { - pub(crate) object: Entity, +pub(crate) struct WithTarget { + pub(crate) target: Entity, } -impl<'q> FetchItem<'q> for WithObject { +impl<'q> FetchItem<'q> for WithTarget { type Item = (); } -impl<'w> Fetch<'w> for WithObject { +impl<'w> Fetch<'w> for WithTarget { const MUTABLE: bool = false; type Prepared = All; @@ -341,18 +341,18 @@ impl<'w> Fetch<'w> for WithObject { } fn describe(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "with (*)({})", self.object) + write!(f, "with (*)({})", self.target) } #[inline] fn access(&self, _: FetchAccessData, _: &mut Vec) {} } -impl StaticFilter for WithObject { +impl StaticFilter for WithTarget { fn filter_static(&self, arch: &Archetype) -> bool { arch.components().any(|v| { - if let Some(v) = v.key().object { - if v == self.object { + if let Some(v) = v.key().target { + if v == self.target { return true; } } diff --git a/src/macros.rs b/src/macros.rs index cf9953c1..d3efa061 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -11,21 +11,26 @@ /// ```rust,ignore /// flax::component! { /// // component -/// name: type, // component +/// pub name: type, // component /// /// // component with metadata/reflection -/// name: type => [ Metadata, ... ], +/// pub(crate) name: type => [ Metadata, ... ], /// /// // relational component -/// name(object): type +/// name(target): type /// /// // relation component with metadata/reflection -/// name(object): type => [ Metadata, ... ] +/// name(target): type => [ Metadata, ... ] /// /// // static entity /// name, /// } /// ``` +/// # Visibility +/// +/// Components are by default only visible to the module they were declared in. However, any +/// visibility qualifier can be added before the name to expose it. +/// /// /// # Metadata /// @@ -45,7 +50,7 @@ /// /// # Relations /// A component can be associated to another entity, which declares a relation of the component -/// type between the subject (entity which has the component), and the object (the associated +/// type between the subject (entity which has the component), and the target (the associated /// entity). /// /// Relation components with different associated entities are distinct. @@ -96,7 +101,7 @@ /// another *generationless* entity id. /// /// This allows for the parameterization of components with component ids being -/// distinct with across different objects. +/// distinct with across different target. macro_rules! component { // Relations ($(#[$outer:meta])* $vis: vis $name: ident( $obj: ident ): $ty: ty $(=> [$($metadata: ty),*])?, $($rest:tt)*) => { diff --git a/src/query/dfs.rs b/src/query/dfs.rs index 1d6f9ca9..fa0b5d0b 100644 --- a/src/query/dfs.rs +++ b/src/query/dfs.rs @@ -112,9 +112,9 @@ impl State { let mut root = true; for (key, _) in arch.relations_like(relation) { root = false; - let object = key.object.unwrap(); + let target = key.target.unwrap(); - self.edges.entry(object).or_default().push(index); + self.edges.entry(target).or_default().push(index); } if root { @@ -292,7 +292,7 @@ where let arch_id = dfs.state.archetypes[arch_index]; let arch = world.archetypes.get(arch_id); - let edge = arch.borrow::(ComponentKey::new(id, Some(dfs.relation))); + let edge = arch.borrow::(ComponentKey::new(dfs.relation, Some(id))); let p = unsafe { &mut *prepared.add(arch_index) }; diff --git a/src/query/topo.rs b/src/query/topo.rs index ed6865a7..4d504114 100644 --- a/src/query/topo.rs +++ b/src/query/topo.rs @@ -69,8 +69,8 @@ impl State { .relations_like(relation) .map(|(key, _)| { assert_eq!(key.id, relation); - let object = key.object().unwrap(); - let loc = world.location(object).unwrap(); + let target = key.target.unwrap(); + let loc = world.location(target).unwrap(); loc.arch_id }) .collect(); diff --git a/src/query/walk.rs b/src/query/walk.rs index eb49f3e3..7b2f3afb 100644 --- a/src/query/walk.rs +++ b/src/query/walk.rs @@ -146,9 +146,9 @@ impl GraphState { // Go backwards through the relations for (key, _) in arch.relations_like(relation) { - let object = key.object.unwrap(); + let target = key.target.unwrap(); - self.edges.entry(object).or_default().push(arch_id); + self.edges.entry(target).or_default().push(arch_id); } } } diff --git a/src/relation.rs b/src/relation.rs index b27363c7..6c33251b 100644 --- a/src/relation.rs +++ b/src/relation.rs @@ -26,11 +26,20 @@ where /// Returns the vtable of the relation fn vtable(&self) -> &'static UntypedVTable; /// Instantiate the relation - fn of(&self, object: Entity) -> Component; + fn of(&self, target: Entity) -> Component; /// Construct a new filter yielding entities with this kind of relation fn with_relation(self) -> WithRelation; /// Construct a new filter yielding entities without this kind of relation fn without_relation(self) -> WithoutRelation; + + /// Convert this into a concrete relation representation + fn as_relation(&self) -> Relation { + Relation { + id: self.id(), + vtable: self.vtable(), + marker: PhantomData, + } + } } impl RelationExt for F @@ -46,8 +55,8 @@ where (self)(dummy()).vtable() } - fn of(&self, object: Entity) -> Component { - (self)(object) + fn of(&self, target: Entity) -> Component { + (self)(target) } fn with_relation(self) -> WithRelation { @@ -69,7 +78,7 @@ where /// Represents a relation which can connect to entities pub struct Relation { - id: Entity, + pub(crate) id: Entity, vtable: &'static UntypedVTable, marker: PhantomData, } @@ -136,16 +145,18 @@ where } impl RelationExt for Relation { + #[inline] fn id(&self) -> Entity { self.id } + #[inline] fn vtable(&self) -> &'static UntypedVTable { self.vtable } - fn of(&self, object: Entity) -> Component { - Component::from_raw_parts(ComponentKey::new(self.id, Some(object)), self.vtable) + fn of(&self, target: Entity) -> Component { + Component::from_raw_parts(ComponentKey::new(self.id, Some(target)), self.vtable) } #[inline] @@ -195,7 +206,7 @@ where fn next(&mut self) -> Option { let (&key, cell) = self.cells.next()?; // Safety: the type matches the relation ext - Some((key.object().unwrap(), unsafe { + Some((key.target.unwrap(), unsafe { cell.get::(self.slot).unwrap() })) } @@ -240,7 +251,7 @@ where fn next(&mut self) -> Option { let (&key, cell) = self.cells.next()?; Some(( - key.object().unwrap(), + key.target.unwrap(), cell.get_mut::(self.entities[self.slot], self.slot, self.change_tick) .unwrap(), )) diff --git a/src/vtable.rs b/src/vtable.rs index 0cb4e13f..9d9f2afd 100644 --- a/src/vtable.rs +++ b/src/vtable.rs @@ -80,6 +80,15 @@ pub struct ComponentVTable { marker: PhantomData, } +impl core::fmt::Debug for ComponentVTable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ComponentVTable") + .field("name", &self.inner.name) + .field("type_name", &self.inner.type_name) + .finish() + } +} + impl core::ops::Deref for ComponentVTable { type Target = UntypedVTable; diff --git a/src/world.rs b/src/world.rs index 2b8a22ce..e0477f4f 100644 --- a/src/world.rs +++ b/src/world.rs @@ -445,6 +445,7 @@ impl World { Ok(()) } + /// Removes all instances of relations and component of the given entities /// in the world. If used upon an entity with a child -> parent relation, this removes the relation /// on all the children. @@ -452,8 +453,9 @@ impl World { let change_tick = self.advance_change_tick(); let archetypes = Query::new(()) .filter(ArchetypeFilter(|arch: &Archetype| { + // Filter any subject or relation kind arch.components() - .any(|v| v.key().id == id || v.key().object == Some(id)) + .any(|v| v.key().id == id || v.key().target == Some(id)) })) .borrow(self) .archetypes() @@ -464,7 +466,7 @@ impl World { let components = src.components().filter(|v| { let key = v.key(); - !(key.id == id || key.object == Some(id)) + !(key.id == id || key.target == Some(id)) }); let (dst_id, dst) = self.archetypes.find_create(components); @@ -1182,8 +1184,8 @@ impl World { // Modify the relations to match new components id.id = *new_ids.get(&id.id).unwrap_or(&id.id); - if let Some(ref mut object) = id.object { - *object = *new_ids.get(object).unwrap_or(object); + if let Some(ref mut target) = id.target { + *target = *new_ids.get(target).unwrap_or(target); } // Safety @@ -1215,8 +1217,8 @@ impl World { // Modify the relations to match new components key.id = *new_ids.get(&key.id).unwrap_or(&key.id); - if let Some(ref mut object) = key.object { - *object = *new_ids.get(object).unwrap_or(object); + if let Some(ref mut target) = key.target { + *target = *new_ids.get(target).unwrap_or(target); } // Migrate custom components @@ -1284,9 +1286,9 @@ impl MigratedEntities { /// If the types do not match pub fn get_component(&self, component: Component) -> Component { let id = self.get(component.key().id); - let object = component.key().object.map(|v| self.get(v)); + let target = component.key().target.map(|v| self.get(v)); - Component::from_raw_parts(ComponentKey::new(id, object), component.vtable) + Component::from_raw_parts(ComponentKey::new(id, target), component.vtable) } /// Returns the migrated relation @@ -1300,7 +1302,7 @@ impl MigratedEntities { let component = self.get_component(component); - move |object| component.of(object) + move |target| component.of(target) } /// Returns the migrated ids diff --git a/src/writer.rs b/src/writer.rs index 1cd8a3e9..03363da0 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -420,7 +420,7 @@ unsafe impl<'b> EntityWriter for Buffered<'b> { // Component does not exist yet, so defer a move // Exclusive relation - if key.object.is_some() && desc.meta_ref().has(exclusive()) { + if key.target.is_some() && desc.meta_ref().has(exclusive()) { if exclusive_relations.contains(&key.id) { panic!("Multiple exclusive relations"); } diff --git a/tests/higher_order.rs b/tests/higher_order.rs index be7df4ae..4b8a49d6 100644 --- a/tests/higher_order.rs +++ b/tests/higher_order.rs @@ -83,7 +83,6 @@ fn relations() { .set(name(), "Jessica".to_string()) .set(hobby(), "Reading") .spawn(&mut world); - dbg!(child_of(parent)); let parent2 = EntityBuilder::new() .set(name(), "Jack".to_string()) diff --git a/tests/relations.rs b/tests/relations.rs index 2a2d4434..c9924ae0 100644 --- a/tests/relations.rs +++ b/tests/relations.rs @@ -1,5 +1,6 @@ use flax::{ components::{child_of, name}, + fetch::nth_relation, filter::All, relation::RelationExt, *, @@ -156,3 +157,142 @@ fn multiple_hierarchies() { assert_eq!(relations[0].0, root); assert_eq!(&*relations[0].1, "RelationValue"); } + +#[test] +fn many_detach() { + component! { + child_of(id): &'static str, + } + + let mut world = World::new(); + + let parent = Entity::builder() + .set(name(), "Parent".into()) + .spawn(&mut world); + + let child1 = Entity::builder() + .set(name(), "Child1".into()) + .set(child_of(parent), "first") + .spawn(&mut world); + + let child2 = Entity::builder() + .set(name(), "Child2".into()) + .set(child_of(parent), "first") + .spawn(&mut world); + + // ANCHOR_END: relation_basic + + // ANCHOR: many_to_many + let parent2 = Entity::builder() + .set(name(), "Parent2".into()) + .spawn(&mut world); + + world.set(child1, child_of(parent2), "second").unwrap(); + + tracing::info!("World: {world:#?}"); + + world.set(child1, child_of(child2), "second").unwrap(); + world.set(child1, child_of(parent2), "third").unwrap(); + + assert_eq!( + Query::new(relations_like(child_of)) + .borrow(&world) + .get(child1) + .unwrap() + .collect_vec(), + [(parent, &"first"), (child2, &"second"), (parent2, &"third")] + ); + + assert_eq!( + Query::new(( + entity_ids(), + nth_relation(child_of, 0), + nth_relation(child_of, 2).opt() + )) + .borrow(&world) + .iter() + .sorted() + .collect_vec(), + [ + (child1, (parent, &"first"), Some((parent2, &"third"))), + (child2, (parent, &"first"), None) + ] + ); + // ANCHOR_END: many_to_many + // ANCHOR: query + + // Mathes a relation exactly + let children_of_parent: Vec = Query::new(entity_ids()) + .with(child_of(parent)) + .borrow(&world) + .iter() + .sorted() + .collect_vec(); + + assert_eq!(children_of_parent, [child1, child2]); + + let children_of_parent2: Vec = Query::new(entity_ids()) + .with(child_of(parent2)) + .borrow(&world) + .iter() + .sorted() + .collect_vec(); + + assert_eq!(children_of_parent2, [child1]); + + // Matches a relation with any parent + let all_children: Vec = Query::new(entity_ids()) + .filter(child_of.with_relation()) + .borrow(&world) + .iter() + .sorted() + .collect_vec(); + + assert_eq!(all_children, [child1, child2]); + + let roots = Query::new(entity_ids()) + .filter(child_of.without_relation()) + .borrow(&world) + .iter() + .sorted() + .collect_vec(); + + assert_eq!(roots, [parent, parent2]); + + // ANCHOR_END: query + + // ANCHOR: lifetime + + assert!(world.has(child1, child_of(parent2))); + + world.despawn(parent2).unwrap(); + + assert!(!world.has(child1, child_of(parent2))); + + world.despawn_recursive(parent, child_of).unwrap(); +} + +#[test] +fn exclusive() { + component! { + child_of(parent): () => [ Exclusive ], + } + + let mut world = World::new(); + + let id1 = Entity::builder().spawn(&mut world); + let id2 = Entity::builder().spawn(&mut world); + + let id3 = Entity::builder() + .set_default(child_of(id1)) + .spawn(&mut world); + + let entity = world.entity_mut(id3).unwrap(); + + assert_eq!(entity.relations(child_of).map(|v| v.0).collect_vec(), [id1]); + + world.set(id3, child_of(id2), ()).unwrap(); + + let entity = world.entity_mut(id3).unwrap(); + assert_eq!(entity.relations(child_of).map(|v| v.0).collect_vec(), [id2]) +} diff --git a/tests/schedule.rs b/tests/schedule.rs index a864d05c..52d7dc85 100644 --- a/tests/schedule.rs +++ b/tests/schedule.rs @@ -418,7 +418,7 @@ fn schedule_par() { } #[derive(Fetch, Debug, Clone)] - struct BattleObject { + struct BattleTarget { id: EntityIds, pos: Component, health: Mutable, @@ -431,14 +431,14 @@ fn schedule_par() { range: range(), pos: pos(), })) - .with_query(Query::new(BattleObject { + .with_query(Query::new(BattleTarget { id: EntityIds, pos: pos(), health: health().as_mut(), })) .with_name("battle") .build( - |mut sub: QueryBorrow, mut obj: QueryBorrow| { + |mut sub: QueryBorrow, mut obj: QueryBorrow| { eprintln!("Prepared queries, commencing battles"); for a in sub.iter() { for b in obj.iter() {