8
\$\begingroup\$

I am wondering if there is a better way than storing the entity ID. For the sake of example, let's assume I have a world with many entities who implement the component Name. Every x amount of time I want these entities to print "Hello, I am greeterName, have a nice day greetedName".

My current way to go would be to create a component BestFriend, and to store in it the entity ID of the best friend entity. Then I would make a system GreetBestFriend that query all of the entities with the Name and BestFriend components and loop through that query to find the entity that matches the BestFriend's stored ID.

My concerns about this method are:

  • Can I be sure that the referenced entity ID will not be changed at some point?
  • Having to loop through all the instances of the query seems inefficient.

As mentioned, the above case is just for the sake of example. I am more generally interested in learning patterns that allow entities to reference each other.

\$\endgroup\$

1 Answer 1

11
\$\begingroup\$

You shouldn't have to "loop through" all instances of a query. Query lookups are typically implemented in such a way that they are fast -- often constant or logarithmic time complexity. Bevy queries have a get method for exactly this purpose:

#[derive(Component)]
struct BestFriend(Entity);

fn greet_best_friends(greeters: Query<(&Name, &BestFriend)>, friends: Query<&Name>) {
    for (greeter_name, &BestFriend(best_friend)) in greeters.iter() {
        let best_friend_name = friends
            .get(best_friend)
            .expect("My best friend got despawned!");
        println!(
            "Hello, I am {}, have a nice day {}",
            greeter_name, best_friend_name
        );
    }
}

In the example, I panic! if my friends query fails to find greeter's friend because entity IDs are guaranteed to be stable and unique to a specific entity. If the Entity was returned by a call to spawn, it will remain valid until it is despawned. Let's go ahead and add some test entities:

let alice = world.spawn(Name::new("Alice")).id();
world.spawn((Name::new("Bob"), BestFriend(alice)));

Now when we run our system, the greeters query will match Bob since he has a name and best friend. Then we'll pull the ID of his best friend Alice and run it through our friends query (which only requires that Alice have a name) and say hello to her. We can be sure the query will complete properly unless Alice was despawned along the way.

As far as efficiency, the Bevy docs show that Query::get has constant time complexity, so you don't need to worry about implementation potentially looping over many entities.

\$\endgroup\$
2
  • \$\begingroup\$ I have a question about some syntax you used in your example: struct BestFriend(Entity); What does the (Entity) part do, exactly? I haven't been able to find any examples of that syntax in either the Rust or Bevy documentation. \$\endgroup\$
    – accounted4
    Commented Feb 28 at 3:34
  • \$\begingroup\$ @accounted4 This defines the struct BestFriend as a tuple with a single member of type Entity. This appears to be the idiomatic way in Bevy to define a component that has exactly one field. You can find an example of this syntax in the official quick start guide where they use this pattern for the struct Name(String); component. \$\endgroup\$
    – Philipp
    Commented Mar 25 at 12:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .