ten3roberts / flax

Batteries included ECS library for rust with entity relations and much more
78 stars 5 forks source link

Question about relationships #9

Closed pherrymason closed 12 months ago

pherrymason commented 1 year ago

I think documentation about relationships could be improved in the user guide: https://ten3roberts.github.io/flax/guide/fundamentals/relations.html

The first block talks about using child_of, but is also defining a component spring_joint, which is not used in the first code snippet.
This confused me as being necessary to define a basic relationship. I would expect a very simple code snippet just using child_of.

Later, it is described that one can define as many relations with the same component (or this is what I understand):

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;

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.

This conflicts with what docs of child_of say (https://docs.rs/flax/latest/flax/components/fn.child_of.html):

Only one parent can exist for an entity. Adding a second relationship will override the existing one, effectively moving the subtree.

Is child_of documentation obsolete?
Or maybe UserGuide refers to relationships done with custom components, like the one in the first snippet spring_joint?

Also, the second code snippet only uses spring_joint to visualize this, not child_of.

On the other hand, trying to define a relation component seems to force to define a type.

component! {
        spring_joint(other): f32 => [Debuggable],
    }

This is not described, and I found it confusing as I was expecting to define and use it as child_of. Is this really mandatory? Can I create custom component relations that don't require a "value"?

This is not a complain, just feedback of some parts I find confusing. I would love to do a PR to improve this... but I don't know the answers :(

ten3roberts commented 1 year ago

Thank you for your input.

I agree that those paragraphs are a bit confusing, I'll clean them up.

What the distinct part refers to is that child_of(id1) and child_of(id2) act like two separate components, and not single component with the value id1 or id2.

The overriding of a previous relationship of the same type is due to Exclusive being added as a component metadata on the definition of the child_of. I'll document this better in the guide.

As for the values, they are for child_of (), but can be anything like a spring strength. I'll separate them and pull the relations with values to a separate section lower down 😊

pherrymason commented 1 year ago

Also, can one get the parent of a given child element?

ten3roberts commented 1 year ago

I suppose you mean finding any child_of relation and the parent it points to for a given entity.

For that you can use https://docs.rs/flax/latest/flax/struct.EntityRef.html#method.relations.

The EntityRef in general contains many more methods for directly manipulating and accessing an entity than the world.

You can use it like:

entity.relations(child_of).first()

Which will give you an Option<Entity, value>

I've pushed a branch cleaning up the documentation, let me know if there are any further improvements you can see.

https://github.com/ten3roberts/flax/pull/10/files

pherrymason commented 1 year ago

Well, not sure if that applies to my use case. Let me give you an example. I'm querying the world, to retrieve some entities matching a list of components:

let mut query = Query::new((entity_ids(), sprite(), transform(), name()));

for (elm, sprite, transform, name) in &mut query.borrow(world) {
}

Let's say I want to know which is the parent of every of these entities.

You propose to use EntityRef:

let mut query = Query::new((entity_ids(), entity_refs(), sprite(), transform(), name()));

for (elm, elm_ref, sprite, transform, name) in &mut query.borrow(world) {
    let parent = elm_ref.relations(child_of).first();
}

I saw there is the component relations_like, would this be equivalent to using EntityRef?

let mut query = Query::new((entity_ids(), relations_like(child_of), sprite(), transform(), name()));

for (elm, relations, sprite, transform, name) in &mut query.borrow(world) {
    let parent = ???
}

On the documentation update, on the surface looks more detailed now! Will check the PR deeply just in case

ten3roberts commented 1 year ago

Yes, the relations_like does the same thing, but does a small stack allocation for the whole archetype that is visited, as they by definition have the same components, and therefore relations.

There is also the https://docs.rs/flax/latest/flax/query/struct.GraphQuery.html which allows completely random walks, and the Dfs and Topo strategy to make a normal query iterate in another order.

In a UI library I am using, I use the topo strategy to render the elements layer by layer, as well as for intersection tests.

I've added an example of using dfs traversal in a more in depth spring joint simulation in the guide

pherrymason commented 1 year ago

Your suggestion to use entity.relations is not working for me. What I'm doing wrong?

component! {
    pub space_reference(other): (),
    pub scene: Scene,

    // Item
    pub name: String,
    pub sprite: Sprite,
    pub on_floor: (),
    pub transform: Transform,
}

let mut query = Query::new((entity_ids(), entity_refs(), sprite(), transform(), name(), on_floor()));
for (id, refs, sprite, transform, name, _) in &mut query.borrow(game_state.world()) {
        let parent = refs.relations(space_reference).first();
}

It fails with:

error[E0599]: no method named `first` found for struct `RelationIter` in the current scope
  --> xxxxrs:43:16
   |
41 |         let parent = refs.relations(space_reference).first();
   |                                                      ^^^^^ method not found in `RelationIter<'_, ()>`
ten3roberts commented 1 year ago

Oh sorry, I meant .next(), just to get the first item in the iterator.

I've added an nth_relation(relation, n) fetch in the PR which will make it easier when you only have a set amount of relations per entity.

ten3roberts commented 1 year ago

The branch has now been merged.

I am going to push a few changes I've wanted to do today, and then I am going to publish a new version to crates.io :smile: