#:rainbow: Rainbow Team Relations :rainbow:
1 messages ยท Page 3 of 1
Checked the impl and that's definitely how it works, not sure how clearly it's documented
Since entity observers are just scoped versions of regular observers any time an entity observer would trigger a global observer will also trigger
The bubbling is a layer on top of that so it just triggers the observers normally at each point of the traversal
Well, that would certainly solve my problem.
We should be very clear about bubbling behavior in the docs.
There should also be a unit test which checks this, so that some future maintainer doesn't inadvertently optimize it away.
I'm thinking about refactoring my widgets so that they all share the same set of observers. For example, button and checkbox both have similar pointer handlers.
This is correct
I originally had it the other way and rewrote it to have it literally work like the event is triggered successfully up the hierarchy. Global observers are triggered multiple times when bubbling.
I can work on improving the docs.
Aervery was also surprised by this behavior when I talked to him about it.
I think part of the reason it's surprising is that it's not how it works in HTML, so people with long experience in the web might not expect it. However, I think this way is much more useful, because it lets us handle bubbling events globally with few observers.
Yeah, it's not like registering a listener on the window (in js land), it's more like registering a listener on everything
but i do actually have registering listeners on the window working locally, for picking events.
i just need to find time to polish it and open a PR.
maybe tomorrow, now that i'm mostly over my cold.
Oooh, i just realized that I could a single handler that works on all windows
This means my top-level FocusKeyboardEvent handler doesn't need to iterate through all windows installing an observer.
oh that's clever. my tests were also using window observers, but actually i can just add a clause to my existing global observer for each event type.
i'm quite enjoying how global observers provide a new way to define shared immediate behavior for components, in the same way that systems define shared scheduled behavior.
What this means is that I can just install add_plugin(TabNavigation) and everything will just work
that Target<T> api really would be the cherry on top
if we really need for the first argument to be special, I wonder if it could be a tuple.
we should open an issue for it probably. it's a solid design, maybe someone will come along and implement it.
I think I may have already, let me check
I filed a ticket for the problem, but with no suggestions for the solution: https://github.com/bevyengine/bevy/issues/15653
If you want to comment and sketch out what you are thinking that would be great
will do
i think we might be able to get away with the following pretty easily
world.observe(|trigger: Trigger<Pointer<Click>, &mut Button>| {
let button = target.data();
/// add behavior common to all button components
});
have something like Trigger<'w, E, D: QueryData = ()> and do a the archetype check and query in the observer dispatcher
but it conflicts with the Bundle that's already there.
This may be a stupid idea: world.observe(trigger: (Trigger<Pointer<Click>>, &mut Button), query: ...etc - in other words, overload the In parameter.
Kind of like a SystemParam++
System input isn't a SystemParam so it would be a major refactor to do this
You could run the query in the dispatch system and pass it in
It doesnโt need to be system param
stuffing it into the trigger is probably doable
It would have to function like a system param at least so the system knows what it accesses
otherwise this would cause issues
world.observe(|trigger: Trigger<Pointer<Click>, (), &mut Button>, my_query: Query<&Button>| {
my_query.get(trigger.entity()) // UB
});
it'd be better to do a whole separate SystemParam, however then SystemParam needs to be able to access the input data, and I'm not sure how to work out the generics for that
so the alternative question is
// how do we prevent this system from getting constructed
world.observe(|trigger: Trigger<Pointer<Click>>, this_button: Target<&mut Button>, my_query: Query<&Button>| {
my_query.get(trigger.entity())
});
Yeah that's one of the questions that made me drop it from the initial implementation, originally trigger was query like but I didn't support system params, when I added system params it became complicated
It can be done by adding extra validation to system construction that takes into account the in parameter
Or doing some refactor that means trigger is no longer the in parameter and acts like a normal system param
How would you pass the system the trigger data besides as system input?
put it in the world and then grab it?
Yeah it's not simple, you could write it to a component on the observer entity or something similar to sneak it in
But you still need a way to pass some context to the system like it's own id
I think we could turn SystemInput into a subset of SystemParam
Yeah that could work
I'll take a look
Or at least let system input share the same validation implementation
a version of validation and get_param that also takes the input data is what I'm thinking
3 lifetimes
type Item<'world, 'state, 'input>: SystemInput;
Does the data need to be mutable? An example would be helpful should be fine actually
eugh this causes issues with exclusive systems still needing to accept inputs
@cloud plover @alpine patrol @runic shadow Based on the previous discussion, I've changed bevy_reactor to have a single shared set of observers for all buttons, rather than having a duplicate set of observers for each button instance: https://github.com/viridia/bevy_reactor/blob/main/crates/bevy_reactor_obsidian/src/controls/button.rs#L325
Similarly, the checkbox and disclosure toggle widgets (both of which toggle on click) now share the same set of observers, because they both have a ToggleState component.
The ButtonState component contains the on_click callback id (which wraps a one-shot system id). Similarly, the ToggleState component contains an on_change handler, whose In parameter is the checked state of the widget.
This means that instead of having hundreds of observers, I have seven.
The benefit is not simply less memory use, but also it means that when viewing observers in the world inspector, the resulting list is much less confusing.
https://github.com/bevyengine/bevy/pull/15698 mostly done, but need to sleep. not sure how to handle validating the 2nd system in a pipe system
Honestly surprised how comparatively little is needed to add it
Also probably missing a few cases where param methods are called but input methods arent, will check tmrw
Linked lists using custom relationships anyone?
struct LinkedList;
type Next = OneToOneDirected<LinkedList, true>;
type Previous = OneToOneDirected<LinkedList, false>;
world.register_component::<Next>();
world.register_component::<Previous>();
let a = world.spawn_empty().id();
let b = world.spawn(Previous::new(a)).id();
let c = world.spawn(Previous::new(b)).id();
let d = world.spawn(Previous::new(c)).id();
world.flush();
assert_eq!(world.get::<Next>(a), Some(&Next::new(b)));
assert_eq!(world.get::<Next>(b), Some(&Next::new(c)));
assert_eq!(world.get::<Next>(c), Some(&Next::new(d)));
assert_eq!(world.get::<Next>(d), None);
assert_eq!(world.get::<Previous>(a), None);
assert_eq!(world.get::<Previous>(b), Some(&Previous::new(a)));
assert_eq!(world.get::<Previous>(c), Some(&Previous::new(b)));
assert_eq!(world.get::<Previous>(d), Some(&Previous::new(c)));
world.entity_mut(a).despawn_recursive_with_option::<Next>(true);
assert!(world.get_entity(a).is_none());
assert!(world.get_entity(b).is_none());
assert!(world.get_entity(c).is_none());
assert!(world.get_entity(d).is_none());
Got directed one-to-one relationships working, not just undirected. Next is directed one-to-many, and then un/directed many-to-many
But many-to-many is not going to be as nice to work with, since there isn't a simple Wrapper(Entity) component you can add to one Entity to modify the relationship
have a single shared set of observers for all buttons, rather than having a duplicate set of observers for each button instance
Moving closer to how I've implemented reactivity ๐
I should be clear, this is not how I would want to do every widget.
Yeah, that's why I said closer. I'm using a single observer per widget (component) to trigger reactivity
Particular for app widgets that are one-offs, it's much less burdensome on the developer if all of the various aspects of the widget - appearance, state, and logic - can all be co-located together instead of being scattered across multiple source files.
However, for widgets that are "commodities" like button - that is, there are lot of the same kind of instances scattered around the UI - being able to centralize the logic into a single handler has more pros than cons.
UIs in the dot-com era were typically organized by vertical separation into layers, whereas modern UIs tend to be mostly organized by horizontal separation into functional areas.
is there an actual write up that covers what you guys did for the render world somewhere?
the retained rendering world?
I think the best your going to find is the migration guide and the current docs
if i tried to write a slightly more expanded migration guide for rendering devs, do yall think you could help me?
can you link it?
the big thing is that you are now having to think about what entities you extract every frame and which ones just sit there
because entity leaks are very possible with the new approach
@alpine patrol, this changes the migration a little bit: https://github.com/bevyengine/bevy/pull/15582
so that's important to add
also RenderEntity is going to implement WorldQuery so instead of querying &RenderEntity you can query RenderEntity and get the correct entity directly (no entity.id())
i think your migration guide is great, but the render devs seem like they need a more cohesive document with specific consideration for common rendering abstractions like ExtractedInstances
sure thing
Write up what you think needs to be written up
I can check it if you want to
@spring hinge โค๏ธ
My brain is too tired atm, taking a break from bevy ATM
I might need to take a break as well after all the retained rendering follow up is done. Part of me is trying to see if it's possible for me to get a burnout and while I love a good science experiment, I don't think I'd like it to succeed.
hey @timid mountain, just out of curiosity, I see that you have 5 Prs open (one in draft but still). Are you willing to put them up for adoption?
A couple are related to relations
@wary fjord I see that you've taken a look at QueryState https://github.com/bevyengine/bevy/pull/15848
I really appreciate the refactorings you bring to Bevy and this seems like a great change.
Out of curiosity, have you given some thought as to how we could turn QueryState into a component and store it in the world?
Sure, any PR of mine is always up for adoption
Sure thing
Couldn't you have waited until someone posted after your "I try to speedrun burnout" message? ๐
The simplest solution is to store Entity for the state (as you did in your PR), but return Query<'w, 'w, D, F> instead of 'w, 's. That means we can still keep a reference to the QueryState in the Query. The problem I ran into and why I stopped is we need to decide where the querystate's archetypes will be updated. get_param might work but it was a bit of churn switching the &SystemMeta reference that gets passed to it into a &mut SystemMeta
For now I'm working on a game project to see what's the most practical and useful ecs stuff for me to work on next
Actually encountering a case where proper multi-event observer support would reduce code duplication ๐
Do you have this code anywhere on a branch so I could look at it?
I don't, sorry
All good
Very cool.
As I've previously mentioned, I would like to define an Owner/Owned relationship, such that:
- When an owner is despawned, all of it's owned entities are automatically despawned as well (regardless of whether you use despawn or despawn_recursive).
- Owners can also be Parents, but the set of
Childrenand the set ofOwnedare separate sets - iterating over children doesn't iterate over Owned.
"Owned" entities would be things like reactions, mutables, effects - satellite helper objects that are attached to an entity but not part of the visible hierarchy.
I suspect that your PR + a little bit of component hook magic would be enough to implement this.
I think so! The Owner and Owned components can be setup as:
struct Ownership;
type Owner = OneToMany<Ownership>;
type Owned = ManyToOne<Ownership>;
It's one-to-many, you can have multiple owned for an owner
The despawning behaviour is something I think you could add using required components and a hook perhaps?
Likely
Basically an on_remove hook for Owner.
Either way, the way I added these relationships is documented in a private trait Relationship that I think would be very easy to copy for adding totally custom behaviour
Less than 100 lines of bevy_ecs-dependent code to create the hooks for a relationship type, including events
Oh actually events might be the simplest answer here
Because this PR will fire a RelationshipEvent<R> for adding and removing
Where R is your custom event
With retained rendering world is the only big item remaining for MVP relations archetype GC?
I'm not sure what that all requires tbh
The rendering world was the main blocker for relations. With retained rendering world that blocker is gone.
Right
There's archetype GC which you need with fragmenting relations because you're going to cycle through archetypes quickly & after that I can't think of anything else that's stopping an immanent MVP relations PR.
We don't need components as entities for mvp relations?
IIRC no because it's just ID pairs. Components as entities just changes components to be the same ID type.
Also most of the hard work for making them use the same ID pool for Components as Entities has already been done a few versions ago.
do we still need to do step 3? https://ajmmertens.medium.com/a-roadmap-to-entity-relationships-5b1d11ebb4eb
we have Identifier but we're not using it in archetype storage
that + (simple) queries for them would basically be MVP relations
Yep, it's just archetype clean-up that's required for relations now
Queries as entities would be nice to do first since we could transition to observers before making table/archetype deletion a thing, but it's fine either way
90% of the work left for components as entities is also just archetype clean-up, so you can get that in there pretty easily
will a component relationship be a unique component ? Like can you only have one Listen<any component> or you can have one of each Listen<component_type> for each component type ?
you can configure exclusivity
by default they'll be exclusive so you can only have one (Eats, *) for example per entity
you could change this to allow for multiple entity targets
What's a good place to start in understanding the state of entity relationships on main today?
Or design work that's been solidified.
I know about https://github.com/bevyengine/bevy/issues/3742 obviously, but I feel like there's a lot more to it that's been developed already.
As per the pinned comment, the design document we're attempting to follow is https://github.com/bevyengine/rfcs/pull/79
There's also some more specifics at https://hackmd.io/iNAekW8qRtqEW_BkcHDtgQ
I'd have to do some digging about the stuff that has and hasn't been implemented for the rfc
I always forget about pins, thanks.
For the minimal implementation section, we've done the first two things
i.e. merge observers and a retained rendering world
The next step is to make queries entities for which I've opened a draft: https://github.com/bevyengine/bevy/pull/14668, but I've had to abandon it because I got stuck fighting with explicit lifetimes
it's not the most difficult thing in the world. Every query holds a QueryState, this PR just tries to make QueryState a component and have a Query hold an Entity reference to said QueryState instead of a QueryState directly
it's the details that are hard
Queries as entities is not required for MVP relations FYI
they would be nice to have though
The next step is archetype cleanup iirc
Although maybe even that isn't necessarily required
yes, but QueryState keeps an internal record of all archetypes that match, so if you're gonna do archetype cleanup, you should also do it there
I agree that we 'technically' don't need much of this stuff
We could add a remove_archetype method to SystemParam
sure
it's just, there's a bunch of caches (and there are going to be more) just subscribing to an 'archetype removed' event with an observer is a lot easier
true
relationships is interesting, because we technically could skip straight to implementing it, but the performance would suck and that's not really how I'd want to roll out the feature
it'd be one of those things you'd have to specifically warn people for, like yes we have relations but it's slow
I'd much prefer it if the feature got implemented and we could tell people to just go nuts with it
OK help me understand this snippet:
alice_mut
.insert_relation::<Eats, Fruits>()
.insert_relation::<Eats>(apple)
.insert_relation::<Eats>(banana);
What happens if you don't call .insert_relation::<Eats, Fruits>()?
I would rather have something that works good enough sooner, than something that is near perfection much later
especially since all the custom relation stuff was removed from https://github.com/bevyengine/bevy/pull/15635 with the expectation that we will have relations sooner rather than later
I'm less concerned with performance at the moment, because I'm trying to design a crate which needs the interface.
The general interface I'm looking for is basically just a HasMany, HasOne, and BelongsTo relationships.
I'm asuming a panic
Would calling the first one register_relation help make that more clear? Or am I missing something elegent about the design here?
it could very well
I'm somewhat surprised that the generics work out with this.
storing data on relations is not possible with the current rfc but is very much planned for
Like, in my mind MVP relations shouldn't have the expectation to be used in performance critical code
I agree and I'm coming around to the 'sooner rather than later idea'
I'm not sure I need to store information on the relations for the basics of this to work.
I'll need to store information when I get to implementing join tables.
however, I will say that relations cause the number of components to explode and that slows down all operations if these optimizations aren't made
and that might not be a very big problem for users who have a decent amount of control over the amount of relations they use, but this becomes a bit of a problem for external plugin code
Pop the user-facing API into an experimental module then
imagine adding a plugin that uses relations and the whole application slows down
which would suck
fair
Why do you say this needs to store information on the relation?
at the end of the day, I'll review anything related to relations
Having the data on that would be super helpful for next-step optimizations
I had a brainfart, for some reason I thought you had to store the entities of ManyOff manually, forgetting that this is exactly the point of relations
my bad
No worries.
I'm just slowly collecting requirements and designs for my SQL crate.
The idea was to use something like SeaORM and have it map to entity relations, but I'm not sure I actually want to use SeaORM. But the idea is the same.
it's just frustrating, because there are truly only 3 to 4 PRs left to do, in order to do it 'properly', but I got stuck doing the most important one
Yea I agree
Once 0.16 dev starts proper, I'll hop back on the ecs dev train
Currently taking a break besides writing my PRs' release notes
Like @timid mountain has a couple of PRs open that just changes a couple of datastructures to be able to cope with an arbituary number of components, those PRs are perfectly fine and could be merged pretty soon
same
Are entity relationships going to be bidirectional?
I believe one-directional
There would be no panic here, wouldn't matter if you didn't call the first one.
ty
What does it accomplish then?
It would add an Eats relationship between the alice entity and the Fruits entity/component
Seems weird to do it that way when you have entities for apple and banana, but it's allowed
That specific shape of API wouldn't work in Rust, I don't think
So you can only iterate over one side of it? Seems strange.
Yeah you would need different methods
OK, cool. I'm not crazy.
You can iterate both sides in theory, it's not in the MVP since it's more complex to implement, but the relationships are directional by default
The main blocker is archetype clean-up but the implementation of that is less the existance of a remove_archetype method and more implementing that method is very painful due to the data structures that currently track archetypes/tables
For queries it's not so bad but you have to deal with the accesses of systems as well
So when I do a.insert_relation::<Something>(b) which side stores the ID or reference?
That is equivalent to inserting a component on a with the id (Something, b)
OK, so it's a.insert_relation::<BelongsTo>(b) if I'm following how it works in other ORMs.
It depends what the relationship represents
Yea actually it's not exactly, because I can have many...
Inserting a relationship onto an entity is similar to a tagged foreign key
yea exactly.
Where you can have multiple foreign keys for a given relationship type, since each pair is a unique component id
But I'll also need the flip side where I can iterate over the as which have a relationship with a b.
Yeah we will definitely support that, maybe not in the first PR but as a quick follow up
It's easier to implement the other way first
Sure
I'm just struggling to imagine the API.
Like I don't understand Targets in the hackmd.io doc.
Targets iterates the other way. i.e. if I call a.insert_relation::<ChildOf>(b) and look for Targets<ChildOf> on a I will get b
In active record I'd say a.bs or b.as if they had a join table.
You could have a similar TargettedBy<ChildOf> that would match on b to give you a, but that's harder to implement due to the way bevy queries are currently implemented
So it seems like I might want to query Targets<HasOne/Many> and iterate that.
When you say "you could" you mean Bevy could? Targets is part of the Bevy API for this, no?
Targets is in the MVP RFC, TargettedBy is not
I think the second snippet in this part of the doc lays out Targets relatively clearly: https://github.com/james-j-obrien/rfcs/blob/minimal-fragmenting-relationships/rfcs/79-minimal-fragmenting-relationships.md#user-facing-explanation
So now I'm wondering how the join table case would look. Where I have two component types each with a relation to many of each other.
In order to do "proper" joins we will need to refactor queries to support matching across multiple entities, which will be complex but super powerful.
You can see some examples of the kinds of things you can do in the Querying the Graph section of this article: https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c
I feel like I need to read all this slowly and really think about it.
I'm so used to thinking of relationships based on who holds the foriegn key.
I guess in my model, I'd have a component declare somewhere that it has a relation to another component through some intermediary component. Whereas, with this that relation would really just be represented as a more complex query.
One advantage to the former is that it could allow a developer to treat direct and indirect relations the same.
For example:
filling.fruits = [apple, banana]
cake.filling = filling
cake.fruits => [apple, banana]
๐ค I didn't fully understand the conversation, but
- the reason for relationships to be components is because a query finds archetypes (sets of components) that satisfy its criteria
- the reason for the information to be encoded in the component ID is because it's nice to not need data or need to access it
- the idea takes advantage of the fact that IDs have 64 bits yet still-living entities can be ID'd using 32 bits
- that's only enough space to fit 2 entities, so we only get 2-ary relationships
- the idea takes advantage of the fact that IDs have 64 bits yet still-living entities can be ID'd using 32 bits
The setup was designed to reuse the existing ECS machinery (e.g. components, archetypes, and an index mapping each component to the archetypes that have it).
the way queries (will) find archetypes is very much like prolog
(I think it's interesting that components and queries conceptually map to prolog's facts and rules. It suggests that queries could someday be used as aliases for tag components.)
tag components sound like a possible solution to my join table problem
by tag component I mean a zero-sized component
oh
Declaring facts in Prolog like this is the equivalent of attaching (zero-sized) components to the alice and bob entities.
Purple(bob).
Red(alice).
Blue(alice).
Declaring rules like this is the equivalent of defining a query and declaring it as an alias for the component.
Purple(X) :- Red(X), Blue(X).
we wouldn't want to answer to "yes" to the question of "does alice have Purple?" if Purple had data because alice wouldn't have any Purple data we can reference, but there's no problem with zero-sized components
That makes sense to me.
I don't think I understood your join table problem, but symmetric relationships can be done.
As long as the ECS can be made aware that relation R is symmetric, then when you add (R, e1) to entity e0, we can have it automatically add (R, e0) to entity e1.
So I could give you an example from a project I'm working on, or a more generic example.
If I have a relation from A->B and another relation from B->C, I may want to iterate A's Cs.
Sorry, I'm getting myself mixed up. That's another thing.
So let me give you my actual use case since I'm confusing myself now...
I have Factions and Systems. There's a join table in my DB system_factions which also stores additional information like influence, happiness, etc.
I want to model this as relations, where a system has many factions, and a faction has many systems.
I mentioned this because in active record (which I'm using as inspiration), I'd have Factions.has_many(Systems, through: :system_factions)
So provided you can tell the world what properties a relation has (i.e. is it transitive? is it is it reflexive? is it exclusive? etc), the ECS has a pair of methods to enforce those properties.
The first is that a world can explicitly instantiate components according to the rules, so adding a relationship to one entity could make the world add relationships to other entities. Pretty straightforward.
I don't follow.
Like in the symmetric example I just gave.
I think the first example I gave is the very definition of a transitive relation.
The second, more interesting one is that queries can "traverse" relationships to answer questions and return references to components on related entities.
So if you want to find spaceships docked on planets that are ruled by allied factions, a query would (eventually) be able to do that.
SpaceShip
(Faction, $ship_faction)
(DockedTo, $planet)
Planet($planet)
(RuledBy, $planet_faction)($planet)
(AlliedWith, $planet_faction)($ship_faction)
Transitivity would most likely be handled this way.
I think this is what I mean by a more complex query though. Whereas in active record the component stores the relationship information and you'd just say SpaceShip.allied_factions or something.
The best example I can give is that flecs has an IsA relationship that is transitive, and if you ask if an entity has T, a query walk up the IsA chain until it finds a T or the chain stops.
You wouldn't need to write out the whole query.
I feel like we're kinda talking past each other.
I guess so. I don't know what all these SQL terms mean in the context of an ECS that organizes and finds entities solely based on the components they have
I'm literally just trying to model relationships where data has foreign keys to other data.
I think the key feature of the interface I want is to be able to setup a relationship where it traverses many relations like your example above with the spaceship, but I don't need to write out all the intermediate links because it was encoded in the relationship structure.
Like if I had a relation a.relates(b) and b.relates(c), I want to be able to query a.cs without thinking about b directly.
So I'd write a.relates(c).through(b) or something.
then a.cs should work.
๐ค flecs has a concept called "prefab slots" but AFAIK it's only used with the parent-child hierarchy. Like, a spaceship entity might have a whole sub-hierarchy of entities beneath it, but you just wanna call spaceship.element(turret) to jump directly to the turret entity.
Yea that doesn't seem helpful to me.
Hmm, then I really don't understand, because to me it sounds like the closest thing to what you're talking about.
Instead of having to traverse instance_of_turret.child_of(foo), foo.child_of(bar), bar.child_of(spaceship), you just say spaceship.element(turret).
It's just some syntax sugar. When the entity spaceship was instantiated (along with its hierarchy), it was given a component that had the ID(turret, instance_of_turret).
The element method (or whatever it's called) just looks for (turret, *) in spaceship's archetype and, if it finds one, returns a ref to the instance.
So far, it sounds to me like you want to be able to register a similar kind of shortcut that queries can understand, instead of having to write out the full query statement.
basically.
but ideally that shortcut is tied to the definition of the relationship.
This is what I'm hoping to be able to model on top of the ECSs relationships: https://guides.rubyonrails.org/association_basics.html#the-types-of-associations
Active Record AssociationsThis guide covers the association features of Active Record.After reading this guide, you will know how to: Declare associations between Active Record models. Understand the various types of Active Record associations. Use the methods added to your models by creating associations.
I'm essentially trying to find the most natural mapping between the concepts.
well, I'm sure it's possible, but bevy kinda needs to implement query traversal first before its UX can really be improved
What is query traversal?
that complex query
ah gotcha.
answering it requires traversal
Anyway. I gtg for now. Maybe I'll try typing up a more explicit set of examples of how I might like to be able to represent these relations.
You can add properties to make them symmetric like flecs already has
it just means setting X-R->Y also sets Y-R->X
edit: oh that was explained
So I looked into this more.
The tl;dr is that ECS queries will work like Prolog, so if you know how to express this stuff in Prolog, that should (eventually) translate 1:1 in Bevy.
All relationships will be inherently bidirectional because, like Prolog, queries will find arguments that satisfy predicates through unification.
To give an example, this Prolog query would yield all (parent, child) pairs.
// Parent and Child are variables
?- ChildOf(Parent, Child).
If you set Parent to "James" and leave Child as a variable, it'll yield all ("James", child) pairs, i.e. it'll find all of James' children.
If you set Child to "Alice" and leave Parent as a variable, it'll find Alice's parents.
When I went through those associations, this is what I got:
belongs_to=> seems to have the same properties that "child of" hashas_one/has_many=> a relationRcan be marked as exclusive, so attempting to add a(R, bar)will replace any existing(R, foo)has_*:through=> You would achieve this with a complex query. If you want to shorten it, I think the natural expression would be Bevy supporting "rules" (queries serving as aliases for components).
For an example of a rule, I'll use one of the has_many:through examples from the Rails link you shared. In Prolog, you could find a doctor's patients (or a patient's doctors) using this Patient rule.
Patient(X, Y) :- Appointment(X, Y), Physician(X).
When the Prolog interpreter later sees Patient(X, Joy) used in another query, it'll (basically) auto-expand the head (Patient(X, Joy)) and replace it with its body (Appointment(X, Joy) and Physician(X)).
(recursively if Appointment or Physician are also rules)
In ECS terms, rules would be queries that you can treat like components, either as new components or as aliases for existing (zero-sized) components.
e.g. this complex query
SpaceShip($this),
Faction($this, $ship_faction),
DockedTo($this, $planet),
Planet($planet),
RuledBy($planet, $planet_faction),
AlliedWith($ship_faction, $planet_faction).
can be shortened to this
Spaceship($this),
AlliedPlanetFaction($this, $planet_faction).
by defining a query that functions as an AlliedPlanetFaction relation and has the rest of the terms
AlliedPlanetFaction(X, Y) :-
Faction(X, $ship_faction),
DockedTo(X, $planet),
Planet($planet),
RuledBy($planet, $planet_faction),
AlliedWith($ship_faction, Y).
btw, if any of y'all are interested in something you can read to learn about Prolog, this is a good resourceโ https://www.metalevel.at/prolog
Introduction to modern Prolog
(it's good to be familiar with it since it's where this stuff is heading, IIRC flecs is 99% of the way there, just doesn't have the rule support yet)
has_* is the inverse of belongs_to so the fact that relations will be bidirectional means that, IIUC, there will really only be has_*.
I'm not quite sure how exclusive relations work. Seems like they should only apply in the has_one case, and could hopefully allow for a query without iteration. Whereas has_many is the more general concept, where you'd iterate the related components like normal.
@polar vortex have you heard of datalog? https://en.wikipedia.org/wiki/Datalog it's a subset of prolog that would seem better scoped maybe?
An important difference is datalog is not turning complete so itโs guaranteed to terminate. Seems desirable for a query language
datalog and prolog are used for somewhat different purposes
datalog is more suited for databases
and then there's https://github.com/vmware/differential-datalog
something like differential datalog is used for the in-progress new rustc trait solver because the semantics line up nicely
idk which one is the more similar, but what I'm saying is that ECS queries will fundamentally operate on the same kind of unification with backtracking principle that logical programming languages like Prolog do
I'm not saying we should use Prolog as a literal DSL, only that it's a natural way to model ECS queries
We'd just enforce exclusivity when moving an entity from one archetype to another. Think of the scene hierarchy for exampleโan entity can only have one parent. If you try to add a different ChildOf relationship to an entity, the ECS should (and will) automatically remove the original one, so you'll never see an entity with more than one parent.
Is there any way we could shove query variables into the type level api?
idk, probably not, strong-typing is pretty antithetical to relationships because entities don't exist at compile-time
you'd only be able to express a limited subset of what's possible at runtime
I donโt quite think I see it- systems are created before most entities too so choosing specific entities seems rare
I just mean like the complex flecs query you posted above
Hmm, think of it like this. There is an abstract ECS/query model and a strongly-typed Rust API on top of it. Strong-typing can only express the subset of the possible operations that only need compile-time knowledge.
That's why we now have a query builder API. There will eventually be a system builder API. You can already define new components at runtime without type information. You just can't express everything with types alone. So IMO it's not rare, we just don't have all the tools to support it yet.
Right, but in AR there's nothing stopping you from belonging to many different relations.
๐ค 1:1 can be enforced if the relationship is also supposed to be symmetric.
Oh yeah for sure, I didnโt mean restricting the api to only the type level. Does that mean that any query wanting relations would need to go through a builder?
In general, yeah. There are some queries you could probably express at compile-time if the relation is a type and the target is an any/all wildcard. But for a complex query like "find all allied spaceships on mars", I think those will need a builder.
@ancient spear, like I know Bevy is super Rust-centric, but like... releasing an editor is only gonna increase demand for some official kind of scripting (like blueprints or Lua or something).
I think as t goes to โ, less code will be statically compiled, so more code will be able to use the "full power" of ECS.
Agreed here, and Iโm not trying to push back against that future :). Was just curious about how using relations might look for the mvp
Yeah, I worded that poorly, sorry. I meant to state it like "I do think it'll be awkward since Bevy built a culture around Rust and strong-typing, but I think that's likely to fade into the background in the future. And there will be less of a need for type system wrangling."
MVP will probably not include all the fancy query traversal stuff.
I hope not fully into the background ๐ , the main reason I use bevy is for its great track record with rust api design
Like, I think it'll just start off as "you can query for relationships" and it'll be a bit before query variables and finding things by following relations.
Curves, states, picking, etc
if it does, it will hopefully also increase the number of people willing to work on scripting.
I mean fade in the sense that, in the future, I expect most people using Bevy will be using an editor-based workflow and they'll be writing scripts instead of literal Rust so they can make changes on the fly, etc.
Rust will still be the foundation, but like fewer and fewer people will deal with it directly, probably.
yeah probably
I would just hope that doesnโt lead to the rust API being any less really really good than it already is
Not that Iโm worried since thereโs very little momentum in that direction as-is
I donโt mean to sound super assertive either, things will evolve fine I think :)
it shouldn't get worse
I think things will converge to: There's a feature-complete "dynamic" API (written in Rust). Both the strongly-typed Rust API and any scripting interfaces bind/wrap those "dynamic" data structures and methods.
like, everything will be the same underneath
i.e. there will only be a single form of a system and it won't matter if its body is a Rust function, an FFI function, or a function that runs a runtime-compiled visual script
I think that's what it means to be data-driven.
The ECS itself has always been a runtime thing, it's just wrapped in a strongly-typed interface. Components are just blobs of data with a known layout (and methods via reflection).
Strongly-typed code just knows that information upfront without asking. Scripted code would have to get it through reflection.
I think we will always have a larger set of users focused on the rust api.
Adding scripting would be more about expanding to new users.
Think engine devs vs technically-focused game designers.
what I mean is that adding support for scripting should look nothing like a new language, it's just a new wrapper over the same operations
For the ecs specifically yeah
like, there wouldn't be "rust queries" and "lua queries" (that'd be silly), whether a query is created through one API or another, it should produce the exact same struct for the ECS to consume
(just to reiterate, there's an abstract ECS model / "dynamic" API that doesn't involve a type system, the strongly-typed Rust API is layered on top of it)
Right, but there's nothing but entities, components, queries, and systems. Adding new features just means adding more of those. Users aren't supposed to invent layers upon layers of abstractions like they often do with a traditional, object-oriented engine.
I just mean that interacting with most of the engine abstractions uses rust-specific things like pattern matching, iterators, traits, generics
nothing is stopping scripting from using the abstractions themselves though, but it might be a little more verbose
That's why I encourage mentally separating the ECS from its strongly-typed Rust API.
A query is just a machine that takes in a set of component IDs and spits out a set of archetype IDs that have those components. No type information is involved.
The strong-typing just sandwiches the actual work. T is used to acquire a TypeId to lookup a component ID on one end, and to safely deref the returned pointer as a &T or &mut T on the other end.
yep
The ECS could (and should) have a runtime iterator struct that strongly-typed Rust iterator APIs just wrap.
Scripting languages wouldn't bind the strongly-typed Rust methods, they'd bind the underlying ones (i.e. get_by_id instead of get). We're just still missing that clean separation in a few key places (queries, systems, commands).
Like, this is how flecs can have C#, C++, Rust bindings. Those are all wrapping the same underlying C data structures and calling the same underlying C functions.
Traits are an interesting point, but I believe they should ultimately be handled the same way. The ECS should introduce a canonical v-table representation (it already kinda has one, see ComponentDescriptor) and components should create them when they're registered to a world. Like, reflection should be part of the ECS model.
yeah I was wondering how we could do this, because something like vtables-as-components and components-as-entities would be great for trait querying
I'm getting kinda rambly, but ultimately all I'm trying to say is that the Rust experience should continue to improve even if scripting does blow up in popularity. All future language bindings should naturally look and feel similar since they'd all be wrapping the same stuff the Rust API wraps.
will relationship allow to iterate by a "runtime value" so the complexity = O(n) where n = the amount of entity with that "runtime value".
Ex: iterate entity with component A that are owned by the player A "with the Owned component with a relationship to the player A"
The complexity will be the same as queries ie O(n)
except you'll have back tracking because of acceleration structures like the component index so that's a pretty fast n
And queries can be cached which will save you from doing the lookup on subsequent runs when there were no structural changes
queries ie O(n),
ie ? typo or smth else
n still = that "runetime value" right
probably meant i.e.
i don't even know what this abbreviation mean
so we will be able to query by "value" on O(n) with reliation instead of O(k) (k = number of entities with the component holding that value)
amazing
ie: in essence
oh nvm I've been believing a lie for half my life
must have been confusing a lot of people during that time
no cause in essence has an equivalent meaning that makes more sense cause it's not old english or whatever id est is
i just want to iterate over the cards with x components of a specific board that hold them
yeah that's a very fast operation & something relations are good at
do it change the archetype or just hold the component index of the entity of that have x relation value of their relation ship
i guess the second
changes archetypes
(id est is Latin, like exempli gratia, which is what "e.g." stand for)
ah yes ofc it's latin
and I was so sure it was "?? example"
I guess twitter had a character limit also back when people spoke latin
What do you mean that flecs doesn't have the rule support yet? What is the "rule" you're referring to?
Foo(X), Bar(X) :- Bazz(X).
if I remember my prolog correctly
global predicates composed of compound facts (or other rules) that procedurally determine other facts
And is this meant to be a query or?
From my understanding just now of this prolog, it sounds similar to the with trait in flecs
It's not. If you query for Bazz you get anything with Foo & Bar
@high stone Is this correct?
#[require(Node, TextLayout, TextFont, TextColor, TextNodeFlags)]
pub struct Text(pub String);
I thought that Text was supposed to represent a single span? I'm having trouble constructing paragraphs because it's trying to lay out each span as a separate node.
You in the right channel?
Actually the left side is a query in a sense & the right side is determining what to do with that query ๐ค
Yeah, sounds similar to the 'with' trait. However I don't recall if flecs allows removing foo or bar in that instance.
The With trait is unrelated to querying. With just ensures required components are added togother.
In Prolog, a "fact" is an axiom.
// Alice and James are friends.
friends(alice, james).
A "rule" infers a truth from other facts and rules.
// Two people are friends if they have something they both like.
friends(X, Y) :- likes(X, Z), likes(Y, Z).
In ECS lingo, a fact is having a component.
Rules would be queries that you can use as component terms in other queries.
Like, rules are about inference.
Rules don't really add (edit: much) new power to queries, but they really help to shorten complex queries. The part on the left of the :- is just shorthand for the part on the right.
Does it make sense? Like, referencing queries as terms in other queries to reduce boilerplate.
๐ค well actually I guess being able to create rules that serve as aliases for other facts and rules does count as a new power.
If you ask Prolog if the friends(bevy, flecs) predicate is true, it'll check if that fact exists in the knowledgebase. If not, it'll try binding bevy and flecs as the arguments for every 2-ary friends rule that exists, one-by-one, until it finds one that unifies (is true) or it runs out of rules.
probably not as you'd want or mean, but creating a query like that is supported and isn't "so complex"
and creating "rules" is possible through some workarounds I suppose, but less magical as you'd want I imagine ๐ค
I do get what you mean yeah. I should have a read about prolog myself, I just never got to it 
let me try to code your example ๐ค I'm curious
I'm pretty sure I haven't misrepresented what flecs is or isn't capable of.
probably, I'm most likely misunderstanding something 
๐ค query scopes is probably the closest thing atm.
I brought up rules earlier because someone asked about, like, creating shortcuts to use in queries later. Like, this is a pretty long query (taken from here).
SpaceShip($spaceship),
Faction($spaceship, $spaceship_faction),
DockedTo($spaceship, $planet),
Planet($planet),
RuledBy($planet, $planet_faction),
AlliedWith($spaceship_faction, $planet_faction)
An author might wish to factor out some of these terms into something they can use in other queries without having to type them all out, like this:
Spaceship($spaceship),
AlliedPlanetFaction($spaceship, $planet_faction)
where AlliedPlanetFaction isn't an actual component, it's just a rule.
AlliedPlanetFaction(X, Y) :-
Faction(X, $ship_faction),
DockedTo(X, $planet),
Planet($planet),
RuledBy($planet, $planet_faction),
AlliedWith($ship_faction, Y).
this is what I build, probably not as simple as stating facts, rules like you said:
#[test]
fn test_facts() {
#[derive(Debug, Component)]
struct Friends;
#[derive(Debug, Component)]
struct Likes;
#[derive(Debug, Component)]
struct Boardgames;
let world = World::new();
// if Alice Friends Bob, then Bob Friends Alice automatically
world.component::<Friends>().add_trait::<flecs::Symmetric>();
let query_boardgame_lovers = world.query::<()>().with::<(Likes, Boardgames)>().build();
world
.observer::<flecs::OnAdd, ()>()
.with::<(Likes, Boardgames)>()
.each_entity(move |e, _| {
let e_id = e.id();
query_boardgame_lovers.each_entity(|boardgame_lover, _| {
if e_id != boardgame_lover {
//this makes e friends with boardgame_lover as well
boardgame_lover.add_first::<Friends>(e);
}
});
});
let alice = world.entity_named("Alice");
let bob = world.entity_named("Bob");
alice.add::<(Likes, Boardgames)>();
bob.add::<(Likes, Boardgames)>();
//observer making them friends
println!("alice Friends bob: {:?}", alice.has_first::<Friends>(bob));
println!("bob Friends alice: {:?}", bob.has_first::<Friends>(alice));
let query_fact = world
.query::<()>()
.with_first_name::<Friends>("$Y")
.set_src_name("$X")
.with::<(Likes, Boardgames)>()
.set_src_name("$X")
.with::<(Likes, Boardgames)>()
.set_src_name("$Y")
.build()
.each_iter(|it, i, _| {
println!(
"entity who are friends and like boardgames: {:?}",
it.src(i as i8).name()
);
});
}
---- test_with_trait stdout ----
alice Friends bob: true
bob Friends alice: true
entity who are friends and like boardgames: "Alice"
entity who are friends and like boardgames: "Bob"
the example I gave was kinda the opposite
let's say we have this Friends relation
but Alice and Bob don't literally have it
yeah sounds like a potential sub query ish style ๐ค
Alice doesn't have Friends(Bob) and Bob doesn't have Friends(Alice)
But if there's a rule like Friends(X, Y) :- Likes(Z, X), Likes(Z, Y) , then if Alice and Bob both had Likes(Soccer), the ECS would infer that they are friends. A query on Friends(Bob, *) would yield Alice even though they don't actually have the literal relation.
IIRC Sander was building toward supporting rules, but rules are not just sub-queries.
A query on Friends(Bob, *) would yield Alice even though they don't actually have the literal relation.
Yeah I see, that level of magic isn't there yet indeed
you could ask a query "are bob and alice friends" if they both like boardgames, but that's the extend of it
I wonder though, couldn't you model them like that ๐ค
if you have a friends component
add a query attached to the friends component
and then in the core code, if Bob has no literal friends relationship, check if friends has queries attached to it, execute and yield the results
this probably wouldnt work for all cases, but in this case, it might
You'd want it to be automatic / built-in into the query engine. Like, yeah, you'd define a query and tie it to the Friends component. But to count as a true rule, every query that had aFriends term in it would need to automatically pick it up.
We have psuedo Rules already in impl'ing WorldQuery, no?
๐ค I feel like that's a bit of a stretch, but kinda? Custom WorldQuery impls at least let you abstract away boilerplate.
If we look at a rule like
// If X satisfies a and b, then it also satisfies c.
c(X) :- a(X), b(X).
If a, b, and c where compile-time components then, sure, you could impl a new WorldQuery as a shortcut for Or<(With<C>, (With<A>, With<B>))>.
But what sets rules apart is the whole "produce new information" thing. Rule composition can be much more flexible. Like, they can be endlessly and recursively composed.
// If X is Z's parent and Z is Y's parent, then X is Y's grandparent.
grandparent(X, Y) :- parent(X, Z), parent(Z, Y).
// A parent is also an ancestor.
ancestor(X, Y) :- parent(X, Y).
// A parent's ancestor is also the child's ancestor.
ancestor(X, Y) :- parent(Z, Y), ancestor(X, Z).
I should say I think bevy is pretty far off from thinking about rule support. It's definitely outside the scope of this working group IMO. I only brought them up to explain how that Ruby on Rails stuff could be translated to the ECS.
Tl;dr @polar vortex is correct
A query on Friends(Bob, *) would yield Alice
Haven't fully figured out yet how this should work in the context of queries that have more than two variables
Ideally it should work as if Bob has (Friends, Alice), but what if Friends was a rule with >2 arguments. Then what does Bob have
Been thinking a lot about combining this + the ability to represent pairs/facts as entities
So Bob could have (Friends, Alice, Besties) where Alice, Besties (or Friends, Alice) is represented by a single entity
Still pretty far away from being able to do inference though. The brute force way would be to just run all rules for each entity but that's not feasible
Feels like it should be possible to look at the terms of a rule (and nested rules) and find which components contribute to it
So you could eliminate running that rule for all archetypes that don't have the component
(but that gets complicated quickly with operators, multi-source queries, traversal etc)
sounds like it could be similar to something datalog calls the "magic set transformation"
https://souffle-lang.github.io/magicset
Hey, I think working relations would allow for exploring/unblocking some better ui ideas, and also be useful on my personal project.
What are the current blockers/ is there something I could help with to make this happen faster?
there are @slate quartz, you could technically already implement it, but they big 'blocker' we currently have is the handling or archetype cleanup
how familiar are you with the ecs internals?
probably not as familiar as I should be for something like this.
I understand the general terms, but I'd probably need to look at the source code to have a concrete idea.
I'm familiar with how ecs's work in principal at least.
For anything specialized I'd by happy to learn if you happen to have some links handy.
If you haven't read it yet, Sander's wrote a good post about the general machinery we will need/want https://ajmmertens.medium.com/a-roadmap-to-entity-relationships-5b1d11ebb4eb
next step in that post for us is cleanup
the big thing about archetypes is that having relationships will cause the number of archetypes to grow a lot
although we aren't following it exactly (we skipped over components as entities)
currently the various datastructures rely on the fact that
- there won't be that many archetypes
- we won't ever remove them (or need to) because 1
so the goal currently is to adapt the datastructures that deal with archetypes to they handle 1) removing arechtypes (cleanup) and 2) a generally larger amount of them
Ah okay; has there been any progress on that front/is there a draft pr/fork of it wip right now somewhere?
(or is it still conceptual phase)
there are a couple of prs you could adopt, that have somewhat stalled
the first two are relatively easy to adopt, the third one is slightly harder and the last one is very very difficult
the trickiest thing is that most of these are all performance sensitive so you need to do some benchmarking
FYI, I'm very happy to review and merge benchmark PRs ahead of time where possible!
Hey ๐ had a read through that roadmap, and I'm curious, are relationships planned to be able to store data eventually? It's not on the roadmap (at least not explicitly) so I would assume it would happen after all those items, if at all
I believe so? Guess I always assumed, since they are represented by rust types.
Yes. Flecs already has this.
Yes
Fantastic ๐
Yo folks! I've been a bit active in this space, perhaps a year ago, but have been on a long hiatus since. However, since the closing of pull #15635, I think I want to get back into it a bit. I feel like that pull got closed not so much because of the approach or the extra features, but because there is no clear consensus on how the next step for the API for relations should look, especially in terms of consistency, regardless of the underlying implementation of those relations.
So, I propose the following:
- I'll start asking questions one by one to flesh out the important bits of the API and consistency model
- A hackmd is created to iterate on the design together, probably based on the contents of rfc pull #78 / #18
- Decisions for which there is consensus are recorded down so we don't need to revisit them in each pull
While we don't want to write an implementation that is impossible to implement, I'd rather not want to go into fragmenting / non-fragmenting relations and the whole implementation plan. Instead the first step is understanding the API and how it interacts with reflection, scene save / load, direct mutations, etc. And even though the API design should go further, I'm looking for something that's realistically compatible with the Parent/Children components that are in use today, or defines a path on how users will move from those components to whatever API we wish to have for this thing.
And just to avoid misunderstandings, I don't want to dictate how the API is or how to do this thing, I merely want to offer my help in facilitating the discussion and moving realistically forward - if my help is accepted.
Making sure the peeps I know that have been heavily part of this in the past see this: @cloud plover @unborn quail @polar vortex @viscid python
https://github.com/bevyengine/bevy/pull/15635 is the PR in question
I'd really like this, and am willing to help however I can
Before doing any of this the uncontroversial items like having hooks cleanup hierarchy to prevent dangling entities & preventing invalid hierarchies should be implemented.
In separate PRs.
I strongly disagree with this
That work is generally valuable
But we need an actual plan, and a single source of truth we can point contributors to
Hierarchy QOL does not impede any relations work & is already scoped out.
Sure, go ahead and implement it. But there's no reason to block design work.
Like said, I want talk about the API, how it looks โ any implementation work is independent of that
Yeah but the inverse shouldn't happen either.
Hey that's me!
Feel free to request my review on any PRs related to this or shoot me any messages if you'd like some help ๐ I closed the original PR because it became clear to me that there's still room for design work here, so it'd be fantastic for you to undertake that crucial first step that I glossed over.
I generally agree ๐ I do think that some design work on "preventing invalid hierarchies" needs to be done though
And in particular, what level of guarantees we want to provide
What exactly? I can do a write up if anything's unclear ๐ค. I have a branch doing this, just been sick the past few days & it's just a looot of code to port because Parent & Children become private.
I'm not at all opposed to progressing in the background for fragmenting relations or just uncontroversial work items related to this - as long as every step is an improvement over status quo, it's just a positive and shouldn't be blocked
If Parent & Children becoming private is transparent for the user, then I think it's uncontroversial โ if it requires a migration guide entry, then I think we should probably be aligned on if we want to hide those or not ๐
There's like a 1 line migration that's about it.
The stuff you could do before like directly replacing components would invalidate hierarchies so it's no longer allowed.
Yeah, the user-facing impact is effectively my only concern here. The migration guide would be nice to see
And fully agree on splitting it out into small PRs!
The only thing that breaks is this:
let p = foo.take::<Parent>().unwrap();
bar.insert(p);
The migration would just be this:
- Query<&Parent>
+ Query<Parent>
- e.get::<Parent>()
+ e.get_components::<Parent>()
I personally believe we should also align this with https://github.com/bevyengine/bevy/discussions/14437 - how are relations written in bsn! syntax? How will they show up in scene files? If we decide all the relevant components are private, then it will also impact that side โ otherwise it's possible to create invalid hierarchies by manually writing a scene that contains the wrong components.
let p = foo.take::<Parent>().unwrap();
bar.insert(p);
I consider this absolutely awful in the first place ๐ But I don't know if people actually write code like that.
They don't which is all the more reason I'm fine with breaking it
Yeah, I'm very okay with users not bieng able to manually remove the Parent / Child component
Pretty sure bsn allows for bundles ๐ค. The component just gets replaced with a Bundle + QueryData.
bsn! is probably the biggest reason to keep Parent as a public component. The alternative is either bsn! gets command support (not bad?) or you create a dummy component, ParentInserter which unwraps into a Parent
ParentInserter is definitely not going to fly IMO
I don't think bundles are part of the data that is written to files when saving a scene and loading a scene โ they are only at insertion time, so even if bsn! syntax allows it, that doesn't really tell us how they interact with things the user can manually edit
- e.get::<Parent>()
+ e.get_components::<Parent>()
Quick question about this... if I have an EntityRef, can I call get_components on it directly? Or do I need some Query separately?
That's fine. Like you cannot stop them messing up scene files. Those really should not be manually edited they should be edited by tools.
Ref is fine
I believe this works now
"hand authorable" is a relatively important goal IIRC
Well, that's part of the consistency model then โ scene files are allowed to break consistency, so that should be written down as a decision.
But it does get a lot harder with relations
Json is "hand authorable" doesn't mean you can't fuck it up. They are well outside our API boundaries if they are manually eiditing scenes. We literally cannot promise anything. Even if the components weren't private this would still be a problem.
Yeah, I agree with that ๐
We need to have a validation step during scene parsing
(which is important to write down)
And I would like to work with #1264881140007702558 to figure out syntax for it
Yeah, there's a gradient:
- relations are never out of sync
- relations are only out of sync if using manually edited scenes
- relations are out of sync if mutating components manually, but not if replacing them whole
- relations are out of sync if not using the blessed API but touching components manually
Now we are at 4, I think ๐
Also while sure planning helps get more contributor bandwidth planning for this is a bit premature. It is likely other parts of bevy will not work with BSN immediately & it will be easier to design for when BSN is actually released.
Well, the big question in my mind is: the next generation scene/ui system seems to capitalize on components at the primary concept that needs to be understood, not bundles or other things. that there's a simple mental model that covers everything and not multiple separate models. So, if relations are not components, doesn't that mean that there's two models to understand โ components and relations? Is that a good direction and also blessed by cart?
- Is doable with a validation stage. Relation edge data is one of the easier parts to sync it's the other stuff (query caches & what not) that will be a headache.
I think this is mostly a question for #1264881140007702558; let me forward it there
Not opposed, but I'd perhaps flesh it out a bit more here first ๐
There are a plethora of crates that have private components so this is a problem for BSN imo.
So, if relations are not components, doesn't that mean that there's two models to understand โ components and relations?
Relations are components.
They're going to require a new notation but they're still components.
(this is why I would really like a central user-facing design doc)
if i had to hazard a guess, i think bsn will go through several rounds of iteration once it's in active use, especially as it relates to the identification of entities within the scene document. once we get that nailed down, i would generally expect that the hierarchy continues to work as expected (even when the hierarchy is relationified) and that additional relations/links can be created within a scene as normal.
theres other stuff, like relations between scenes, and currently i sort of expect we won't even try to support that
all conjecture on my part
I'm just not sure what that's supposed to do? What would I be designing? Every aspect of the relations API? Most of "how would X look in bevy" is just "look at flecs we're copying that" & the other questions like "how's it going to play with bsn"... bsn doesn't exist yet so you're asking to design how it'd behave with something that doesn't exist? It's a dsl anyway we can define whatever we want. Like how detailed are you imagining this doc to be? Do you just want imagined rust & bsn versions of the examples in the flecs manual? Cause a massive plan outlining everything is not feesable or productive.
Most of "how would X look in bevy" is just "look at flecs we're copying that"
I'm a maintainer and SME-ECS. This is not what we're doing.
onboard people who can help to review the prs.
You're welcome to not participate in the process of "define what the API should look like", but I would appreciate if you stopped discouraging others from pursuing valuable work.
I sincerely hope we are not copying Flecs to do entity.add(Parent, other) as "add" is totally unintuitive at least to me. But I have no opposition on the general idea of looking like the Flecs API, except different where we want it to be different.
Yeah, flecs is a really valuable source of prior art, and unless we have a good reason, I'd like to align where possible
But "just copy flecs" isn't the plan that @crude oracle or I have in mind, and we've publicly said so in the past
But, I think this discussion does make it pretty clear we need to have a consensus on how entities look and behave from the user point of view โ as people are not aligned on that. I'm not so concerned about the Query API as more query stuff can always be added, but the other stuff is important, regardless of the implementation.
Yup this is definitely not the plan
Plenty of great things to learn from in Flecs, and I do suspect a fair amount of overlap.
But we should consider each design decision on its own and pursue whatever makes the most sense for Bevy
Terminology will be changed ofc but I'd be quite suss if it didn't look very similar to flecs especially the query builders. I've seen this tendency to make things "bevy" which often feels like designs that're trying to be clever with the kind of patterns bevy devs are used to only for it to be reverted into something simpler a few releases later because the design turned out to be impractical.
You're welcome to disagree on any specifics, but that doesn't change the broad shape of the decision: Bevy's API will be defined by Bevy devs, not flecs' decisions.
i mean, isn't that just part of a healthy design process? do you think flecs also dosn't do exploratory design work, and then end up changing it later?
And in order to actually plan that out and evaluate it, we need to write it down.
I would have been of the same opinion a few years ago but over time I'm seeing that people have a tendency to rediscover/reinvent prior art instead of just sitting down with said prior art for a bit first.
i think it's good to have 'design pressure' in favor of both accepted solutions and exploration, it's part of how we self-moderate. it seems like alice and cart reacted poorly to "just copy flecs" because that would be favoring accepted solutions in excess.
there's a middle ground of exploration and borrowing, and part of defining that approach is (as alice said) to write down our choices.
to document when we are choosing to follow flecs, and when not, and the reasons why
I'm also particularly frustrated by @viscid python speaking authoritatively without any granted authority. It's confusing and discouraging. I really value your expertise, but your tone often comes across in a way that shuts down conversations and presents consensus when there is none.
Even as a maintainer and SME, it's important to distinguish between "I think it would be good if" and "we have decided that"
Sorry if I come across as such. I have no authority here & I've made every effort to be careful & prefix my own thoughts with "I" & welcome if you'd point out anything I missed. I'm not seeing where I went wrong rereading my messages. I'd also like to say I find assertions of "That work is generally valuable" without any explanation frustrating. Cause I have reasons why I think certain things are not valuable (atleast not now) with often the only consensus I can see being:
- Vibes
- We don't like where we are now & want something different
BTW, just for clarity: when I say "we", I mean an inclusive we, as in all of us together - not "we" as in some specific group of people to which I belong and some others do not. It's just a figure of speech for me.
I broadly agree with your critiques of the linked PR, FWIW. Your tone is often very strongly negative and confident though, and discouraging work that you think is a waste of time (like writing a design doc) is a really unhelpful thing to do in the context of Bevy. We are largely communication / decision / motivation limited, not "quantity of work" limited.
I'd really like to record both the what should we do and the "why should we do it" in a single place though.
I agree that our design and decision-making in this working group in particular often feels pretty vibes-based, without the appropriate context or rationale for anyone to really feel confident in the choices made.
I can lay out why I think it's important for us to prioritize writing a design doc focused on user-facing API and terminology if you'd like?
I was discouraging some of the work in the PR but in the case of the design doc I really don't understand it & I'm trying to understand why it's wanted. Cause this has been asked for multiple times, has been produced in a couple of different iterations, with each time not changing much about peoples understanding? I do not see what I currently understand as a "design doc" being helpful for that reason. I believe just getting features out there is much more helpful to people's understanding. Like I recall lots of discussions with a negative reception when it came to one shot systems & observers even for very clear use cases like ui & the editor. That completely changed when the features landed. Just getting the features in peoples hands contributes to their understanding far more than API designs because API designs ime are more easily understood by people already familiar with the subject matter. I think as more pieces of this come into place consensus will be easier to reach which is why I think this is unproductive.
Part of the difficulty with the flex documentation on relations is that it's spread over many blog posts, and often digresses into the history and implementation details.
What would be helpful is something more like a traditional TDD, one which explains the problem, gives background and terminology, and presents a solution in an organized manner.
I have a few reasons why I'm in favor of it:
- New contributors constantly come and ask "how can I help with relations", and there's no good onboarding material to point them to.
- This can evolve into the module docs for the user once it's published.
- I have a hard time tracking the whole set of features in my head, and want an overview to chart out problem areas.
- I want to be able to define a user-facing API so we can get a crude version in place early in the process, and then iteratively refine the backend without breaking users.
Well, for one โ @echo ore writes a pull request which preserves consistency on all operations except component mutation. And to be fair, the ask was to convert current Parent/Children into using component hooks which explicitly do not support catching mutation. And many people seemed fine that the consistency was enforced unless kind of intentionally trying to break it โ but some people like @viscid python were not, saying that consistency should be maintained also for that, meaning that the components need to be private. And it's not clear what people are actually expecting on that front - what is acceptable. By documenting these assumptions, we get these decisions written out and then pull requests will not be rejected based on requirements that nobody has written out or that haven't been generally agreed upon.
IMO even if you take the internal implementation of relations as a black box there is still a huge design space that needs to be explored for the API and how to communicate it to users. Questions like: How do you build queries for them? How to add and remove them? etc. etc. which the MVP RFC doesn't cover exhaustively at all. Having that discussion now can be productive immediately, of course it needs to take internals in to account to some degree. flecs definitely doesn't offer all the answers here, even for the basic stuff that we can assume to be shared (with, without etc.) it's API makes heavy use of operator overloading that don't directly map to rust code. The rust binding can offer inspiration but it's definitely imperfect.
You also need to worry about making sure users actually understand how to use the API in a way that they can apply it to their projects.
I also don't want the implementation PRs to get bogged down with design discussion around APIs, dragging out the process ๐
Those are often the most contentious!
It would be great to have a consensus API already designed before the PRs are even opened, and then we can just check for correctness during code review
You are going to need at least a strawman API to explain to people how it's going to work
You can add appropriate caveats saying "this is not the final API"
As far as the implementation is concerned there are two main routes to relations:
- Something like the implementation in the PR above that's built on top of the current ECS features. This gets it in to the hand of users quickly and we can get some common use cases established. Although I do share @viscid python 's concern of adding all this code and then having to rip it out later.
- Built more deeply into the ECS where relationship edges split archetypes so that queries for them are more efficient and we can build more bespoke support for them. The problem with this is all the technical blockers which I attempt to cover in the RFC (and a lot of which are now resolved). In short we need to support
world.unregister_component(component_id)since when a relationship edge is removed it's like deleting a component and that's really hard, especially refactoring bevy as it exists today.
The amount of API surface that bevy_ecs currently support makes these PRs difficult, it's very repetitive and makes the whole thing less approachable to new contributors. The main thing stopping me from taking a weekend or two and trying to implement the operation is it sounds like a huge chore and if I'm going to write boring code I may as well do it for work ๐ฆ
Yeah, the API duplication in bevy_ecs is quite rough ๐ฆ
In terms of having a placeholder implementation, and the work involved in ripping it out and replacing it, there are really two aspects to this: the burden on the implementers of the feature, and the migration burden on the users of the feature. Something I'd like to understand better is: from a user perspective, is the placeholder a way-stop on the way to true relations, or is it a detour?
If we want to do 1, I'd really like to make sure that there's basically no migration work for users
The linked PR was definitely more of a detour: it introduced a number of concepts and migrating would require breaking your code
But I don't think that has to be true
I would 110% take slow relations with almost no features as a user though
Imo hierarchy is the placer holder way-stop currently but any extensions (like the ones I discouraged in the PR) make it a detour.
I think we can probably create an API that is easy to migrate if we design with that in mind
I agree that the linked PR was definitely a detour, but the important distincition there is โ do we want to support ent.insert(Parent::new(other)) as an API? If we do that, and it behaves correctly, that's something that's really hard to take away from users once it is out there.
To extend the metaphor further, the only way to avoid making a detour is to have a clear idea of the destination ๐
That doesn't seem like the way it should work for edges as components
Generally I think not: I think this needs to be gated behind special syntax
These are exactly the decisions we need to write down โ because the author of the pull certainly thought it'd be good to allow this for the user, and that's exactly what using component hooks enables.
I'm pretty sure you could implement an API similar to the one proposed in the RFC inefficiently now and there would be no migration
I'll concede a bit on the design docs cause I think I've been assuming too much about what people imagine from reading flecs examples but I think any API design docs should be
- small
- focus on relations (ie. not getting into other things like bsn)
- focus on immediate features of relations (basic query functionality)
Yep, totally fine with that as a scope
If we have special syntax for every relation operation, then the only place where hooks are needed is despawn - everything else can happen with the special syntax.
I want something in place that's "less bad than the existing stuff" and "future-compatible"
(IMO, and maybe this is already assumed) We should also make sure the design doc is compelling and detailed enough that users don't need to know anything about flecs to grok it
Keeping everything in the frame of how bevy uses and implements relations makes it easier for new contributors to get started, and not just get told "go look at flecs first"
Personally, while I think the design doc is important for a wide variety of users - the thing I consider most important is writing down the design decisions. Without a proper record of the decisions made, and the non-decisions made, it's impossible to write pulls for the implementation of things as the design discussion starts up in the pull request.
I am actually quite interested in your last point @runic shadow about API duplication. Do you think there's any way in which we can shrink the API, to make it more approachable? What parts of the API are you thinking off. @wary fjord recently did some very good work by unifying a lot of the entity methods of world.
This isn't a big distinction, but I'm envisioning that as a working mode, in the beginning, there's a section at the end of the document that says "Design decisions" and in there there's "Question: Should user be able to insert Parent component manually? Decision: No, this is not allowed, they should use the add_relation API." etc.
All of the entity methods across commands/world/entityref/entitymut etc
Let's move this subconversation to #ecs-dev?
do you think that API surface is unavoidable alice, or are there maybe some usefull cuts we could make?
I plan to continue https://hackmd.io/@bevy/Hyj8Kjy1ye post-0.15 fwiw
But, overall on this topic, I'm going to let things simmer for a while so everybody gets to give their opinion โ but if the originally proposed hackmd method of working on this is suitable, could someone create a document under @bevy where we could start the work? Initial contents could be the current RFC that's on relations.
Or we could have a separate RFC simply for "relations API"
I can do this when I get home this evening
Let's do a hackmd to start
The Github RFC UI is really bad for long-winded conversations ๐ฅฒ
Yeah, I meant that we'd do a hackmd that is for a yet to be proposed new RFC which is only "relations API" and doesn't contain any discussion for the implementation side โ and once it's close to finish, it would be migrated to Github.
As opposed to continuing the RFC that is currently there on the hackmd side. I'm fine either way.
Yeah, let's keep this one scoped to just the API
And if we design nonsense we can trust the implementation folks to yell at us for giving them an impossible spec
Okay, I can try to write a skeleton for it, copying some stuff from the old one, and then we can start going the questions
Now for something different -question: In flecs, when trying to find the children of a certain entity, how are all the archetypes which contain the entity as a ChildOf looked up? What kind of a data structure contains the archetypes? Assuming there's thousands of objects that have at least one child, there's thousands of archetypes in fragmenting relations, so how many memory lookups does it take to get to the archetype (or multiple archetypes) that has the children in a vec?
You do a lookup in the component index (which we have) and you get a list of archetypes
What kind of data structure is the component index?
Hashmap, but we should also investigate low id optimisations
(wouldn't be relevant for ChildOf though)
So it's a hashmap from component id to vec<archetype id> and then based on archetype id, we need to look from Archetypes which has vec<archetype> the corret row which contains an archetype which has vecs for all the components?
The archetype contains a vec of the component ids but it doesn't contain the storage, that's either in an associated table or a sparse set
right, yeah, left out that part of the indirection
Depends how you're querying for it
- You can either query for
(ChildOf, e)in which case it just goes through the component index on query construction - You can query for
(ChildOf, *)in which case you'll firstly get all entities withChildOfat query construction & later narrow down with joins by providinges
Just to be clear, does flecs use a hash map there, or do you just mean that bevy's component index is a hash map?
Bevy and flecs both use a hashmap there, flecs has low id optimisations
(so for low ids it's just an array)
So for the latter case, every time the join provides and e, it will compose a (ChildOf, e) and look it up in the component index to get a list of archetype ids, and then for each of those then looks in the archetypes vec to get to the storage of that archetype (to simplify the last bit a bit)?
I'm not sure what it back tracks from. I think it's the query's matched archetype list because that's usually smaller than the archetype list in the component index which will have more archetypes that weren't matched (or at worst the exact same number).
Well, we are talking about the parent query here, so (ChildOf, *) archetype list matches every distinct parent so there'd be thousands of archetypes. Obviously the other components might filter the list, but the archetypes for a specific parent must be looked up in the component index, because every other list that contains those has a ton of archetypes. It can possibly skip the archetype ids it knows do not contain some other component needed by the query, but that's a separate part of the filtering I'm not so interested in.
Oh wait yeah mixed those up. Component index look up should be quicker because componentindex[(ChildOf, e)] basically gives you Children for e. (except not an entity list but an archetype list with each archetype possibly containing multiple entities)
Well, it gives you the archetype ids, and looking up the archetypes from the archetype vectors gives the storages and the storages then have all the components for the children, including an Entity vec which is equivalent to the Children component on Bevy. But for flecs, looking up the entity ids of each of the children isn't really necessary, unless they are also a parent and the query is recursive. Just stating out the obvious to ensure my understanding is correct.
Right, you edited it to say the same thing pretty much.
Yeah the main challenge with making a forward compatible query api for hierarchy is that to do joins with hierarchy you need to get component data. With flecs & "true relations" you don't.
One more question: when a new (ChildOf, e) is added on an entity - does it allocate a new archetype id for that if that's the first time (ChildOf, e) has appeared for a specific e? Are the archetype ids allocated sequentially, and then the archetype is appended to a linear archetype vec?
Which arechtype vec are you referring to?
A global vec of all archetypes, if such exists for flecs as well.
In bevy it's Archetypes and it contains Vec<Archetype> and archetype id is an index into that vec. If I understand correctly.
It'll make a new archetype anyway. Not sure about flec's archetype storage.
Not 100% conclusive, but:
/* Tables */
ecs_sparse_t tables; /* sparse<table_id, ecs_table_t> */
/* Table lookup by hash */
ecs_hashmap_t table_map; /* hashmap<ecs_type_t, ecs_table_t*> */
So, I'd say that the storage of archetypes in flecs is sparse<>, but that the component index doesn't point to archetype ids (table ids) but instead directly to the archetypes (tables) via pointers.
The flecs documentation and blog posts are two different things. The documentation is here: https://www.flecs.dev/flecs/md_docs_2Docs.html
"just copy" is never a good idea but Bevy is implementing fragmenting relationships, which means that a big part of the design is already locked down (e.g. you will need archetype deletion, you will need to update query caches etc). I think for a lot of these things it's more "not reinventing the wheel" than copying since there are only so many ways you can do something (efficiently)
You're of course welcome to rediscover how to do these things efficiently :p - it took me years to iterate & arrive at my current design
The only area where I do see things diverge is the user facing API because like @runic shadow mentioned the flecs API heavily relies on features that aren't available in Rust
people can complain about .set::<Parent>(e) not being "bevy" but as someone who's done all sorts of type magic with APIs before I don't see any way to make implementing .insert(C) & .insert((C, e)) not be a complete pain in the ass
is the placeholder a way-stop on the way to true relations, or is it a detour?
The biggest "danger" is that users will rely on things that the placeholder does better, or that features will get developed for the placeholder that can't be easily ported
For example, the current Bevy hierarchy implementation makes it trivial to store an ordered list for a parent's children which is something you can rely on today. That'll get a lot harder with fragmenting relationships
Flecs API is not that, it's: entity.child_of(parent). which is a shortcut to entity.add::<flecs::ChildOf>(parent)
even for the basic stuff that we can assume to be shared (with, without etc.) it's API makes heavy use of operator overloading that don't directly map to rust code. The rust binding can offer inspiration but it's definitely imperfect.
If the operator overloading is what I think it is you're talking about, rust overloads it too
.with::<Position>() is set to default .inout_none
.with::<&Position>() is set to .in()
.with::<&mut Position>() is set to .inout() IIRC
but yeah, the rust API is definitely imperfect, since it reflects for the most part on the CPP API , which does a bunch of function overloading and has stronger generics / specialization, where the cpp API has one .get and .get_mut function, rust flecs has 10 of them, or so in total, but there's no way around it.
preface: I have no idea how bevy versioning works
just my 50 cents reading everyone's concerns, what if you cut all the crap what if's of the API and concerns of breaking the existing API out of the window, and instead of trying to aim for a 0.16 or 0.17 release (which a lot of people do, hope for, and aim for), aim for a major 0.20 Bevy release which introduces breaking API changes to the ECS to support it's ongoing development, such as relationships. I imagine BSN will be part of that as well.
This means though you'd have a separate 0.20 bevy_ecs fork, that needs to stay in sync with PR's / fixes to the main branch, but in my eyes, would avoid a lot of wasted time fighting the current API & would allow for faster commits, iterations, alpha/beta testing the API.
That sounds like you're suggesting break everything in a big release? Rust did that after it introduced editions and quickly learned that while people don't mind breaking changes they don't mind small breaking changes. Bevy wouldn't have a backwards compatibility mechanism either like editions which would make it even more painful.
not break everything, break what's necessary. Similar to how Flecs 3 became Flecs 4. In the end, the changes and migration, won't be as bad as you think, I think.
but the goal is simply to stop bikeshedding about the API, and start developing it
and once developed, you can revisit the API to align it closer to the current API where possible, if possible.
but since this working group existed, the goal was always "next release!" for many people, and I think, that's where a lot of the problems come from because you are required to develop with the current API.
It makes development require more energy tho because you have a separate fork with all this stuff. It doesn't help testing either because people won't be able to use any of the plugins their game uses. Same problem as tracking main.
There was never a release deadline afaik. Just "whenever people can".
yeah there wasn't, but I cannot count the amount of times I've read "I want to land minimal relationships in 0.xx" where xx is the next release, and similarly "I want to contribute to relationships for next bevy release" and countless times "hoping on min relationships next bevy release"
I've been reading those for over 2 years at this point I pay them no mind 
it just complicates things, and if you set a deadline target, which allows more flexibility and separate development, it'll become so much easier in the end
develop + change API -> re-evaluate -> develop more -> refine API (trying to match current API as much as possible)
Does Sander work on flecs full time?
Those kind of maneuvers are much easier when you have/rely on smaller contributor pools. It's harder for bevy to do unless you have a free SME or someone of that caliber that can spearhead PRs.
instead of develop + current API constraint + delay + delay + delay (/s)
iirc he has a job aswell but he does spend a significant amount of time on flecs
Sort of.
Beforehand it was mostly his free time, but recently in his new job, working for Meta (indirectly), which uses Flecs, he's contributing to Flecs during work as well I imagine. I'm jealous, he works with the infamous Mike Acton.
yes
I agree here then, I don't think bevy's development structure would mesh well with doing a side fork
an experimental module could probably work though
Are there any releases with experimental modules currently?
text might be getting one for 0.15, I don't know the current state of that currently though
controversial, then it should change it's development structure. Every engine I know of, including Unity, Godot, Unreal, has done the same in the past, working on separate features completely independant from the main branch, but stay in sync with PR's
don't all those have full paid teams?
A common pattern I've seen in other crates is feature gating experimental items, which would allow for, as an example, new methods, fields, etc.
Meshlets and ghost nodes
I kind of agree. I would like to see more direction from experts instead of trying to inform multiple people on a subject & having them reach consensus. (I basically want Joy or James to be fulltime ecs devs or something lol)
sure, but that didn't stop godot, and in my eyes, you've got dedicated contributors who want relationships to happen, but are stopped by the bureaucracy you're dealing with (including the API)
Yeah I feel way more strongly about user facing API than the performance strategies ๐
We used to have boxy but she left because doing bevy & rust was too much for her mental health & I wouldn't like asking her to return because I don't want that for someone's well being.
FYI this is a hard requirement for UI. If fragmenting relations can't do this we need to reconsider our options
I think this is addressed by having a draft user API written up, agreed on, and then implemented however it has to to reach MVP. The PR that started all this discussion failed in large part because it attempted to do all that in one go.
They can the implementation is just harder
Okay, I'm glad to hear it
But yeah, ordered children is non-negotiable for moving over our main hierarchy
Yeah the reason flecs can do things like this is because the main team is both small & exceptionally skilled.
It seems to me as an outsider that a lot of precursor work has to be done before user-facing features become available. Where I honestly think we should be doing the reverse; offer a relationship API that isn't performant, isn't pretty, but does offer the functionality required. Then fragmenting relationships etc. can come in and make that feature set massively better with a concrete set of benchmarks and user stories to work against
the implementation doesn't have to be in one go, can still be split up, but without the constraint of keeping the current API in a separate fork / branch
The current API doesn't interfere with implementing relations at all though?
and I don't know about you, but personally, I hate admin work, I just want to code, I procrastinate admin work till I have to. That includes writing a fictional draft user API.
then I misread your problems from the last 24h hours
(maybe the things surrounding relationships, like QueryBuilder?)
That's a luxury for small teams tho
I believe a tl;dr is #15635 (mine) proposed a relationship API and implementation that potentially wouldn't be possible with the goal relationship API. Without that API written down, placeholders can't be developed, and the real API becomes bogged down in UX discussions rather than implementation concerns
So this all started because someone charged ahead writing a simple relations API without a clear path for users to go from current solution -> simple -> final, and we didn't have agreement on what it should include / if we should do it
(But feel free to correct me if that summary isn't accurate!)
The existing hierarchy code lives in a totally seperate crate and is just glued on: it's not slowing down development in any way
I see I see ๐ well it was all just a suggestion from an outsider lurking in, where I think having a separate branch would be more beneficial than directly PR'ing into main for relationships
Long-lived branches are really painful to review and keep up to date
(working groups solve some of that pain)
Yep, definitely some
I think Bevy's current approach of doing the long-lived stuff outside Bevy as a plugin and then upstreaming once it's refined/accepted is a good approach
We're not really getting stalled out on admin work or anything: we're just missing a final desired API and there's a few tricky backend PRs to go to make it performant
like someone suggested, don't care "directly" about making it performant on first iteration
I will say if I had a clear API to aim for, I feel like I could've made my PR conformant to it, avoiding the controversy
I think even if you do, you'd still end up 10x slower than current flecs approach (or not, who knows)
Yep, that's 100% my hope ๐
I don't even care if it's slower than our current implementation
(but that will probably block moving Parent/Children over)
my 50 cents, once you have a clear API, do some TDD ๐, write your tests before implementing the functionality
hell, your API draft should include TDD's for a mock API in practice
you'll quickly realize that the draft needs adjustments once you do (which happens to me a lot)
Note that my example of owner/owned does not require order-preserving; I'd be fine in a world where relations exist, but parent/child used a different mechanism.
Yeah, I'm fine to move forward with a partial solution, or alternate storages or whatever
But UI very much needs order-preserving
I'm not sure that normal game world entities do though ๐ค
It would definitely be annoying to have the order not be stable in the inspector though. You'd at least want to stably sort them
I could imagine wanting it even for performance reasons. E.g., all enemies Near the player sorted by distance
Yeah, I think we want query sorting at rest anyways for perf reasons
Which we can use to implement an alternate relations storage (cribbing from @rough gyro here, full disclosure)
Great writeup by @viscid python https://github.com/bevyengine/bevy/issues/13464
Yeah was gonna say that should address most ui sorting needs (atleast all the ones I can think of)
You could also add a lateral Sibling relation
Which would be kind of cursed
But open up some neat relational querying
when you say order preserving, what kind of order? child -> parent ordering, or another form?
(I'm increasingly convinced that only UI needs this for Parent/Child)
Sibling order
Elements of a todo list would be stored as children of a single parent for instance
And we want to ensure that they're laid out in a consistent order
yeah just seems like a "simple" query sort on inserted time, or name
Mmm not really. You can query for that directly. Adding more relations would cause more fragmentation. If you stick to 1 relation (Parent) then all siblings are in the same table which is better for perf.
Yep, and we have an API like this ๐
The hope is just that we can avoid adding a ZIndex-flavored component on UI nodes that stores their relative position in the sibling list
Since that's a pain to maintain
And then you need to pull in the data every time you want to iterate the children in a stable order
can't you just do, like with the todo list, order by insertion time?
How do you get the insertion time? What if you rearrange elements of the list?
Yeah the low..high vecs in queries would essentially be that index if you use sorting.
Like elements in the table? If we use the query sorting I outlined & they have the same Z offset then their order is changed. The sorts should be stable (by default).
if I were to create a todo list like that, I would store a custom ordering component on the parent todo entity, and query for that with the component being optional
insertion time can just be world delta time, and the order function can be stored on the parent as well
Insertion time won't work well (even using change ticks) if they're inserted in the same system
Okay, I'm definitely too sleepy to understand this right now ๐ I'll try again another day
Yeah I should sleep too. If it's still unclear please tell ๐
Fwiw the current implementation of Bevy is also not ideal for UI IMO, because it's a simple vector of child ids which is expensive when doing things like removing a single child from a long list. Storage that's optimized for UI will I think look very differently from plain fragmenting relationships or bevy's Children, and will be more informed by the structure of (bsn) templates
(I would not use query sorting for this, it's too expensive)
just made a mock todo list in flecs, with sorting (which isn't ideal as Sander pointed out, I'd probably rewrite it with a custom cache on the todo list entity that orders on insertion, removing, sort fn changed). Used atomic for the insertion to preserve order. It isn't the best way to go about it either, but simplest solution.
edit: why am I using world time even, the atomic counter would be enough, lol.
-+-+- first run : no custom ordering - use inserted time -+-+-
Todo list: "todo_list"
- "Buy milk"
- "Buy eggs"
- "Buy bread"
-+-+- second run : custom ordering -+-+-
Todo list: "todo_list"
- "Buy bread"
- "Buy milk"
- "Buy eggs"
It's better than not being able to sort at all until a better solution is made.
If you opt out of table reordering the perf should be pretty similar to how we currently sort child nodes.
Here's a quick skeleton for the new RFC - if someone can move it under @bevy and give me edit permissions, then I'd be happy to continue it there: https://hackmd.io/@nakedible/S1SXbF1Wkx
As for the actual content of the RFC, I plan on starting to ask questions here bit by bit
I intend to put it as "publicly" editable so anyone with an account can edit, but I don't want to do that before we move it over to ensure the move is as easy as possible
Can you pass me an edit link and I'll copy it over
any target release version for relationship ? just so i can plan my game developement better
ASAP but uh guessing it is very hard
https://hackmd.io/@bevy/welcome anyone can join the hackmd org
Minimal relations API doc: https://hackmd.io/getbapR5TTCNVuBlNju1Yw
@rough gyro done
yea i don't expect it in 0.16 either, but like is there absolutly no idea or maybe around 0.17-0.18 max ?
(now accessible at https://hackmd.io/@bevy/minimal_relations)
i have to know to know to decide how to plan my dev time
I wouldn't be surprised if something basic and slow was in place for 0.16 or 0.17
ok thanks
It'll take us a long time to get up to full feature parity with flecs though ๐
i just need something to query over a "value" relation ship
(and by the time you have feature parity, flecs will have new features ๐)
Yes, but we'll also have features that you don't ๐
If you know what you're doing the port from relations at home to relations is only going to be a slog it shouldn't be difficult or annoying just monotonous. Removing entity: Entity fields from components & changing queries. So proceed with relations at home.
I didn't know that ๐
I also tend to configure the docs so that anyone can edit them, even without an account. Don't think normal users can do that for their docs, but i'm happy to set it up if people want it.
Whatever is customary and convenient for people, I don't mind either way
I've now removed my old document, so nobody accidentally stumbles upon that anymore, and restored the formatting on the current document
You're the best, thank you
But just realized that the first thing to do is to map the current hierarchy API and the expectations around it. For example that Children is a gettable component that has mutation apis like swap, sort_by, etc. - but the way to access the entities is that it implements Deref<Target=[Entity]>. After that's "fully" mapped, we can have the discussion on backwards compatibility that which features are important to keep and which can replaced with something totally different.
I'll try to do this work first, before going to the actual questions to be resolved
We can also talk about features that it currently has that we don't want it to have
Like the fact that you can mem swap it ๐ฆ
https://github.com/bevyengine/bevy/pull/15923 by @ionic pecan is a good place to look for operations that we care about exposing easily
Currently itโs quite difficult to get the list of ancestors for a given entity. If relations could let us efficiently query and cache that info, it would open up tremendous opportunities for optimization in various tree traversal systems.
yeah that's "up traversal"
Being able to enforce an acyclic graph by construction would also be huge for those same class of algorithms. They are currently unsafe because of the potential for cycles fks with threading.
That's not going to be worth doing. Best you can do is enforce no diamonds. You can add tools to lazily detect if cycles are traversed but enforcing acyclic is the halting problem.
Hmm. We can move the unsafe traversal into the ECS though, right?
currently transform and view propagation use unsafe recursive code
will relations let us move the unsafe protion of that into the ecs and expose a general safe traversal api?
I added a method to get this for 0.15 ๐ It's not fast, but it's a nice API
oho lemme see
hey this is ghost nodes again, no?
Nope, this is the ghost-node free version
the gift that keeps on giving.
Yes. With the new query APIs multiple entities can be touched at once during iteration. For example:
for (transform, parent_transform) in world
.query::<(&mut Transform, &mut Transform)>()
.with::<Parent>("$p")
.term::<1>(|term| term.src("$p"))
.build()
.iter_mut()
{
// ..
}
Can violate mut alias rules if an entity sets itself as its parent (we currently disallow this). So the thing that has to be checked for queries is do their terms ever stomp on them selves. This can also happen in a 3 entity cyclic graph where you get the second transform from your grandparent instead of your parent where you grandparent is you. This shouldn't be terrible tho? Cause the complexity should remain a constant k for any query because its terms will be fixed.
You can enforce an acyclic structure through a validation step. I had to do this last week for a flow-diagram smart form at work
Hmmh, maybe I should make all these lists based on 0.15 instead of 0.14 ๐
That validation step is expensive is the problem
It can be expensive on first-pass but it's trivial to maintain a cache of the calculation steps
Is it painfully expensive if we do it incrementally?
Being able to assert that a relation graph is acyclic would be super useful
You only have to check if each relationship graph mutation creates a cycle, and we can store whatever data on each member of the tree
And again, this is about correctness. Performance is a secondary concern
Important, but it doesn't matter how performant it is if it doesn't do what it needs to
what if I want to do this across threads?
I think it could be useful to store a marker on the members of the tree that says whether the entities, f.e. children, are unique or not
that would also allow doing this across threads (if I understand you correctly in that you mean par_iter_mut)
@echo ore@cloud plover The things mentioned do not solve the problem. Even if you enforce 1 edge type to be acyclic queries can ask for multiple which means you get cycles again depending on how the terms traverse edges. You would have to check all edge types when new edges are crated which can mean going through your entire world. Not only is that impractically expensive but you also make many use cases invalid because it's almost guaranteed that cycles across different edge types will form. This is why this is best checked lazily by queries (which is still a hard problem). I apologize if I'm being overly assertive but this is just the halting problem & it's best to give up on trying to solve this eagerly.
Relationship data is definitely something that would be useful. At a fundamental level, I think what "Relationships" have been referred to thus far is just the concept of a Foreign Key. That foundation then leads into the more complex relationship structures (one-to-many, join tables for many-to-many with data, etc.)
Again I strongly disagree because I just did this at work last week. You have performance concerns, not computability concerns. It is not "halting problem" to determine if a graph is acyclic, the algorithm is trivial and already included in Bevy for the Schedule graph (Tarjan SCC)
Even if you enforce 1 edge type to be acyclic queries can ask for multiple which means you get cycles again
I only care about ensuring that each individual edge type is acylic though?
i think it's vitally important to allow parallel acyclic traversal on a single relation type. if this is not possible, then relations will provide very little value for me and probably won't be useful for transform propagation.
The motivation here is to allow us to kick the unsafe code out of bevy_transform, and allow users to parallely iterate over the transform hierarchy
ideally add safe to parallel acyclic but that's a secondary concern to correct
That won't get rid of the problems is what I'm saying because queries can traverse multiple edges. They will not touch just one. You are used to only dealing with one because hierarchy only has 1 edge type.
Okay, am I missing something stupid โ VisitEntities is implemented for Parent, but not for Children, where as VisitEntitiesMut is implemented for both Parent and Children?
this is all i want
Does VisitEntities for Children automatically work due to IntoIterator<Item=&Entity>?
Which is possible without trying to do this.
I don't understand how
Or why it's undesirable / not valuable to enforce rules about "a single graph type is acyclic"
I don't think anyone is asking for all relationship types to form an acyclic graph? The requirement is that Parent is acyclic. That's all
yep
sander has told me that we can cause individual relations to be topologically sorted, surely that imples that individual relations can be made acyclic
If we had the ability to express this natively with relations, it makes the systems-as-entities migration even easier
I think it might be worth separating acyclic graphs as an invariant vs graphs in general
"I wish for the ECS to maintain this graph in an acyclic state when I mutate it" vs "just give me a graph, I do not care about cycles"
Which if we're making the underlying storage private, we can enforce on mutation, which means we only need to:
- Entity is mutated
- Traverse up the parents, adding each to a hash set
- if a duplicate is found, cyclic! Panic! Abort!
- If no parent is ever found, not cyclic, all good. Let's go!
Definitely, not every relationship type needs (or wants!) to be acyclic
If we maintain a graph that is acyclic, we can then rely on f.e. children not having duplicates, and we no longer need hash sets to parallel traverse mutably
Storing this as an associated constant in the trait impl seems nice
Heck, you don't even need the hashset, just keep going til you hit your initial entity again.
I don't understand what I haven't made clear. 1 edge type being acyclic doesn't solve the problems caused by cycles because queries will not ask for just 1 edge type. If a query asks for &mut Transform from both e1 & e2 here e1 -Foo-> * -Bar-> * -Bazz-> e2 there can be a cycle that can only be checked by the query to prevent UB.
if the query is the type with the problem, then we could have a query variation that maintain the necessary invariant
We're not asking to make queries acyclic, we're talking about making the Parent relationship graph acyclic.
And only expose the dangerous methods in cases that we've validated as being safe
i have a specific query that will be just one edge
Getting errors on insertion makes things so much easier to debug
and i would like it to be acyclic please
If you're really worried about performance, you could even make the cycle detection a debug assertion
"In my game, I'll never make a cycle, because I'm perfect"
This concept could also be applied to sorted queries.
Sorting is an action that can be done after you retrieve the data, or also be maintained as an invariant for every data retrieval. This way you don't surprise either side with the use case of the other
Where have I said that's what you're asking? Where have I said that anyone was asking for all relation types to form an acyclic graph? I'm telling you that's the most flexible & least costly way to check for cycles.
(fwiw probably better as a feature flag than debug assertion since most people compile bevy in release mode)
I really don't understand what you're proposing TBH
I genuinely feel like people aren't even trying to
Like I'm actually mad. I'm gonna leave this discussion.
I was sincerely attempting to understand, FWIW, and have spent quite a bit of time trying to understand your concerns and see if there's a viable alternate strategy here
I can definitely see why it's hard to maintain acyclic guarantees for queries involving multiple relation types
i will cop to not really understanding relations very well, but I have a very specific nail and I want to make sure we are getting the right hammer for it.
I apologize, I am trying to understand your perspective here. I believe you're suggesting making cycle detection the responsibility of the Query rather than an (optional) invariant of a relationship edge type. I'm saying that I believe it is a stronger assertion to have it in the relationship itself, and that it can be done in a performant manner.
I believe it is a stronger assertion to have it in the relationship itself
this is what flecs does fwiw- though the actual detection of cycles is kind of on a best effort basis, since even doing perfect cycle detection in debug mode would slow things down by too much
I'm also not sure whether these are mutually exclusive, the information from a relationship edge type could be used for Query in some cases
Our current queries are "best-effort" sometimes in that QueryFilters can be archetypal or not
Couldn't a similar approach fit here too?
Completely agree, the two ideas are not mutually exclusive
Yeah, or the various ReadOnly traits
And then you could have an unsafe method that transmutes it in some form, like assert_acyclic
Yeah seems reasonable, add(parent) and add_assume_acyclic(parent)
I don't know how queries are meant to interact with relations in the current plans, but I think it's clear that if multiple relations are involved in the same query, there's no guarantees that there are no duplicate entities even if everything is acyclic. For example, if there's relations Mother and Father, and we live in Alabama, there's no guarantees that e -> Mother -> Father and e -> Father -> Father are two distinct entities.
Thinking about it more, a HashSet for maintaining the uniqueness invariant is likely not avoidable, but it could be lazily updated/skipped for some operations
(In that there are transformations that preserve/do not touch uniqueness, and thus don't need the check)
Personally, I think acyclicity is just one consistency rule that may need to be maintained for some relations, but it's certainly not the only one. So I'm more thinking like component hooks, that relations should have hooks on insertion / replace that can do the necessary checks to maintain consistency. And Parent/Children may have a check for cycles. But, there's many ways to design this.
There's a bunch of options. You can go storage-less and just iterate up the parents until you find the root ancestor or you find your original entity again. Or you can store a NรN matrix of descendants, or each entity could store a hashset of its ancestors, all sorts of possibilities
Yep, I'd like a number of consistency rules
Agreed. We should provide some fundamental relationship invariant options, but exposing hooks/observers for those invariants makes a lot of sense
"Tree structure" for rexample
And we'll want to be able to encode that information into the component metadata somehow, as then expose it at the type level if it's static
Same as we do for dense/sparse storage
I reckon we need a Relationship trait with those items. Especially since we might want to enforce that the component is a ZST
Yep, probably Relationship: Component
As Joy would remind us though, it's important that the ECS itself is the source of truth for this data, not the type system
Because otherwise it breaks dynamic representations
That is a good point. I guess the Relationship trait could provide object-safe methods for getting its invariants?
Personally, I didn't hate the proposal in the earlier pull which just had PhantomData<fn(&R)>. Any type could be the marker for the relation and wouldn't need to be a ZST, but it was also obvious that the it's not actually contained in the relation. So I'm not yet sold on enforcing Relation to be a separate trait like Component. But we'll see.
To allow dyn Relationship (or a dyn ObjectSafeRelation)
Yeah, being able to have object-safe representations of both components and relations would be really handy
Kind of orthogonal, but a useful desiderata
Threw a section onto the hack document including that we would like invariants to be available for relationships, with some examples of common invariants
Nice!
We absolutely can't call them "component traits" in Bevy ๐
I know, but same idea
The acyclic/traversable traits are what's preventing applications from accidentally creating queries that can end up in infinite loops
I believe you're suggesting making cycle detection the responsibility of the
Query
All of my suggestions have been just this. As was already mentioned they're not mutually exclusive with edge properties they'd just be what does the best effort checking.
We don't just have to worry about infinite loops we have extra problems flecs does not because of rust's mut aliasing rules
If a query asks for
&mut Transformfrom bothe1&e2heree1 -Foo-> * -Bar-> * -Bazz-> e2there can be a cycle that can only be checked by the query
which is all the more reason to have queries check edge invariants like cycles instead of trying to have a command/hook/whatever check them eagerly on edge mutations. Simpler invariants like exclusivity, symmetry or cleanup can be upheld by hooks & observers.
I agree, the Query will need to check these things. My reason for saying we still want to include the option for acyclic invariance at the relationship level is that Query isn't the only way to traverse this data. E.g., getting the Parent from an entity using EntityRef. Having the invariant available at the relationship level also allows the Query to rely on it for its own validation. E.g., instead of needing to validate all relationship joins, it can skip validation on single-edge joins where the edge is acyclic, since by those invariants e1 -Parent-> e2 will always produce two unique entities e1 and e2.
From a debugging perspective as well, enforcing the invariant in the relationship means you're more likely to detect and log/panic when the invariant is broken, rather than when it is noticed
Yeah, I think "invariant" is a strong word.
IMO, at best, query traversal can notice cycles during whatever kind of traversal it's doing. (Enforcing cleanup polices would give more opportunities to detect them.)
We definitely wouldn't want to eagerly run the SCC algorithm every single time the relationship gets added to something.
You wouldn't need to run the SCC algorithm for every change. Since relationship mutation would only be possible via a highly controlled API, we can run a single up-traversal starting at the mutated entity, and halting when either we hit a root, or ourselves (cycle detected)
I do agree with making it a feature flag so it can be disabled for maximum performance, but even included it shouldn't make much of a difference, especially with some appropriate caching
Also would only need to check on adding a relationship, since removing cannot create a cycle
What if you the loop goes back to B, the second entity up and not A, the starting entity
Would only be possible if the cycle already existed.
Since the check would be run on each mutation, you know that the previous state was already cycle free
Meaning the only cycle that could exist must include the current entity
IIRC you and I have talked about this before, but queries with variables and/or traversal shouldn't pose too much of a borrowchk problem if bevy is willing to tweak its approach to add some runtime borrowchk.
Because it's way harder to statically prove that complex queries can't match the same archetype without being ultra pessimistic.
In a single query, each source variable contributes a set of matching archetypes. And you often can't statically guarantee multiple variables won't get the same value, so there's nCk subsets of terms that have the same source.
// Just think about what sets of archetypes this can match.
A, B($this, $a), C($a), D($a, $b), E($b)
And to check if two queries are disjoint, I believe you'd need to test each subset in Q1 against every subset in Q2.
And that's kind of the easier problem. The harder problem is that complex queries can potentially alias themselves.
// What if $this == $x?
T, R($this, $x), T($x)
So IMO what we should do is give each archetype-component a ref-counter for query terms to increment/decrement so Bevy can runtime borrowchk and error/panic instead of aliasing.
A big challenge with this is that you need really fine grained runtime checking, or you get arbitrary runtime panics
Like, it would suck to not let users construct queries like this one (or worse, require unsafe to construct them). IMO it's better to use the ref-counters to prevent aliasing, along with an escape hatchโan iterator that yields pointers instead of references (the iterator would be safe but user would have to invoke unsafe to deref).
E.g. it needs to happen at the individual entity/component level, not archetype/table column level
๐ค to me, more panics sound preferable over paying to borrow-check component values individually
that's why I'd have the iterator as a backup
so Bevy can runtime borrowchk and error/panic instead of aliasing
What I mean is that this runtime borrowcheck needs to be per component instance, not column
Guess we Arc<T>'ing it all up ๐คก
I think I understood you properly. Per-column sounds preferable to me than per-instance atm.
Yeah, but the issue with per column is that you get really arbitrary failures
Because if a query happens to match two entities that at that moment in time are in the same table, you'd get a panic
Even though the query is not really aliasing
And it's really easy to end up in that situation
Likes($x, $y), Likes($y, $x), Name($x), Name($y)
right, and that'll be annoying, but pushing people to reach for the "unsafe iterator" in those cases is a viable escape hatch
If x and y are in the same table, query will panic
I'm also not hot on per component instance checks. It's the main reason why the flecs rust binding has these kinds of loopholes
What you're suggesting with an unsafe iterator is essentially the same as what flecs rust is doing
If you want to make it 100% safe you either have to compromise big on performance, or give up on a lot of useful features
IIRC I got the idea from you and Indradb, shortly after the rust bindings were released
I had been thinking about how to statically prove that two complex queries don't alias and about requiring unsafe to use a query that could alias itself and I had to agree back then that it would scare off people from using these features, so panics with an escape hatch just seemed better.
Probably irrelevant, but in this example, why would an entity contain both directions of Likes?
some entities like themselves, so it would borrow the same components twice
This query matches entities that like each other
Oh duh, sorry i misread
If an entity likes itself you actually would get a valid aliasing issue
The problem I wanted to illustrate here is that it's not at all inconceivable that two different entities for x and y are in the same table
Which would cause a runtime panic if you do column only safety checks
Even though there's not strictly speaking an aliasing issue
at this point, I see the strongly-typed API as a best-fit approximation of what the underlying "ECS runtime" can do
Hmm would an 'Arc at home' be cheaper/less expensive then just using Arc 
In that example, and I think many of the common cases, you could determine during initialisation that the two sources that access the same components are $x and $y and add one equality check to the query plan. Not free, but much cheaper than locks
I think 'Arc at home' will just make the code (much) shorter
That's an interesting idea
It wouldn't be 100% safe still since you can obtain references through other APIs
But it would provide better checking for queries yeah
Well in bevy that's already blocked in most cases
equality check to skip results where they're equal?
You can customise the behaviour, warn panic or skip
(I was sketching this out while imagining how I would re-write the bevy query engine to support variables and such)
Including the ability to run nested queries?
Bevy already checks at initialisation if two queries in the same system overlap so yeah
I think the biggest issue is actually the *_many(_mut) methods
although I guess those would be covered by the runtime borrowchk
Yeah those methods generally pay some overhead to check for aliasing anyway, although it isn't simple
I've been working on an EntitySet trait that can remove this overhead in cases where we already know there won't be any entity duplicates
it'd be separate *_many_mut_unique methods though
That sounds feasible. Cool idea
Easy to implement as well
It would have a measurable perf impact since a query wouldn't be able to return entities in bulk- or would need an operation that does the alias check in bulk
But probably a lot faster/more storage efficient than per component instance checking
And you only need it for queries that actually can alias, which is not going to be the majority
I think TAA is still technically experimental, because it still has some ghosting/sharpness issues we haven't fixed. We'll never be able to fix absolutely all artifacts, but we can do better then we have.
It does feel a bit quirky to have a database which can either panic and crash, return incorrect results or cause UB
But I guess that's the price for satisfying the borrowchecker
Yeah, as far as rust is concerned the alternative is more UB
orz
I mean, the database will return the correct results, it's just not always safe to deref all terms at once
(and you can easily have a function during initialisation that is marked unsafe that bypasses adding the additional checks/instructions)
Won't this cause any issues in practice? iirc in some of our discussions someone mentioned that the rust compiler makes assumptions on refs not being aliased
It does, the likelihood of actual miscomplications in a case like this is low, but if you are writing and reading from both refs during the system you could get some wacky behaviour
Okay, so that's very much a you're on your own option
Yeah, you should really only use that when you know there are going to be no actual aliases and you just want to omit the checking
Right, so this would cause issues if the above query does match an entity that likes itself
what do we mean by issues?
bevy can't have UB, so there would have to be runtime panics to use the standard API, but yeah, users would be on their own if they used an unsafe iterator escape hatch
Yep, we are only talking about the potential for issues after opting in to unsafe
Unfortunately there is no safe way to allow them to do what they want (alias in a query) without changing something about what is passed to the system
Makes you wonder whether a scripting language binding for bevy should allow for this
It'd be nice to be able to not have to worry about these limitations in a scripting environment
the scripting environment would have to bind the iterator to allow aliasing, so rust would be satisfied as some binding would be calling unsafe
That's interesting to think about, because in theory if you pass raw pointers that alias to the scripts environment it's not like the script is going to miscompile by assuming noalias
Technically should be fine to skip the checks for scripts
Depending on the impl of the binding you may not need to
idk if y'all are focusing on compiler stuff, but isn't the language the ECS is written in irrelevant to whether or not a scripting environment should do anything about aliasing?
Agreed, the ECS doesn't really care what happens once it's passed off the pointers
It's just a little strange that the query results could be different in a scripting environment vs from rust
It's a little strange that a query returns incorrect results period :p The results of the scripting binding would actually be correct
Strangeness will exist with or without scripting
incorrect with the equality check thing, right?
