From 771682d7aaff2509e6a73b6a235d93a2d7218950 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 18:45:27 -0400 Subject: [PATCH 01/82] Template --- rfcs/min-relations.md | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 rfcs/min-relations.md diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md new file mode 100644 index 00000000..4a018bad --- /dev/null +++ b/rfcs/min-relations.md @@ -0,0 +1,73 @@ +# Feature Name: `min-relations` + +## Summary + +One paragraph explanation of the feature. + +## Motivation + +Why are we doing this? What use cases does it support? + +## Guide-level explanation + +Explain the proposal as if it was already included in the engine and you were teaching it to another Bevy user. That generally means: + +- Introducing new named concepts. +- Explaining the feature, ideally through simple examples of solutions to concrete problems. +- Explaining how Bevy users should *think* about the feature, and how it should impact the way they use Bevy. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, explain how this feature compares to similar existing features, and in what situations the user would use each one. + +## Reference-level explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +## Drawbacks + +Why should we *not* do this? + +## Rationale and alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? +- Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? + +## \[Optional\] Prior art + +Discuss prior art, both the good and the bad, in relation to this proposal. +This can include: + +- Does this feature exist in other libraries and what experiences have their community had? +- Papers: Are there any published papers or great posts that discuss this? + +This section is intended to encourage you as an author to think about the lessons from other tools and provide readers of your RFC with a fuller picture. + +Note that while precedent set by other engines is some motivation, it does not on its own motivate an RFC. + +## Unresolved questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +## \[Optional\] Future possibilities + +Think about what the natural extension and evolution of your proposal would +be and how it would affect Bevy as a whole in a holistic way. +Try to use this section as a tool to more fully consider other possible +interactions with the engine in your proposal. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +If a feature or change has no direct value on its own, expand your RFC to include the first valuable feature that would build on it. From 8f450d1171a1966ad8c5d18f03981b347ba41c7c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 19:06:14 -0400 Subject: [PATCH 02/82] Notes on future work --- rfcs/min-relations.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 4a018bad..8fa33eb1 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -59,15 +59,9 @@ Note that while precedent set by other engines is some motivation, it does not o ## \[Optional\] Future possibilities -Think about what the natural extension and evolution of your proposal would -be and how it would affect Bevy as a whole in a holistic way. -Try to use this section as a tool to more fully consider other possible -interactions with the engine in your proposal. - -This is also a good place to "dump ideas", if they are out of scope for the -RFC you are writing but otherwise related. - -Note that having something written down in the future-possibilities section -is not a reason to accept the current or a future RFC; such notes should be -in the section on motivation or rationale in this or subsequent RFCs. -If a feature or change has no direct value on its own, expand your RFC to include the first valuable feature that would build on it. +1. GRAPH +2. Kinded entities +3. Use with UI. +4. Frustum culling. +5. Replace parent-child. +6. Reverse relations. From a45c1114d656e9ed29e1b6ee89b588c504d8e28c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 19:06:24 -0400 Subject: [PATCH 03/82] Summary and motivation --- rfcs/min-relations.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 8fa33eb1..32727553 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -2,11 +2,26 @@ ## Summary -One paragraph explanation of the feature. +Relations connect entities to each other, storing data in these edges. This RFC covers the minimal version of this feature, allowing for basic entity grouping and simple game logic extensions. ## Motivation -Why are we doing this? What use cases does it support? +Entities often need to be aware of each other in complex ways: an attack that targets another unit, groups of entities that are controlled en-masse, or complex parent-child hierarchies that need to move together. + +We *could* simply store an `Entity` in a component, then use `query.get(my_entity)` to access the relevant data. +But this quickly starts to balloon in complexity. + +- How do we ensure that these components are cleaned up when the entity they're pointing to is? +- How do we handle pointing to multiple entities in similar ways? +- How do we look for all entities that point to a particular entity of interest? +- How do we despawn an entity and all of its children (with many types of child-like entities)? +- How do we quickly access data on the entity that we're attached to? +- How do we traverse this graph of entities? +- How do we ensure that our graph is acylic? + +We *could* force our users to solve these problems, over and over again in each individual game. + +This powerful and common pattern deserves a proper abstraction that we can make ergonomic, performant and *fearless*. ## Guide-level explanation From 6868bc56a500471a97ad99d1671c35ea95e5af83 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 19:14:40 -0400 Subject: [PATCH 04/82] Drawbacks --- rfcs/min-relations.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 32727553..de558ec8 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -45,7 +45,10 @@ The section should return to the examples given in the previous section, and exp ## Drawbacks -Why should we *not* do this? +1. Introduces another core abstraction to users that they will want to learn. +Rewriting our parent-child code in terms of relations is important, but will introduce the concept to users quite quickly. +2. Even if relations are more ergonomic than the Entity-in-component approach to accomplish the same tasks, relations will be held to a higher bar for quality. +3. Heavy use of relations can seriously fragment archetypes. We need to be careful that this doesn't badly impact our performance. ## Rationale and alternatives From 8cffbe8dca04752e5c930bfe69c36d2b3f272725 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 19:17:06 -0400 Subject: [PATCH 05/82] Prior art in Flecs --- rfcs/min-relations.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index de558ec8..ed24905f 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -57,17 +57,11 @@ Rewriting our parent-child code in terms of relations is important, but will int - What is the impact of not doing this? - Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? -## \[Optional\] Prior art +## Prior art -Discuss prior art, both the good and the bad, in relation to this proposal. -This can include: - -- Does this feature exist in other libraries and what experiences have their community had? -- Papers: Are there any published papers or great posts that discuss this? - -This section is intended to encourage you as an author to think about the lessons from other tools and provide readers of your RFC with a fuller picture. - -Note that while precedent set by other engines is some motivation, it does not on its own motivate an RFC. +[Flecs](https://github.com/SanderMertens/flecs), an advanced C++ ECS framework, has a very similar implementation, which they call "relationships". +These are subtly different, and use an elaborate query domain-specific language. +You read more about them in the [corresponding PR](https://github.com/SanderMertens/flecs/pull/358). ## Unresolved questions @@ -75,7 +69,7 @@ Note that while precedent set by other engines is some motivation, it does not o - What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? -## \[Optional\] Future possibilities +## Future possibilities 1. GRAPH 2. Kinded entities From 49d4f2a417dfca61d8056d4ef7c2e2e61178522e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 21:37:03 -0400 Subject: [PATCH 06/82] Basic guide-level explanation --- rfcs/min-relations.md | 225 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 202 insertions(+), 23 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index ed24905f..c2afb44c 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -1,5 +1,10 @@ # Feature Name: `min-relations` +// FIXME +// Make sure to talk about these: +// - Reverse relations. +// - Or support for add_target_filter. + ## Summary Relations connect entities to each other, storing data in these edges. This RFC covers the minimal version of this feature, allowing for basic entity grouping and simple game logic extensions. @@ -9,6 +14,7 @@ Relations connect entities to each other, storing data in these edges. This RFC Entities often need to be aware of each other in complex ways: an attack that targets another unit, groups of entities that are controlled en-masse, or complex parent-child hierarchies that need to move together. We *could* simply store an `Entity` in a component, then use `query.get(my_entity)` to access the relevant data. +(In this RFC, we call this the **Entity-in-component pattern**.) But this quickly starts to balloon in complexity. - How do we ensure that these components are cleaned up when the entity they're pointing to is? @@ -25,23 +31,178 @@ This powerful and common pattern deserves a proper abstraction that we can make ## Guide-level explanation -Explain the proposal as if it was already included in the engine and you were teaching it to another Bevy user. That generally means: +**Relations** are components that connect entities together, pointing from the entity they are stored on to the entity that they target. +Just like other components, they can store effectively arbitrary data, or they can be data-less unit structs that serve as **markers**. -- Introducing new named concepts. -- Explaining the feature, ideally through simple examples of solutions to concrete problems. -- Explaining how Bevy users should *think* about the feature, and how it should impact the way they use Bevy. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, explain how this feature compares to similar existing features, and in what situations the user would use each one. +Each relation has three parts: -## Reference-level explanation +1. The **source entity** that it is stored on. +2. The **target entity** (or simply **target**) that it points to. +3. The **relation kind** that defines the data that is stored on it. + +Making your first connection is easy, and mirrors the API that you're familiar with from components: + +```rust +// Relations might just be markers +struct FriendsWith; + +// But they might store data instead +struct Owes(i32); + +// Let's make some entities +let world = World::new(); +let alice = world.spawn().id(); +let boxy = world.spawn().id(); + +// You can make relations with commands! +commands.entity(alice).insert_relation(FriendsWith, boxy); // uwu:3 + +// You can remove them with commands! +commands.entity(alice).remove_relation(FriendsWith, boxy) // not uwu :( + +// You can use direct World access! +world.entity_mut(alice).insert_relation(FriendsWith, boxy); // uwu:3 + +// Relations are directed +commands.entity(boxy).insert_relation(FriendsWith, alice); // one way friendship sad oh wait you reinserted your one nvm rip my joke :( + +// You can mix different relation kinds on the same entity! +world.commands.entity(alice).insert_relation(Owes(9999), boxy); // :))))) + +// You can add mulitple relations of the same kind to a single source entity! +let cart = world.spawn().id(); +world.commands.entity(alice).insert_relation(FriendsWith, cart); // Hi! +``` + +Once your relations are in place, you'll want to access them in some form. +Just like with ordinary components, you can request them in your queries: + +```rust +// Relations can be queried for immutably +fn friendship_is_magic(mut query: Query<(&mut Magic, &Relation), With>) { + // Each entity can have their own friends + for (mut magic, friends) in query.iter_mut(){ + // Relations return an iterator when unpacked + magic.power += friends.count(); + } +} + +// Or you can request mutable access to modify their data +fn interest(query: Query<&mut Relation>){ + for debts in query.iter(){ + for (owed_to, amount_owed) in debts { + println!("we owe {} some money", owed_to); + amount_owed *= 1.05; + } + } +} + +// You can query for entities that target a specific entity +fn friend_of_dorothy( + mut query: Query>>, + dorothy: Res, +) { + // Setting relation filters requires that the query is mutable + query.set_relation_filters( + RelationFilters::new() + .add_target_filter::(dorothy.entity) + ); -This is the technical portion of the RFC. Explain the design in sufficient detail that: + for source_entity in query.iter(){ + println!("{} is friends with Dorothy!", source_entity); + } -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. + // RelationFilters are reset at the end of the system, make sure to set them every time your system runs! + // You can also reset this manually by applying a new `RelationFilters` to the query + // They override existing relation filters, rather than adding to them +} -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. +// Or even look for some combination of targets! +fn caught_in_the_middle(mut query: Query<&mut Stress, With>>, + dorothy: Res, + elphaba: Res){ + + // You can directly chain this as set_relation_filters returns &mut Query + query.set_relation_filters( + RelationFilters::new() + .add_target_filter::(dorothy.entity) + .add_target_filter::(elphaba.entity) + ).for_each(|mut stress| { + stress.val = 1000; + }) +} + +/// If we want to combine targets in an "or" fashion, we need a slightly different syntax +TODO: write example + +// Query filters work too! +fn not_alone( + commands: mut Commands, + query: Query>>, + puppy: Res +) { + for lonely_entity in query.iter(){ + commands.insert_relation(FriendsWith, puppy.entity); + } +} + +// So does change detection with Added and Changed! +fn new_friends(query: Query<&mut Excitement, Added>>){ + query.for_each(|mut excitement|{excitement.value += 10}); +} + +``` + +Sometimes, we may instead want to look for "entities that are targets of some relation". +We can do so by iterating over all entities with that relation, and then filtering by target. + +```rust +fn all_children( + player_query: Query>, + child_query: Query<&Name, (With, Without)>>, +) { + let player_entity = player_query.single.unwrap(); + + child_query.set_relation_filters( + RelationFilters::new() + .add_target_filter::(player_entity) + ); + + for name in child_query.iter() { + println!("{} is one of my children.", name) + } +} +``` + +Finally, you can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. + +```rust +// We need to use a marker component to avoid having multiple mutable references +fn debts_come_due( + mut debtor_query: Query<(&mut Money, Relation), Without>, + mut lender_query: Query<&mut Money, With>, +) { + for (mut debtor_money, debt) in debtor_query.iter(){ + for (lender, amount_owed) in debt { + let mut lender_money = lender_query.get().unwrap(); + if debtor_money >= amount_owed { + debtor_money.dollars -= amount_owed.dollars; + lender_money.dollar += amount_owed.dollar; + } else { + panic!("Nice kneecaps you got there..."); + } + } + } +} +``` + +### Grouping entities + +### Entity hierarchies + +## Reference-level explanation + +TODO: Boxy explains the magic. ## Drawbacks @@ -59,21 +220,39 @@ Rewriting our parent-child code in terms of relations is important, but will int ## Prior art -[Flecs](https://github.com/SanderMertens/flecs), an advanced C++ ECS framework, has a very similar implementation, which they call "relationships". -These are subtly different, and use an elaborate query domain-specific language. +[`Flecs`](https://github.com/SanderMertens/flecs), an advanced C++ ECS framework, has a similar feature, which they call "relationships". +These are somewhat different, they use an elaborate query domain-specific language along with being more feature rich. You read more about them in the [corresponding PR](https://github.com/SanderMertens/flecs/pull/358). ## Unresolved questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? +1. How do we put relations in Bundles? +2. What do we actually call `Noitaler`? Candidates so far are: + 1. `ReverseRelation` + 2. `Inverse` + 3. `CoRelation` + 4. `TargetOf` +3. Do we need a full graph constraint solver in our queries to handle things like "check for unrequited love"? +4. Do we want a macro to make complex calls easier? + +```rust +macro_rules Relation { + (this $kind:ty *) => Relation<$kind>, + (* $kind:ty this) => Noitaler<$kind>, +} +``` ## Future possibilities -1. GRAPH -2. Kinded entities -3. Use with UI. -4. Frustum culling. -5. Replace parent-child. -6. Reverse relations. +1. Data on the target entity. +2. GRAPH +3. Kinded entities +4. Use with UI. +5. Frustum culling. +6. Replace parent-child. +7. Automatically symmetric relations. +8. `Noitaler` +9. Non-self-referential relations using archetype invariants. `Query<&mut Money, Relation>` + +AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. +EDITOR's NOTE: `Noitaler` may very much be used :3 From 1f400339f8364de25340b8abba478620578a2277 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 21:43:33 -0400 Subject: [PATCH 07/82] Improved Future Work section --- rfcs/min-relations.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index c2afb44c..174c5692 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -244,15 +244,23 @@ macro_rules Relation { ## Future possibilities -1. Data on the target entity. -2. GRAPH -3. Kinded entities -4. Use with UI. -5. Frustum culling. -6. Replace parent-child. -7. Automatically symmetric relations. -8. `Noitaler` -9. Non-self-referential relations using archetype invariants. `Query<&mut Money, Relation>` +Relation enhancements: + +1. Sugar for accessing data on the target entity in a single query. `Relation` +2. Graph traversals. +3. Arbitrary target types instead of just `Entity` combined with `KindedEntity` proposal to ensure your targets are valid. +4. Automatically symmetric relations. +5. `Noitaler`* +6. Graph shape guarantees using archetype invariants. +7. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s +8. Assorted Performance optimizations. +9. Relation orders to enable things like `Styles`. + +Relation applications in the engine: + +1. Replace existing parent-child. +2. Applications for UI: styling, widget wiring etc. +3. Frustum culling. AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. EDITOR's NOTE: `Noitaler` may very much be used :3 From 96065d48b2db89d9d9c815d7cd6941570da3551c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 21:53:41 -0400 Subject: [PATCH 08/82] Added `despawn_recursive` to future work --- rfcs/min-relations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 174c5692..bf1323bb 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -255,6 +255,7 @@ Relation enhancements: 7. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s 8. Assorted Performance optimizations. 9. Relation orders to enable things like `Styles`. +10. Generalized `despawn_recursive`. Relation applications in the engine: @@ -263,4 +264,4 @@ Relation applications in the engine: 3. Frustum culling. AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. -EDITOR's NOTE: `Noitaler` may very much be used :3 +EDITOR's NOTE: `Noitaler` may very much be used :3 \ No newline at end of file From f0908963b6c8fa32602b97aa88c3ade623ed5139 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 21:57:28 -0400 Subject: [PATCH 09/82] Relation cleanup note --- rfcs/min-relations.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index bf1323bb..fda2031e 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -153,6 +153,8 @@ fn new_friends(query: Query<&mut Excitement, Added>>){ ``` +TODO: mention relation cleanup on despawn. + Sometimes, we may instead want to look for "entities that are targets of some relation". We can do so by iterating over all entities with that relation, and then filtering by target. From adfb72e4ceba306d8fc55084b6ce5c4c0a0b2503 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 22:03:35 -0400 Subject: [PATCH 10/82] Minor bug fix in example --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index fda2031e..2e550e64 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -187,7 +187,7 @@ fn debts_come_due( for (mut debtor_money, debt) in debtor_query.iter(){ for (lender, amount_owed) in debt { let mut lender_money = lender_query.get().unwrap(); - if debtor_money >= amount_owed { + if debtor_money.dollars >= amount_owed.dollars { debtor_money.dollars -= amount_owed.dollars; lender_money.dollar += amount_owed.dollar; } else { From 94c59d228c658e7bd7630836b39595760ec697c2 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 22 Apr 2021 22:06:42 -0400 Subject: [PATCH 11/82] Actually get the right lender --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 2e550e64..d2e9a9ef 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -186,7 +186,7 @@ fn debts_come_due( ) { for (mut debtor_money, debt) in debtor_query.iter(){ for (lender, amount_owed) in debt { - let mut lender_money = lender_query.get().unwrap(); + let mut lender_money = lender_query.get(lender).unwrap(); if debtor_money.dollars >= amount_owed.dollars { debtor_money.dollars -= amount_owed.dollars; lender_money.dollar += amount_owed.dollar; From 5636cec447c7542dd2d182547f705b70221efc13 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 14:43:53 -0400 Subject: [PATCH 12/82] Improved examples Credit to Boxy for finding these --- rfcs/min-relations.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index d2e9a9ef..6b9a8c2f 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -63,15 +63,15 @@ commands.entity(alice).remove_relation(FriendsWith, boxy) // not uwu :( // You can use direct World access! world.entity_mut(alice).insert_relation(FriendsWith, boxy); // uwu:3 -// Relations are directed -commands.entity(boxy).insert_relation(FriendsWith, alice); // one way friendship sad oh wait you reinserted your one nvm rip my joke :( +// Relations are directed; a relation in one direction does not imply the same in the other direction +commands.entity(boxy).insert_relation(FriendsWith, alice); // one way friendship sad :( // You can mix different relation kinds on the same entity! -world.commands.entity(alice).insert_relation(Owes(9999), boxy); // :))))) +commands.entity(alice).insert_relation(Owes(9999), boxy); // :))))) // You can add mulitple relations of the same kind to a single source entity! let cart = world.spawn().id(); -world.commands.entity(alice).insert_relation(FriendsWith, cart); // Hi! +commands.entity(alice).insert_relation(FriendsWith, cart); // Hi! ``` Once your relations are in place, you'll want to access them in some form. @@ -87,7 +87,7 @@ fn friendship_is_magic(mut query: Query<(&mut Magic, &Relation), Wi } } -// Or you can request mutable access to modify their data +// Or you can request mutable access to modify the relation's data fn interest(query: Query<&mut Relation>){ for debts in query.iter(){ for (owed_to, amount_owed) in debts { @@ -124,10 +124,12 @@ fn caught_in_the_middle(mut query: Query<&mut Stress, With // You can directly chain this as set_relation_filters returns &mut Query query.set_relation_filters( + // Note that we can set relation filters even if the relation is in a query filter RelationFilters::new() .add_target_filter::(dorothy.entity) .add_target_filter::(elphaba.entity) ).for_each(|mut stress| { + println!("{} is friends with Dorothy!", source_entity); stress.val = 1000; }) } From c4e2af67ea5b74dbfa51a56702a4efb9089f1c08 Mon Sep 17 00:00:00 2001 From: Ellen Date: Fri, 23 Apr 2021 20:08:17 +0100 Subject: [PATCH 13/82] boop --- rfcs/min-relations.md | 92 ++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 6b9a8c2f..69e2b866 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -80,21 +80,21 @@ Just like with ordinary components, you can request them in your queries: ```rust // Relations can be queried for immutably fn friendship_is_magic(mut query: Query<(&mut Magic, &Relation), With>) { - // Each entity can have their own friends + // Each entity can have their own friends for (mut magic, friends) in query.iter_mut(){ - // Relations return an iterator when unpacked - magic.power += friends.count(); - } + // Relations return an iterator when unpacked + magic.power += friends.count(); + } } // Or you can request mutable access to modify the relation's data fn interest(query: Query<&mut Relation>){ - for debts in query.iter(){ - for (owed_to, amount_owed) in debts { - println!("we owe {} some money", owed_to); + for debts in query.iter(){ + for (owed_to, amount_owed) in debts { + println!("we owe {} some money", owed_to); amount_owed *= 1.05; - } - } + } + } } // You can query for entities that target a specific entity @@ -102,36 +102,38 @@ fn friend_of_dorothy( mut query: Query>>, dorothy: Res, ) { - // Setting relation filters requires that the query is mutable - query.set_relation_filters( + // Setting relation filters requires that the query is mutable + query.set_relation_filters( RelationFilters::new() .add_target_filter::(dorothy.entity) ); - for source_entity in query.iter(){ - println!("{} is friends with Dorothy!", source_entity); - } + for source_entity in query.iter(){ + println!("{} is friends with Dorothy!", source_entity); + } // RelationFilters are reset at the end of the system, make sure to set them every time your system runs! - // You can also reset this manually by applying a new `RelationFilters` to the query - // They override existing relation filters, rather than adding to them + // You can also reset this manually by applying a new `RelationFilters` to the query + // They override existing relation filters, rather than adding to them } // Or even look for some combination of targets! fn caught_in_the_middle(mut query: Query<&mut Stress, With>>, - dorothy: Res, - elphaba: Res){ - - // You can directly chain this as set_relation_filters returns &mut Query - query.set_relation_filters( - // Note that we can set relation filters even if the relation is in a query filter + dorothy: Res, + elphaba: Res){ + + // You can directly chain this as set_relation_filters returns &mut Query + query.set_relation_filters( + // Note that we can set relation filters even if the relation is in a query filter RelationFilters::new() + // Note: add_target_filter is additive- i.e. entities this query + // matches must be friends with *both* dorothy AND elphaba .add_target_filter::(dorothy.entity) - .add_target_filter::(elphaba.entity) + .add_target_filter::(elphaba.entity) ).for_each(|mut stress| { - println!("{} is friends with Dorothy!", source_entity); - stress.val = 1000; - }) + println!("{} is friends with Dorothy and Elphaba!", source_entity); + stress.val = 1000; + }) } /// If we want to combine targets in an "or" fashion, we need a slightly different syntax @@ -141,16 +143,16 @@ TODO: write example fn not_alone( commands: mut Commands, query: Query>>, - puppy: Res + puppy: Res ) { - for lonely_entity in query.iter(){ - commands.insert_relation(FriendsWith, puppy.entity); - } + for lonely_entity in query.iter(){ + commands.insert_relation(FriendsWith, puppy.entity); + } } // So does change detection with Added and Changed! fn new_friends(query: Query<&mut Excitement, Added>>){ - query.for_each(|mut excitement|{excitement.value += 10}); + query.for_each(|mut excitement|{excitement.value += 10}); } ``` @@ -165,14 +167,14 @@ fn all_children( player_query: Query>, child_query: Query<&Name, (With, Without)>>, ) { - let player_entity = player_query.single.unwrap(); - + let player_entity = player_query.single.unwrap(); + child_query.set_relation_filters( RelationFilters::new() .add_target_filter::(player_entity) ); - for name in child_query.iter() { + for name in child_query.iter() { println!("{} is one of my children.", name) } } @@ -186,17 +188,17 @@ fn debts_come_due( mut debtor_query: Query<(&mut Money, Relation), Without>, mut lender_query: Query<&mut Money, With>, ) { - for (mut debtor_money, debt) in debtor_query.iter(){ - for (lender, amount_owed) in debt { - let mut lender_money = lender_query.get(lender).unwrap(); - if debtor_money.dollars >= amount_owed.dollars { - debtor_money.dollars -= amount_owed.dollars; - lender_money.dollar += amount_owed.dollar; - } else { - panic!("Nice kneecaps you got there..."); - } - } - } + for (mut debtor_money, debt) in debtor_query.iter(){ + for (lender, amount_owed) in debt { + let mut lender_money = lender_query.get(lender).unwrap(); + if debtor_money.dollars >= amount_owed.dollars { + debtor_money.dollars -= amount_owed.dollars; + lender_money.dollar += amount_owed.dollar; + } else { + panic!("Nice kneecaps you got there..."); + } + } + } } ``` From 23b975b278539bfc06c7c2f08400739f93efa978 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 17:09:34 -0400 Subject: [PATCH 14/82] Remove unnecessary filter in all_children example --- rfcs/min-relations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 69e2b866..faf06282 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -165,7 +165,7 @@ We can do so by iterating over all entities with that relation, and then filteri ```rust fn all_children( player_query: Query>, - child_query: Query<&Name, (With, Without)>>, + child_query: Query<&Name, With>>, ) { let player_entity = player_query.single.unwrap(); @@ -270,4 +270,4 @@ Relation applications in the engine: 3. Frustum culling. AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. -EDITOR's NOTE: `Noitaler` may very much be used :3 \ No newline at end of file +EDITOR's NOTE: `Noitaler` may very much be used :3 From cf82d3ad0e56dc8ab06ed38532ab16427d035efc Mon Sep 17 00:00:00 2001 From: Ellen Date: Fri, 23 Apr 2021 22:24:53 +0100 Subject: [PATCH 15/82] merge conflcits sucks :_: --- rfcs/min-relations.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 69e2b866..b891e546 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -185,18 +185,15 @@ Finally, you can use the `Entity` returned by your relations to fetch data from ```rust // We need to use a marker component to avoid having multiple mutable references fn debts_come_due( - mut debtor_query: Query<(&mut Money, Relation), Without>, - mut lender_query: Query<&mut Money, With>, + mut debt_query: Query<(Entity, Relation)>, + mut money_query: Query<&mut Money>, ) { - for (mut debtor_money, debt) in debtor_query.iter(){ + for (debtor, debt) in debt.query() { for (lender, amount_owed) in debt { - let mut lender_money = lender_query.get(lender).unwrap(); - if debtor_money.dollars >= amount_owed.dollars { - debtor_money.dollars -= amount_owed.dollars; - lender_money.dollar += amount_owed.dollar; - } else { - panic!("Nice kneecaps you got there..."); - } + *money_query.get(debtor).unwrap() + .checked_sub(amount_owed) + .expect("Nice kneecaps you got there..."); + *money_query.get(lender).unwrap() += amount_owed; } } } From df96a018aa356d986ec28508bb135de6d4bc928a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 19:12:58 -0400 Subject: [PATCH 16/82] Improved clarity of introduction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index f653ed2e..143a6fe4 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -7,7 +7,10 @@ ## Summary -Relations connect entities to each other, storing data in these edges. This RFC covers the minimal version of this feature, allowing for basic entity grouping and simple game logic extensions. +Relations allow users to create logical connection between entities. Each source entity can have multiple kinds of relations, each with their own data, and multiple relations of the same kind pointing to a different target entity. +Queries are expanded to allow efficient filtering of entities by the relations they have or the targets of these relations. + +This RFC covers the minimal version of this feature, allowing for basic dynamic entity grouping and simple game logic extensions. ## Motivation From 55dd9884380eba7a3ce44c4706583c4bd69c79c1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 20:07:56 -0400 Subject: [PATCH 17/82] Relations are not components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 143a6fe4..025f8243 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -34,8 +34,8 @@ This powerful and common pattern deserves a proper abstraction that we can make ## Guide-level explanation -**Relations** are components that connect entities together, pointing from the entity they are stored on to the entity that they target. -Just like other components, they can store effectively arbitrary data, or they can be data-less unit structs that serve as **markers**. +**Relations** are similar to components that connect entities together, pointing from the entity they are stored on to the entity that they target. +Just like components, they can store effectively arbitrary data, or they can be data-less unit structs that serve as **markers**. Unlike components, they don't store the target entity directly, as that connection is managed by the engine. Each relation has three parts: From a7aff667942325a8868fbfb1f9452c5a8b05534f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 20:08:26 -0400 Subject: [PATCH 18/82] Fix example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 025f8243..7c43cc7b 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -155,7 +155,9 @@ fn not_alone( // So does change detection with Added and Changed! fn new_friends(query: Query<&mut Excitement, Added>>){ - query.for_each(|mut excitement|{excitement.value += 10}); + query.for_each(|(mut excitement, new_friends)| { + excitement.value += 10 * new_friends.count(); + }); } ``` From 2bbd8389450e6caec3cb8ceff615829e6123c452 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 20:21:54 -0400 Subject: [PATCH 19/82] Reduce optimism on learning burden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 7c43cc7b..f89a5786 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -214,7 +214,7 @@ TODO: Boxy explains the magic. ## Drawbacks -1. Introduces another core abstraction to users that they will want to learn. +1. Introduces another core abstraction to users that they will have to learn. Rewriting our parent-child code in terms of relations is important, but will introduce the concept to users quite quickly. 2. Even if relations are more ergonomic than the Entity-in-component approach to accomplish the same tasks, relations will be held to a higher bar for quality. 3. Heavy use of relations can seriously fragment archetypes. We need to be careful that this doesn't badly impact our performance. From e2ce855258c2d5910b91d52cb9b6e237dd0bd45e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 20:32:58 -0400 Subject: [PATCH 20/82] Add bikeshed tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index f89a5786..7dc7cc56 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -271,5 +271,5 @@ Relation applications in the engine: 2. Applications for UI: styling, widget wiring etc. 3. Frustum culling. -AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. +[bikeshed]: AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. EDITOR's NOTE: `Noitaler` may very much be used :3 From 014b33ff6352f02abf425ba9d70cb6b847b0a412 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 23 Apr 2021 20:33:15 -0400 Subject: [PATCH 21/82] Needs more bikeshed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 7dc7cc56..562b41cc 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -235,7 +235,7 @@ You read more about them in the [corresponding PR](https://github.com/SanderMert ## Unresolved questions 1. How do we put relations in Bundles? -2. What do we actually call `Noitaler`? Candidates so far are: +2. What do we actually call [`Noitaler`][[bikeshed]? Candidates so far are: 1. `ReverseRelation` 2. `Inverse` 3. `CoRelation` From 4ef058bef5d917cf1b2d2483ba8ac266d15b83ca Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 20:36:03 -0400 Subject: [PATCH 22/82] Removed redundant all_children example --- rfcs/min-relations.md | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 562b41cc..46480622 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -164,28 +164,7 @@ fn new_friends(query: Query<&mut Excitement, Added>>){ TODO: mention relation cleanup on despawn. -Sometimes, we may instead want to look for "entities that are targets of some relation". -We can do so by iterating over all entities with that relation, and then filtering by target. - -```rust -fn all_children( - player_query: Query>, - child_query: Query<&Name, With>>, -) { - let player_entity = player_query.single.unwrap(); - - child_query.set_relation_filters( - RelationFilters::new() - .add_target_filter::(player_entity) - ); - - for name in child_query.iter() { - println!("{} is one of my children.", name) - } -} -``` - -Finally, you can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. +You can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. ```rust // We need to use a marker component to avoid having multiple mutable references From ca9d3eb763e8afb0bf94810159d85085edb8ac56 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 20:36:26 -0400 Subject: [PATCH 23/82] Removed oudated comment --- rfcs/min-relations.md | 1 - 1 file changed, 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 46480622..8ca67c74 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -167,7 +167,6 @@ TODO: mention relation cleanup on despawn. You can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. ```rust -// We need to use a marker component to avoid having multiple mutable references fn debts_come_due( mut debt_query: Query<(Entity, Relation)>, mut money_query: Query<&mut Money>, From 555a21942590f3b660c7659d348521b6fdac2b60 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 20:37:34 -0400 Subject: [PATCH 24/82] Removed weak drawback --- rfcs/min-relations.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 8ca67c74..99528c49 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -194,8 +194,7 @@ TODO: Boxy explains the magic. 1. Introduces another core abstraction to users that they will have to learn. Rewriting our parent-child code in terms of relations is important, but will introduce the concept to users quite quickly. -2. Even if relations are more ergonomic than the Entity-in-component approach to accomplish the same tasks, relations will be held to a higher bar for quality. -3. Heavy use of relations can seriously fragment archetypes. We need to be careful that this doesn't badly impact our performance. +2. Heavy use of relations can seriously fragment archetypes. We need to be careful that this doesn't badly impact our performance. ## Rationale and alternatives From 2bd4bfee4eb4cbee7fbb6fb27a2dfaf1970fbf06 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 20:42:47 -0400 Subject: [PATCH 25/82] Clarified that not all problems will be solved by min_relations --- rfcs/min-relations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 99528c49..d5315391 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -31,6 +31,7 @@ But this quickly starts to balloon in complexity. We *could* force our users to solve these problems, over and over again in each individual game. This powerful and common pattern deserves a proper abstraction that we can make ergonomic, performant and *fearless*. +Eventually, at least ;) ## Guide-level explanation From c88a59299285922df433c61372b88fa08f09c060 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 21:08:23 -0400 Subject: [PATCH 26/82] Noted that Rationale is incomplete --- rfcs/min-relations.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index d5315391..488c12b4 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -199,10 +199,7 @@ Rewriting our parent-child code in terms of relations is important, but will int ## Rationale and alternatives -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? -- Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? +TODO: Write. ## Prior art From 86bfb3eb21a015deeba8e761d7e027b261db77d3 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 21:38:14 -0400 Subject: [PATCH 27/82] Fixed hard tabs :( --- rfcs/min-relations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 488c12b4..202ec94b 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -157,8 +157,8 @@ fn not_alone( // So does change detection with Added and Changed! fn new_friends(query: Query<&mut Excitement, Added>>){ query.for_each(|(mut excitement, new_friends)| { - excitement.value += 10 * new_friends.count(); - }); + excitement.value += 10 * new_friends.count(); + }); } ``` From 8727707705dd64a130285894b65ba72966f53ecf Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 22:15:54 -0400 Subject: [PATCH 28/82] Improved target filter API --- rfcs/min-relations.md | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 202ec94b..f0b6363a 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -3,7 +3,7 @@ // FIXME // Make sure to talk about these: // - Reverse relations. -// - Or support for add_target_filter. +// - Or support for target filters. ## Summary @@ -106,11 +106,11 @@ fn friend_of_dorothy( mut query: Query>>, dorothy: Res, ) { - // Setting relation filters requires that the query is mutable - query.set_relation_filters( - RelationFilters::new() - .add_target_filter::(dorothy.entity) - ); + // Setting relation filters requires mutable access to the query + query.filter_relation::( + RelationFilter::target(dorothy.entity) + // .build() causes the relation filters to be applied + ).build(); for source_entity in query.iter(){ println!("{} is friends with Dorothy!", source_entity); @@ -125,24 +125,19 @@ fn friend_of_dorothy( fn caught_in_the_middle(mut query: Query<&mut Stress, With>>, dorothy: Res, elphaba: Res){ - + + // Note that we can set relation filters even if the relation is in a query filter + query.filter_relation::( + RelationFilter::any_of() + .target(dorothy.entity) + .target(elphaba.entity) // You can directly chain this as set_relation_filters returns &mut Query - query.set_relation_filters( - // Note that we can set relation filters even if the relation is in a query filter - RelationFilters::new() - // Note: add_target_filter is additive- i.e. entities this query - // matches must be friends with *both* dorothy AND elphaba - .add_target_filter::(dorothy.entity) - .add_target_filter::(elphaba.entity) - ).for_each(|mut stress| { + ).build().for_each(|mut stress| { println!("{} is friends with Dorothy and Elphaba!", source_entity); stress.val = 1000; }) } -/// If we want to combine targets in an "or" fashion, we need a slightly different syntax -TODO: write example - // Query filters work too! fn not_alone( commands: mut Commands, @@ -183,6 +178,8 @@ fn debts_come_due( } ``` +### Advanced relation filters + ### Grouping entities ### Entity hierarchies From b504089cfbbff9028f064a0b471241450725b489 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 22:34:03 -0400 Subject: [PATCH 29/82] Automatic cleanup --- rfcs/min-relations.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index f0b6363a..83674871 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -1,10 +1,5 @@ # Feature Name: `min-relations` -// FIXME -// Make sure to talk about these: -// - Reverse relations. -// - Or support for target filters. - ## Summary Relations allow users to create logical connection between entities. Each source entity can have multiple kinds of relations, each with their own data, and multiple relations of the same kind pointing to a different target entity. @@ -158,7 +153,8 @@ fn new_friends(query: Query<&mut Excitement, Added>>){ ``` -TODO: mention relation cleanup on despawn. +Relations between entities are automatically cleaned up when either the source or target entity is removed. +Just like components, you can view which relations were removed in the last frame through the `RemovedRelation` system parameter, which also returns their data. You can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. From 91f52b515cbc272291a899002014d0b675584fa3 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 22:34:23 -0400 Subject: [PATCH 30/82] Clarity improvements to Future Work --- rfcs/min-relations.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 83674871..0af719c0 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -220,24 +220,27 @@ macro_rules Relation { ## Future possibilities -Relation enhancements: - -1. Sugar for accessing data on the target entity in a single query. `Relation` -2. Graph traversals. -3. Arbitrary target types instead of just `Entity` combined with `KindedEntity` proposal to ensure your targets are valid. -4. Automatically symmetric relations. -5. `Noitaler`* -6. Graph shape guarantees using archetype invariants. +Relation enhancements beyond the scope of `min-relations`: + +1. Sugar for accessing data on the target entity in a single query. +Proposed API: `Relation` +2. Graph shape guarantees (e.g tree, acyclic, 1-depth, non-self-referential). +Likely implemented using archetype invariants. +3. Graph traversals API: breadth-first, depth-first, root of tree etc. +4. Arbitrary target types instead of just `Entity` combined with `KindedEntity` proposal to ensure your targets are valid. +5. Automatically symmetric (or anti-symmetric) relations. +6. `Noitaler`*, for relations that point in the opposite direction. 7. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s 8. Assorted Performance optimizations. -9. Relation orders to enable things like `Styles`. -10. Generalized `despawn_recursive`. +9. Relative ordering between relations of the same kind on the same entity. +This would enable the `Styles` proposal from #1 to use relations. +10. Generalized `despawn_recursive` by parameterizing on relation type. Relation applications in the engine: -1. Replace existing parent-child. +1. Replace existing parent-child API. 2. Applications for UI: styling, widget wiring etc. -3. Frustum culling. +3. Frustum culling with multiple cameras. [bikeshed]: AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. EDITOR's NOTE: `Noitaler` may very much be used :3 From 1a121bbc84b6d362e7577c732f6bd9eab0c022ef Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 23:21:03 -0400 Subject: [PATCH 31/82] Added change_source and change_target API --- rfcs/min-relations.md | 47 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 0af719c0..ce05afb2 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -153,9 +153,6 @@ fn new_friends(query: Query<&mut Excitement, Added>>){ ``` -Relations between entities are automatically cleaned up when either the source or target entity is removed. -Just like components, you can view which relations were removed in the last frame through the `RemovedRelation` system parameter, which also returns their data. - You can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. ```rust @@ -174,10 +171,54 @@ fn debts_come_due( } ``` +### Nuances of relations + +Relations between entities are automatically cleaned up when either the source or target entity is removed. +Just like components, you can view which relations were removed in the last frame through the `RemovedRelation` system parameter, which also returns their data. + +Each relation kind + target combination is treated like a unique type for the purposes of archetypes. +This makes can be relevant to the performance of your game; accelerating target entity filtering but creating overhead if you have a very large number of distinct targets. +As a result, changing the source or target of a relation can only be done while the app has exclusive access to the world, via `Commands` or in exclusive systems. +Here's an example of how you might do so: + +```rust +fn love_potion( + commands: mut Commands, + query: Query>, With)>, + player_query: Query>){ + + let player = player.single().unwrap(); + + for victim in query.iter(){ + // change_target() and change_source() preserve the relation's data + // Only one relation of a given kind can exist between a source and a target; + // these command applies to any relations that match, + // and overwrite any conflicting relations + commands.entity(victim).change_target::(player); // This is unethical!! + } +} + +fn adoption( + commands: mut Commands, + // As with components, you can query for relations that may or may not exist + query: Query<(Entity, &NewOwner, Option>), With>, +) { + for (kitten, new_owner, previous_ownership) in query.iter(){ + // Changing the target or the source will fail silently if no appropriate relation exists + match previous_ownership { + // The first parameter is the data, the second is the old target + Some(_,_) => commands.entity(kitten).change_source::(new_owner); // uwu :3 + None => commands.entity(kitten).insert_relation(Owns::default(), new_owner); // uwu!! + } + } +} +``` + ### Advanced relation filters ### Grouping entities + ### Entity hierarchies ## Reference-level explanation From 82548cb1f4cf06a5f5dc848dd928dc0b173699e4 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 23:24:27 -0400 Subject: [PATCH 32/82] Entity group use case explanation --- rfcs/min-relations.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index ce05afb2..0a1ccabd 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -218,6 +218,25 @@ fn adoption( ### Grouping entities +By targeting a common entity, relations work fantastically as an ergonomic way to group entities. +This pattern acts as a nice complement to marker components when either: + +1. You want to define the *kind* of group, rather than just membership. +2. The number of different groups you need is unknowable at compile time. + +Here are some concrete examples where this pattern works well: + +- you're storing multiple parts (such as meshes or sprites with their own `Transform`s) of the same in-game-object across multiple entities +- you want to dynamically group units together, like in a real-time-strategy game +- the effects created by a single entity, like the bullets they're spewing out +- you want a way to mark the controller of a unit in a queryable way, but the number of possible controllers is unknown at compile time +- you have multiple cameras, and want to mark which frustum each entity is in so they can be culled independently + +Let's examine the real-time strategy group example hands-on: + +```rust + +``` ### Entity hierarchies From 40c8ebe0cb24387bc721a59d0ceb8ae9233fa155 Mon Sep 17 00:00:00 2001 From: Alice Date: Fri, 23 Apr 2021 23:53:41 -0400 Subject: [PATCH 33/82] Relations for entity graphs explanation --- rfcs/min-relations.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 0a1ccabd..c561f613 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -238,7 +238,28 @@ Let's examine the real-time strategy group example hands-on: ``` -### Entity hierarchies +### Entity graphs + +Unsurprisingly, you can build all manner of complex entity graphs using relations. +Before you begin to rip out your resources with graph data structures storing `Entity` keys, +pause to consider that this is, in fact, `*min*-relations`. +Performance will be limited, APIs will be lacking, +and you will bemoan your inability to specify information about the global properties of the graph. + +However, even relations do have three critical advantages over the **external graph data structure pattern**: + +1. You can readily store data in each edge of your directed graph. +2. You can quickly filter for which entity-node edges are coming from or pointing to. +3. You don't have to think about synchronization. + +These advantages make even `min-relations` a compelling alternative to both the Entity-in-component and external graph data structure patterns if: + +1. You don't care about performance. +2. You don't need to perform complex traversals. +3. You're operating on general graphs that can contain cycles and self-referential edges. +4. You're too lazy to thoroughly ensure your code is free of synchronization bugs. + +Let's take a look at how you could use relations to build an API for a tree-shaped graph that looks suspiciously like Bevy 0.5's parent-child hierarchy. ## Reference-level explanation From e5481f01baea25333752946ad5e36141abf7bc61 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 00:00:12 -0400 Subject: [PATCH 34/82] Clarity --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index c561f613..77196f64 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -249,7 +249,7 @@ and you will bemoan your inability to specify information about the global prope However, even relations do have three critical advantages over the **external graph data structure pattern**: 1. You can readily store data in each edge of your directed graph. -2. You can quickly filter for which entity-node edges are coming from or pointing to. +2. You can quickly filter for the source or target entities (nodes) of each relation (edge). 3. You don't have to think about synchronization. These advantages make even `min-relations` a compelling alternative to both the Entity-in-component and external graph data structure patterns if: From 2daaaa2596bda9eeeca7436dada5a93fce64baec Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 00:39:08 -0400 Subject: [PATCH 35/82] Advanced relation filters stub --- rfcs/min-relations.md | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 77196f64..4dc24b96 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -216,6 +216,66 @@ fn adoption( ### Advanced relation filters +As the savvy reader may have guessed, we can combine relation filters in arbitrarily complex ways. +While most of you may never need to plumb the dizzying heights of compound relation filtering, +the DBAs in the audience will surely both thank us for our efforts and deride our naive implementations. + +Fortunately, there are several simple and convenient API extensions that you might want to use. +The first of these is the ability to filter by the source entity as well: + +```rust +fn + +``` + +Next, you may wish to operate over entire groups of entities at once: + +```rust +fn + +``` + +From time-to-time, we may care about *excluding* entities who have relations to certain targets: + +```rust +fn blackball(){ + +} + +``` + +`any_of` and `all_of` can be used to collect groups of entities into a single filter. +`any_of` uses **or semantics**, returning any entity if any of the filters are met, +while `all_of` uses **and semantics**, rejecting entities who do not meet every specified filter. + +```rust +fn + +``` + +We can filter on multiple types of relations at once by chaining together our `.filter_relation` methods before we call `.build()`. + +```rust +fn + +``` + +Chaining filters in this way uses "and" semantics, just like `all_of`. + +While relations filters are reset each time you leave a system, you may want to reset them sooner, +commonly when performing multiple subqueries in bevy. To do so, simply call `query.reset_filter::()` or `query.reset_filters()`. + +```rust + +``` + +Finally, for when you have *truly* complex relation filtering needs, you can turn to **compound relation filters**. + +```rust +fn + +``` + ### Grouping entities By targeting a common entity, relations work fantastically as an ergonomic way to group entities. @@ -291,6 +351,7 @@ You read more about them in the [corresponding PR](https://github.com/SanderMert 4. `TargetOf` 3. Do we need a full graph constraint solver in our queries to handle things like "check for unrequited love"? 4. Do we want a macro to make complex calls easier? +5. Do we want to be able to filter queries by their source as well? ```rust macro_rules Relation { From fd261eb28f0e6a803b3e2124541b23e072f25708 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 00:49:41 -0400 Subject: [PATCH 36/82] Implementation sketch for compound relation filters --- rfcs/min-relations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 4dc24b96..38b1528c 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -270,11 +270,11 @@ commonly when performing multiple subqueries in bevy. To do so, simply call `que ``` Finally, for when you have *truly* complex relation filtering needs, you can turn to **compound relation filters**. +By combining `RelationFilter::not` with the power of `RelationFilter::inner_join`, `RelationFilter::left_join`, `RelationFilter::right_join`, `RelationFilter::full_join`, you can extract the information you desire from even the most convoluted of architectures! +As you might expect, these are `(RelationFilter, RelationFilter) -> RelationFilter` functions, +and act like the [standard database joins](http://www.sql-join.com/sql-join-types) of the same names. -```rust -fn - -``` +No example is given: if you need these, you will know what to do. ### Grouping entities From 2b280b1e4fbc4d141d9baa3386b203924f569a93 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 02:27:58 -0400 Subject: [PATCH 37/82] First draft of rationale and alternatives --- rfcs/min-relations.md | 52 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 38b1528c..46c1c10e 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -333,7 +333,57 @@ Rewriting our parent-child code in terms of relations is important, but will int ## Rationale and alternatives -TODO: Write. +### Why do relations fragment archetypes? + +TODO: Boxy explains her decisions. + +### Why are relations stored in the ECS? + +TODO: Boxy help! + +### Why aren't relations described as a special type of components? + +They don't behave in exactly the same ways, +and describing them as such is confusing to beginners. + +Under the hood, the existing implementation models + +TODO: add examples of the differences + +### Why don't we just store Entities in components? + +See [Motivation]. + +### Why don't we just store graph data structures in a Resource? + +See [Motivation] *and* [Entity Graphs]. + +### Why do we need arbitrarily complex relation filters?? + +Relation filters take advantage of archetypes to be much faster and more ergonomic than doing the same operation by naive iteration. +In order to ensure that it can always be used effectively, we need a genuinely complete DSL, +in addition to the convenient methods discussed first. + +The API for compound relation filters is simple to implement and easily ignored. + +### Why is my favorite feature relegated to Future Work? + +It's already many thousands of words long, and the associated PR has thousands of lines of code. +Done is better than perfect, and we had to draw the line somewhere. +As with all things, we can gradually improve this over time. + +The set of features expressed here is useful enough to be a clear improvement over the existing patterns in at least some use cases. +There's a lot of value in getting this feature in front of users to see the response at scale. + +### Why is the implementation for this so *slow*? + +Many uses of relations are not performance-critical, +and the value of a nice API and automatic handling of data synchronization far outweighs any performance cost for those users. + +We can fix the perf once we have a better understanding of the APIs we need to support. +There's also nothing stopping you from doing things the old ways in your elaborate graph-traversing systems. + +See also: the question directly above. ## Prior art From b3848684b3b95f6f26312bdd75b54d27c96b12a0 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 11:42:11 -0400 Subject: [PATCH 38/82] Improved examples for changing target and source --- rfcs/min-relations.md | 60 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 46c1c10e..4c91f1c6 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -182,36 +182,75 @@ As a result, changing the source or target of a relation can only be done while Here's an example of how you might do so: ```rust + fn love_potion( - commands: mut Commands, - query: Query>, With)>, + mut commands: Commands, + query: Query, With>, player_query: Query>){ let player = player.single().unwrap(); - for victim in query.iter(){ - // change_target() and change_source() preserve the relation's data + for victim, former_love in query.iter(){ + // Only one relation of a given kind can exist between a source and a target; - // these command applies to any relations that match, - // and overwrite any conflicting relations - commands.entity(victim).change_target::(player); // This is unethical!! + // like always, new relations overwrite existing relations + // with the same source, kind and target + for (_, former_lover) in former_love { + commands.entity(victim).change_target::(former_lover, player); // This is unethical!! + } } } fn adoption( - commands: mut Commands, + mut commands: Commands, // As with components, you can query for relations that may or may not exist query: Query<(Entity, &NewOwner, Option>), With>, ) { for (kitten, new_owner, previous_ownership) in query.iter(){ // Changing the target or the source will fail silently if no appropriate relation exists match previous_ownership { - // The first parameter is the data, the second is the old target - Some(_,_) => commands.entity(kitten).change_source::(new_owner); // uwu :3 + // We can change sources by controlling which entity owns the relation + // move_relation is directly analagous to move_component + Some(_, old_owner) => commands.entity(kitten).move_relation::(old_owner, new_owner); // uwu :3 None => commands.entity(kitten).insert_relation(Owns::default(), new_owner); // uwu!! } } } + +// `change_target` and `move_relation` preserve the relation's data +// This system removes all springs attached to mass 1, and adds them to mass 2 instead +fn reattach_springs(mut commands: Commands, + selected_mass1: Query>, + selected_mass2: Query>, + query: Query<(Entity, Relation)> + ) { + + let s1 = selelected_mass1.single().unwrap(); + let s2 = selelected_mass2.single().unwrap(); + + // Relation filtering for the target is faster because it works on archetypes, + // but you can still filter by hand + for (source_mass, spring) in query.iter_mut(){ + + // The _ is the spring's data + for (target_mass, _) in spring { + // This makes it safe to use an else if below + assert!(source_mass != target_mass, + "Springs should not connect a mass to itself!"); + + // Spring relations are symettric; + // we have two identical relations in opposite direction that we must preserve + if source_mass == s1 { + // Changing the source + commands.entity(target_mass).move_relation::(s1, s2); + } else if target_mass == s1 { + // Changing the target + commands.entity(source_mass).change_target::(s1, s2); + } + } + } +} + ``` ### Advanced relation filters @@ -330,6 +369,7 @@ TODO: Boxy explains the magic. 1. Introduces another core abstraction to users that they will have to learn. Rewriting our parent-child code in terms of relations is important, but will introduce the concept to users quite quickly. 2. Heavy use of relations can seriously fragment archetypes. We need to be careful that this doesn't badly impact our performance. +3. `move_component`, `move_relation` and `change_target` only work on `Clone` components / relations due to the need to temporarily be able to access removed component data. ## Rationale and alternatives From 0792124b3256afc20618bfa09a13a43062f6b634 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 11:46:35 -0400 Subject: [PATCH 39/82] Typo fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 4c91f1c6..8df568f3 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -157,7 +157,7 @@ You can use the `Entity` returned by your relations to fetch data from the targe ```rust fn debts_come_due( - mut debt_query: Query<(Entity, Relation)>, + mut debt_query: Query<(Entity, &Relation)>, mut money_query: Query<&mut Money>, ) { for (debtor, debt) in debt.query() { From 00be4826794db39f1c5e4ca93484bb8d88558991 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 11:47:35 -0400 Subject: [PATCH 40/82] Typo fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 8df568f3..f06b003a 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -204,7 +204,7 @@ fn love_potion( fn adoption( mut commands: Commands, // As with components, you can query for relations that may or may not exist - query: Query<(Entity, &NewOwner, Option>), With>, + query: Query<(Entity, &NewOwner, Option<&Relation>), With>, ) { for (kitten, new_owner, previous_ownership) in query.iter(){ // Changing the target or the source will fail silently if no appropriate relation exists From 362ad6977aebcad39e3826bb6be78c68003a182d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 11:49:40 -0400 Subject: [PATCH 41/82] Fixed link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index f06b003a..2671de88 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -434,7 +434,7 @@ You read more about them in the [corresponding PR](https://github.com/SanderMert ## Unresolved questions 1. How do we put relations in Bundles? -2. What do we actually call [`Noitaler`][[bikeshed]? Candidates so far are: +2. What do we actually call [`Noitaler`](#bikeshed)? Candidates so far are: 1. `ReverseRelation` 2. `Inverse` 3. `CoRelation` From 7d63fe83709c4962d9438162f72821789336aeb9 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 11:51:31 -0400 Subject: [PATCH 42/82] Fixed link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 2671de88..eaeddc4b 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -474,5 +474,6 @@ Relation applications in the engine: 2. Applications for UI: styling, widget wiring etc. 3. Frustum culling with multiple cameras. -[bikeshed]: AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. +AUTHOR'S NOTE: Much like `yeet`, `Noitaler` is the bikeshed-avoidance name for the inverse of `Relation`, and will never be used. + EDITOR's NOTE: `Noitaler` may very much be used :3 From afe1555c6710850e6ef1fe632e273f3ab5e469da Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 11:53:11 -0400 Subject: [PATCH 43/82] Humans own kittens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index eaeddc4b..c210857a 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -212,7 +212,7 @@ fn adoption( // We can change sources by controlling which entity owns the relation // move_relation is directly analagous to move_component Some(_, old_owner) => commands.entity(kitten).move_relation::(old_owner, new_owner); // uwu :3 - None => commands.entity(kitten).insert_relation(Owns::default(), new_owner); // uwu!! + None => commands.entity(new_owner).insert_relation(Owns::default(), kitten); // uwu!! } } } From cb95234a93edc271a973784051242913f5cdeb22 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 11:54:12 -0400 Subject: [PATCH 44/82] Fixed backwards relation filter --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index c210857a..3b120ead 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -123,7 +123,7 @@ fn caught_in_the_middle(mut query: Query<&mut Stress, With // Note that we can set relation filters even if the relation is in a query filter query.filter_relation::( - RelationFilter::any_of() + RelationFilter::all_of() .target(dorothy.entity) .target(elphaba.entity) // You can directly chain this as set_relation_filters returns &mut Query From 9ff30f9ed37343a166fc572f2db9ba3a693f54ac Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 11:55:34 -0400 Subject: [PATCH 45/82] Fixed spring query --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 3b120ead..0f161bb8 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -221,7 +221,7 @@ fn adoption( // This system removes all springs attached to mass 1, and adds them to mass 2 instead fn reattach_springs(mut commands: Commands, selected_mass1: Query>, - selected_mass2: Query>, + selected_mass2: Query>, query: Query<(Entity, Relation)> ) { From 46a9d0ee8ebf28da8fa5293944ea0b57abd8d73b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 11:58:48 -0400 Subject: [PATCH 46/82] Reduced character of writing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paweł Grabarz --- rfcs/min-relations.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 0f161bb8..f54c20f6 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -255,13 +255,11 @@ fn reattach_springs(mut commands: Commands, ### Advanced relation filters -As the savvy reader may have guessed, we can combine relation filters in arbitrarily complex ways. -While most of you may never need to plumb the dizzying heights of compound relation filtering, -the DBAs in the audience will surely both thank us for our efforts and deride our naive implementations. +For those cases where previous examples weren't enough, there are several additional API extensions that you might want to use. Using those, Relation filters can be combined in arbitrarily complex ways. -Fortunately, there are several simple and convenient API extensions that you might want to use. The first of these is the ability to filter by the source entity as well: + ```rust fn From bf6bd3eaa53c4735d5809ae09352188397921cdb Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 12:29:56 -0400 Subject: [PATCH 47/82] Phrasing --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index f54c20f6..b977f8d7 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -354,7 +354,7 @@ These advantages make even `min-relations` a compelling alternative to both the 1. You don't care about performance. 2. You don't need to perform complex traversals. 3. You're operating on general graphs that can contain cycles and self-referential edges. -4. You're too lazy to thoroughly ensure your code is free of synchronization bugs. +4. You don't want to worry about synchronization bugs. Let's take a look at how you could use relations to build an API for a tree-shaped graph that looks suspiciously like Bevy 0.5's parent-child hierarchy. From 6bc157f3b7b2e1c9cfb5dd8df8bd572da7afa325 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 12:43:09 -0400 Subject: [PATCH 48/82] Clarified spring example --- rfcs/min-relations.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index b977f8d7..47f8ebf1 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -211,7 +211,7 @@ fn adoption( match previous_ownership { // We can change sources by controlling which entity owns the relation // move_relation is directly analagous to move_component - Some(_, old_owner) => commands.entity(kitten).move_relation::(old_owner, new_owner); // uwu :3 + Some(_, old_owner) => commands.entity(old_owner).move_relation::(new_owner, kitten); // uwu :3 None => commands.entity(new_owner).insert_relation(Owns::default(), kitten); // uwu!! } } @@ -220,32 +220,28 @@ fn adoption( // `change_target` and `move_relation` preserve the relation's data // This system removes all springs attached to mass 1, and adds them to mass 2 instead fn reattach_springs(mut commands: Commands, - selected_mass1: Query>, - selected_mass2: Query>, + selected_mass1: Res, + selected_mass2: Res, query: Query<(Entity, Relation)> ) { - let s1 = selelected_mass1.single().unwrap(); - let s2 = selelected_mass2.single().unwrap(); - - // Relation filtering for the target is faster because it works on archetypes, - // but you can still filter by hand - for (source_mass, spring) in query.iter_mut(){ - - // The _ is the spring's data + let m1 = selelected_mass1.entity; + let m2 = selelected_mass2.entity; + + for (source_mass, spring) in query.iter_mut(){ for (target_mass, _) in spring { - // This makes it safe to use an else if below assert!(source_mass != target_mass, "Springs should not connect a mass to itself!"); - // Spring relations are symettric; - // we have two identical relations in opposite direction that we must preserve - if source_mass == s1 { - // Changing the source - commands.entity(target_mass).move_relation::(s1, s2); - } else if target_mass == s1 { - // Changing the target - commands.entity(source_mass).change_target::(s1, s2); + // Spring relations are symmetric; each spring is defined by + // two identical relations that point opposite direction. + // We need to preserve both during this operation + if m1 == source_mass { + // If mass 1 is the source, we must change the source to mass 2 + commands.entity(m1).move_relation::(m2, target_mass); + } else if target_mass == m1 { + // If mass 1 is the target, we must change the target to mass 2 + commands.entity(source_mass).change_target::(m1, m2); } } } From c164e154acb292ecbfbeeec0f0ee7e759bfc1612 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 13:31:29 -0400 Subject: [PATCH 49/82] Simplified compound relation filters --- rfcs/min-relations.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 47f8ebf1..db4b42b2 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -303,11 +303,13 @@ commonly when performing multiple subqueries in bevy. To do so, simply call `que ``` Finally, for when you have *truly* complex relation filtering needs, you can turn to **compound relation filters**. -By combining `RelationFilter::not` with the power of `RelationFilter::inner_join`, `RelationFilter::left_join`, `RelationFilter::right_join`, `RelationFilter::full_join`, you can extract the information you desire from even the most convoluted of architectures! -As you might expect, these are `(RelationFilter, RelationFilter) -> RelationFilter` functions, -and act like the [standard database joins](http://www.sql-join.com/sql-join-types) of the same names. +`RelationFilter::any_of` and `RelationFilter::all_of` can combine *other* relation filters, +allowing you to nest your logic arbitrarily deep. +Let's look at a relatively simple example of that. -No example is given: if you need these, you will know what to do. +```rust + +``` ### Grouping entities From c0833fa84dc5c3293555c7b6144f333528c8b2a6 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 13:41:55 -0400 Subject: [PATCH 50/82] Added reference to EnTT prior art Credit to @DavidVonDerau for sharing this link with us --- rfcs/min-relations.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index db4b42b2..72272219 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -427,6 +427,9 @@ See also: the question directly above. These are somewhat different, they use an elaborate query domain-specific language along with being more feature rich. You read more about them in the [corresponding PR](https://github.com/SanderMertens/flecs/pull/358). +You can, of course, build similar data structures using the ECS itself. +Here's a look at the complexities involved in doing so in [EnTT](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/). + ## Unresolved questions 1. How do we put relations in Bundles? From ed1694bc0af60f72f8944dd20ad770673a521906 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 13:42:33 -0400 Subject: [PATCH 51/82] Noted that perf work is better done once we can compare perf --- rfcs/min-relations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 72272219..a1c1609c 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -416,7 +416,8 @@ There's a lot of value in getting this feature in front of users to see the resp Many uses of relations are not performance-critical, and the value of a nice API and automatic handling of data synchronization far outweighs any performance cost for those users. -We can fix the perf once we have a better understanding of the APIs we need to support. +We can fix the perf once we have a better understanding of the APIs we need to support, +and a tangible implementation to benchmark against. There's also nothing stopping you from doing things the old ways in your elaborate graph-traversing systems. See also: the question directly above. From bcc3a4dd92cd5fc603fcf007e8abaabe40b11a70 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 13:48:55 -0400 Subject: [PATCH 52/82] Improved clarity of advanced relation filtering --- rfcs/min-relations.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index a1c1609c..c205012c 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -253,8 +253,9 @@ fn reattach_springs(mut commands: Commands, For those cases where previous examples weren't enough, there are several additional API extensions that you might want to use. Using those, Relation filters can be combined in arbitrarily complex ways. -The first of these is the ability to filter by the source entity as well: - +The first of these is the ability to filter by the source entity, rather than target entity. +Note that you can get the same sort of effect by using `Query::get`; +this functionality just makes it more ergonomic to specify complex relation filters. ```rust fn @@ -268,7 +269,8 @@ fn ``` -From time-to-time, we may care about *excluding* entities who have relations to certain targets: +From time-to-time, we may care about *excluding* entities who have relations to certain targets. +To do so, we set our relation filter on a `Without>` query parameter. ```rust fn blackball(){ @@ -277,6 +279,8 @@ fn blackball(){ ``` +We can even combine positive and negative filters by including multiple copies + `any_of` and `all_of` can be used to collect groups of entities into a single filter. `any_of` uses **or semantics**, returning any entity if any of the filters are met, while `all_of` uses **and semantics**, rejecting entities who do not meet every specified filter. @@ -293,10 +297,12 @@ fn ``` -Chaining filters in this way uses "and" semantics, just like `all_of`. +Filters chained in this way will operate on the restricted list produces by the previous filter (following "and semantics"). -While relations filters are reset each time you leave a system, you may want to reset them sooner, -commonly when performing multiple subqueries in bevy. To do so, simply call `query.reset_filter::()` or `query.reset_filters()`. +Each query stores the entities that it is filtering as data for the duration of the system. +If you wish to filter for different entities within the same system, +simply call `query.reset_filter::()` or `query.reset_filters()`. +Here's an example showing off the value of this functionality: ```rust From 523cd591dd717a946b36573f40bc14808ea5e355 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 13:52:28 -0400 Subject: [PATCH 53/82] Noted connection to undirected graphs --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index c205012c..07a77b55 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -466,7 +466,7 @@ Proposed API: `Relation` Likely implemented using archetype invariants. 3. Graph traversals API: breadth-first, depth-first, root of tree etc. 4. Arbitrary target types instead of just `Entity` combined with `KindedEntity` proposal to ensure your targets are valid. -5. Automatically symmetric (or anti-symmetric) relations. +5. Automatically symmetric (or anti-symmetric) relations to model undirected edges. 6. `Noitaler`*, for relations that point in the opposite direction. 7. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s 8. Assorted Performance optimizations. From 07e47dbcf78b13488b88f725e3ed7b33f5e7606c Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 14:04:44 -0400 Subject: [PATCH 54/82] More detail in future work --- rfcs/min-relations.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 07a77b55..bdf2bda7 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -445,9 +445,8 @@ Here's a look at the complexities involved in doing so in [EnTT](https://skypjac 2. `Inverse` 3. `CoRelation` 4. `TargetOf` -3. Do we need a full graph constraint solver in our queries to handle things like "check for unrequited love"? -4. Do we want a macro to make complex calls easier? -5. Do we want to be able to filter queries by their source as well? +3. Do we want a macro to make complex calls easier? +4. Do we want to be able to filter queries by their source as well? ```rust macro_rules Relation { @@ -473,6 +472,7 @@ Likely implemented using archetype invariants. 9. Relative ordering between relations of the same kind on the same entity. This would enable the `Styles` proposal from #1 to use relations. 10. Generalized `despawn_recursive` by parameterizing on relation type. +11. \[Undecided\] A full graph constraint solver in our queries like in FLECS for advanced querying. Relation applications in the engine: From 17d74d6d3e898605ce9c0a25118ba0ac92bebf04 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 24 Apr 2021 14:31:20 -0400 Subject: [PATCH 55/82] Add TODO Co-authored-by: bjorn3 --- rfcs/min-relations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index bdf2bda7..bc812b56 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -258,6 +258,7 @@ Note that you can get the same sort of effect by using `Query::get`; this functionality just makes it more ergonomic to specify complex relation filters. ```rust +// TODO complete this fn ``` From 1099c39f4557700ebc6ac2a736fc6467e45fbd68 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 14:46:27 -0400 Subject: [PATCH 56/82] Builder pattern returns a value --- rfcs/min-relations.md | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index bdf2bda7..88b68826 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -98,22 +98,17 @@ fn interest(query: Query<&mut Relation>){ // You can query for entities that target a specific entity fn friend_of_dorothy( - mut query: Query>>, + query: Query>>, dorothy: Res, ) { - // Setting relation filters requires mutable access to the query - query.filter_relation::( + let filtered = query.filter_relation::( RelationFilter::target(dorothy.entity) // .build() causes the relation filters to be applied ).build(); - for source_entity in query.iter(){ + for source_entity in filtered.iter(){ println!("{} is friends with Dorothy!", source_entity); } - - // RelationFilters are reset at the end of the system, make sure to set them every time your system runs! - // You can also reset this manually by applying a new `RelationFilters` to the query - // They override existing relation filters, rather than adding to them } // Or even look for some combination of targets! @@ -299,15 +294,6 @@ fn Filters chained in this way will operate on the restricted list produces by the previous filter (following "and semantics"). -Each query stores the entities that it is filtering as data for the duration of the system. -If you wish to filter for different entities within the same system, -simply call `query.reset_filter::()` or `query.reset_filters()`. -Here's an example showing off the value of this functionality: - -```rust - -``` - Finally, for when you have *truly* complex relation filtering needs, you can turn to **compound relation filters**. `RelationFilter::any_of` and `RelationFilter::all_of` can combine *other* relation filters, allowing you to nest your logic arbitrarily deep. @@ -392,6 +378,10 @@ Under the hood, the existing implementation models TODO: add examples of the differences +## Why do we need the builder pattern for filtering relations? + +TODO: Boxy explains perf implications + ### Why don't we just store Entities in components? See [Motivation]. From 09df9a372d85e1f10665151d24d74e5796e9ff9e Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 14:46:50 -0400 Subject: [PATCH 57/82] More future work --- rfcs/min-relations.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 29eb94b0..35a95dc7 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -459,11 +459,14 @@ Likely implemented using archetype invariants. 5. Automatically symmetric (or anti-symmetric) relations to model undirected edges. 6. `Noitaler`*, for relations that point in the opposite direction. 7. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s -8. Assorted Performance optimizations. +8. Assorted performance optimizations. For example: + 1. Reducing the cost of having many archetypes. + 2. Relation storage type options to cause archetype fragmentation based on whether *any* relations of that type are present. + 3. Index-backed relations look-up. 9. Relative ordering between relations of the same kind on the same entity. This would enable the `Styles` proposal from #1 to use relations. 10. Generalized `despawn_recursive` by parameterizing on relation type. -11. \[Undecided\] A full graph constraint solver in our queries like in FLECS for advanced querying. +11. \[Controversial\] A full graph constraint solver DSL ala [Flecs](https://github.com/SanderMertens/flecs) for advanced querying. Relation applications in the engine: From aee4daedbef8835376df121c0c5793f3a3a4077d Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 14:46:57 -0400 Subject: [PATCH 58/82] Misc cleanup --- rfcs/min-relations.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 35a95dc7..e54e98ec 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -121,7 +121,7 @@ fn caught_in_the_middle(mut query: Query<&mut Stress, With RelationFilter::all_of() .target(dorothy.entity) .target(elphaba.entity) - // You can directly chain this as set_relation_filters returns &mut Query + // You can directly chain this as filter_relations returns &mut Query ).build().for_each(|mut stress| { println!("{} is friends with Dorothy and Elphaba!", source_entity); stress.val = 1000; @@ -186,7 +186,6 @@ fn love_potion( let player = player.single().unwrap(); for victim, former_love in query.iter(){ - // Only one relation of a given kind can exist between a source and a target; // like always, new relations overwrite existing relations // with the same source, kind and target From af543da0b5c5af79a45de1d860f9d8b466828165 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 15:03:22 -0400 Subject: [PATCH 59/82] Consistency with RemoveComponents --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index e54e98ec..957c71cb 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -169,7 +169,7 @@ fn debts_come_due( ### Nuances of relations Relations between entities are automatically cleaned up when either the source or target entity is removed. -Just like components, you can view which relations were removed in the last frame through the `RemovedRelation` system parameter, which also returns their data. +Just like components, you can view which relations were removed in the last frame through the `RemovedRelations` system parameter, which also returns their data. Each relation kind + target combination is treated like a unique type for the purposes of archetypes. This makes can be relevant to the performance of your game; accelerating target entity filtering but creating overhead if you have a very large number of distinct targets. From d1f90c362bc6c0f99b7023bfe43b898040f75afc Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 15:08:02 -0400 Subject: [PATCH 60/82] Add TODO --- rfcs/min-relations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 957c71cb..7bf2aff7 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -245,6 +245,8 @@ fn reattach_springs(mut commands: Commands, ### Advanced relation filters +TODO: Complete examples in this section + For those cases where previous examples weren't enough, there are several additional API extensions that you might want to use. Using those, Relation filters can be combined in arbitrarily complex ways. The first of these is the ability to filter by the source entity, rather than target entity. @@ -252,7 +254,6 @@ Note that you can get the same sort of effect by using `Query::get`; this functionality just makes it more ergonomic to specify complex relation filters. ```rust -// TODO complete this fn ``` From 8e858c0500c09e358a27571bfeb3f79b190b47d8 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 16:50:22 -0400 Subject: [PATCH 61/82] Dramatically optimized spring example Credit to Boxy and Frizi for pointing this out --- rfcs/min-relations.md | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 7bf2aff7..cafd9111 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -216,28 +216,22 @@ fn adoption( fn reattach_springs(mut commands: Commands, selected_mass1: Res, selected_mass2: Res, - query: Query<(Entity, Relation)> + query: Query> ) { let m1 = selelected_mass1.entity; let m2 = selelected_mass2.entity; + let springs = query.get(m1); - for (source_mass, spring) in query.iter_mut(){ - for (target_mass, _) in spring { - assert!(source_mass != target_mass, - "Springs should not connect a mass to itself!"); - - // Spring relations are symmetric; each spring is defined by - // two identical relations that point opposite direction. - // We need to preserve both during this operation - if m1 == source_mass { - // If mass 1 is the source, we must change the source to mass 2 - commands.entity(m1).move_relation::(m2, target_mass); - } else if target_mass == m1 { - // If mass 1 is the target, we must change the target to mass 2 - commands.entity(source_mass).change_target::(m1, m2); - } - } + // Spring relations are symmetric; each spring is defined by + // two identical relations that point opposite direction. + // We need to preserve both during this operation + for (target_mass, _) in springs { + // When mass 1 is the source, we must change the source to mass 2 + commands.entity(m1).move_relation::(m2, target_mass); + + // When mass 1 is the target, we must change the target to mass 2 + commands.entity(target_mass).change_target::(m1, m2); } } From 42793612b441da4476613f6845d87bf43d488015 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 16:52:21 -0400 Subject: [PATCH 62/82] Fixed formatting --- rfcs/min-relations.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index cafd9111..bb801e98 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -217,8 +217,7 @@ fn reattach_springs(mut commands: Commands, selected_mass1: Res, selected_mass2: Res, query: Query> - ) { - +) { let m1 = selelected_mass1.entity; let m2 = selelected_mass2.entity; let springs = query.get(m1); From 674bd5b8da66669409cde93080d0823f8126a234 Mon Sep 17 00:00:00 2001 From: Alice Date: Sat, 24 Apr 2021 17:34:46 -0400 Subject: [PATCH 63/82] Noted bikeshed for command arg order --- rfcs/min-relations.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index bb801e98..ed373a1e 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -430,7 +430,9 @@ Here's a look at the complexities involved in doing so in [EnTT](https://skypjac 3. `CoRelation` 4. `TargetOf` 3. Do we want a macro to make complex calls easier? -4. Do we want to be able to filter queries by their source as well? +4. Bikeshed: what argument order do we want for `change_target` and `move_relation`? Currently: + 1. `commands.entity(source).change_target(old_target, new_target)` + 2. `commands.entity(old_source).change_target(new_source, target)` ```rust macro_rules Relation { From dd827904a4887b8f83dfa996f9493e7a00f1eb9c Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 01:21:41 -0400 Subject: [PATCH 64/82] Added get_relation example --- rfcs/min-relations.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index ed373a1e..323c88ec 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -96,6 +96,16 @@ fn interest(query: Query<&mut Relation>){ } } +// You can look for relations with a specific kind, source and target +fn unrequited_love(query: Query<(Entity, &Relation)>){ + for (lover, crushes) in query.iter(){ + for (crush, _) in crushes { + let reciprocal_crush = query.get_relation::(crush, lover); + reciprocal_crush.expect("Surely they must feel the same way!"); + } + } +} + // You can query for entities that target a specific entity fn friend_of_dorothy( query: Query>>, From 7c2cbcfb38bd982c162d13d3c135de7dba5cf93e Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 01:22:06 -0400 Subject: [PATCH 65/82] Example bug fix --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 323c88ec..b2e573bc 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -215,7 +215,7 @@ fn adoption( match previous_ownership { // We can change sources by controlling which entity owns the relation // move_relation is directly analagous to move_component - Some(_, old_owner) => commands.entity(old_owner).move_relation::(new_owner, kitten); // uwu :3 + Some(old_owner, _) => commands.entity(old_owner).move_relation::(new_owner, kitten); // uwu :3 None => commands.entity(new_owner).insert_relation(Owns::default(), kitten); // uwu!! } } From c9f0e38749714070bb38a63ae576fbdee897a980 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 01:22:32 -0400 Subject: [PATCH 66/82] Ideas for reference-level explanation --- rfcs/min-relations.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index b2e573bc..3d9e0065 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -356,6 +356,10 @@ Let's take a look at how you could use relations to build an API for a tree-shap TODO: Boxy explains the magic. +### Data Storage Model + +### The Second Relation Filter Type Parameter + ## Drawbacks 1. Introduces another core abstraction to users that they will have to learn. From 23f758d29f02776008cdb329f34f4a7983aba20e Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 05:14:49 -0400 Subject: [PATCH 67/82] Filtering over groups of entities --- rfcs/min-relations.md | 117 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 3d9e0065..dad37cc0 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -248,24 +248,123 @@ fn reattach_springs(mut commands: Commands, ### Advanced relation filters -TODO: Complete examples in this section +The examples shown above show of the basics of relation filtering, but that's not all they can do. +The most important extension is the ability to filter for multiple entities at once from a `Vec`: -For those cases where previous examples weren't enough, there are several additional API extensions that you might want to use. Using those, Relation filters can be combined in arbitrarily complex ways. +```rust +// In this example, possible movess are stored on each piece +// as a relation to a specific board position +fn next_move(mut commands: Commands, + board_state: Res, + query: Query<(Entity, &Relation), With>){ + + let valid_positions: Vec = board_state.compute_valid_positions(); -The first of these is the ability to filter by the source entity, rather than target entity. -Note that you can get the same sort of effect by using `Query::get`; -this functionality just makes it more ergonomic to specify complex relation filters. + let filtered = query.filter_relation::( + RelationFilter::any_of().targets(valid_positions) + ).build(); -```rust -fn + for (piece, valid_moves) in filtered.iter(){ + for (potential_position, current_move) in valid_moves { + // Our heuristic is always >= 0 + let mut current_best = -1.0; + // This is just a dummy to be replaced + let best_move = Entity::new(); + let current = current_move.compute_heuristic() + + if current > current_best{ + current_best = current; + best_move = current_move; + } + } + commands.insert_relation::(piece, best_move); + } +} +// We can use this with either all_of or any_of to get the effect we need +fn all_roads_lead_to_rome(commands: mut Commands, + cities_query: Query>, + roads_query: Query<(Entity, (With, With>)>){ + let all_cities: Vec = cities_query.iter().collect(); + roads_query.filter_relation::( + RelationFilter::all_of().targets(all_cities) + ) + .build() + // The cities still left are connected to all other cities by roads + // We're tagging them with a Hub marker component so we can find them again quickly + .map(|city| commands.entity(city).insert(Hub)); +} ``` -Next, you may wish to operate over entire groups of entities at once: +As your filters grow in complexity, it can be useful to filter by the source entity, rather than target entity. +Note that you can get the same sort of effect by using `Query::get`; +this functionality just makes it more ergonomic to specify certain complex relation filters. ```rust -fn +fn paths_to_choose(location: Res, + mut query: Query<&mut Relation>){ + // We only care about paths that lead away from our current position + query + .filter_relation::( + RelationFilter::source(location.entity) + ) + .build() + .map(|_, mut path|{ + path.highlight(); + }); + + // You can accomplish the same thing by subsetting your query + // using query.get, query.single or branching on Entity + // This is equivalent because queries are composed of a collection of source entities + let mut paths_from_player_location = query.get_mut(location.entity).unwrap() + for (path, _) in paths_from_player_location { + println!("It will take {} minutes to get to {} from here.", + path.travel_time, + path.destination_name + ); + } +} +// With more complex logic that blends sources and targets, +// the value of this approach is more evident +fn sever_groups(commands: mut Commands, + selection_query: Query, + mass_query: Query, With>)> +){ + // Our goal is to remove all of the springs + // connecting these two groups of point masses + let mut group_1 = Vec::new(); + let mut group_2 = Vec::new(); + + for (entity, selection) in selection_query.iter(){ + match selection { + Selection::Group1 => group_1.push(entity), + Selection::Group2 => group_2.push(entity), + } + } + + let 1_to_2 = mass_query.filter_relation::( + RelationFilter::any_of().sources(group_1).targets(group_2) + ).build(); + + let 2_to_1 = mass_query.filter_relation::( + RelationFilter::any_of().sources(group_2).targets(group_1) + ).build(); + + // First we remove the springs that point in one direction + for (source, springs) in 1_to_2.iter(){ + for (target, _) in springs{ + commands.remove_relation::(source, target) + } + } + + // And then, because the relation is symmetric, the other + for (source, springs) in 2_to_1.iter(){ + for (target, _) in springs{ + commands.remove_relation::(source, target) + } + } +} ``` From time-to-time, we may care about *excluding* entities who have relations to certain targets. From 2bd87d07ae7498e2d2b992e8fcefe912e839bdc1 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 05:37:25 -0400 Subject: [PATCH 68/82] Negative filtering example --- rfcs/min-relations.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index dad37cc0..32994318 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -354,14 +354,14 @@ fn sever_groups(commands: mut Commands, // First we remove the springs that point in one direction for (source, springs) in 1_to_2.iter(){ for (target, _) in springs{ - commands.remove_relation::(source, target) + commands.entity(source).remove_relation::(target) } } // And then, because the relation is symmetric, the other for (source, springs) in 2_to_1.iter(){ for (target, _) in springs{ - commands.remove_relation::(source, target) + commands.entity(source).remove_relation::(target) } } } @@ -371,10 +371,29 @@ From time-to-time, we may care about *excluding* entities who have relations to To do so, we set our relation filter on a `Without>` query parameter. ```rust -fn blackball(){ +// Fraternizing with communists is a strict disqualifcation! +fn purity_testing( + commands: mut Commands, + candidate_query: Query, Without>)>, + communist_query: Query<(Entity, With>, +) { + let communists = communist_query.iter().collect(); + + // Normally, Without relation filters disqualify all entities with that relation. + // But by narrowing them to only affect certain sources or targets, + // we can control which entities are affected! + candidate_query.filter_relation::( + // Even a single communist friend is disqualifying! + RelationFilter::any_of().targets(communists) + ) + .build() + // Candidates who are not friends with any communists have been filtered + // Leaving only suspect candidates to be stripped of their candidacy + .map(|candidate| commands.entity(candidate).remove::(); + // Note that candidates with no friends at all are always excluded from the query, + // sparing them from our arbitrary interrogation } - ``` We can even combine positive and negative filters by including multiple copies From 249142314374f444a0e4928e792e8b98148ad2f4 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 05:50:08 -0400 Subject: [PATCH 69/82] Restructured and shortened guide level explanation a bit --- rfcs/min-relations.md | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 32994318..58173121 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -249,7 +249,11 @@ fn reattach_springs(mut commands: Commands, ### Advanced relation filters The examples shown above show of the basics of relation filtering, but that's not all they can do. -The most important extension is the ability to filter for multiple entities at once from a `Vec`: +The most important extension is the ability to filter for multiple entities at once from a `Vec`. +In concert with this, `any_of` and `all_of` can be used to collect groups of entities into a single filter. +`any_of` uses **or semantics**, returning any entity if any of the filters are met, +while `all_of` uses **and semantics**, rejecting entities who do not meet every specified filter. + ```rust // In this example, possible movess are stored on each piece @@ -396,17 +400,6 @@ fn purity_testing( } ``` -We can even combine positive and negative filters by including multiple copies - -`any_of` and `all_of` can be used to collect groups of entities into a single filter. -`any_of` uses **or semantics**, returning any entity if any of the filters are met, -while `all_of` uses **and semantics**, rejecting entities who do not meet every specified filter. - -```rust -fn - -``` - We can filter on multiple types of relations at once by chaining together our `.filter_relation` methods before we call `.build()`. ```rust @@ -415,13 +408,11 @@ fn ``` Filters chained in this way will operate on the restricted list produces by the previous filter (following "and semantics"). - -Finally, for when you have *truly* complex relation filtering needs, you can turn to **compound relation filters**. -`RelationFilter::any_of` and `RelationFilter::all_of` can combine *other* relation filters, -allowing you to nest your logic arbitrarily deep. -Let's look at a relatively simple example of that. +We can even combine positive and negative filters by including multiple copies of the same relation in our query, and then specifying which relation we're filtering using the second type parameter. +This advanced technique is helpful when you really need to be specific, as shown in these examples: ```rust +fn ``` @@ -441,12 +432,6 @@ Here are some concrete examples where this pattern works well: - you want a way to mark the controller of a unit in a queryable way, but the number of possible controllers is unknown at compile time - you have multiple cameras, and want to mark which frustum each entity is in so they can be culled independently -Let's examine the real-time strategy group example hands-on: - -```rust - -``` - ### Entity graphs Unsurprisingly, you can build all manner of complex entity graphs using relations. @@ -593,7 +578,8 @@ Likely implemented using archetype invariants. 9. Relative ordering between relations of the same kind on the same entity. This would enable the `Styles` proposal from #1 to use relations. 10. Generalized `despawn_recursive` by parameterizing on relation type. -11. \[Controversial\] A full graph constraint solver DSL ala [Flecs](https://github.com/SanderMertens/flecs) for advanced querying. +11. Compound relation filters, letting you nest logic arbitrarily deep. These are just sugar / perf for very advanced users, and can wait for a while. +12. \[Controversial\] A full graph constraint solver DSL ala [Flecs](https://github.com/SanderMertens/flecs) for advanced querying. Relation applications in the engine: From bec0d2ce337d9bc38d06157a55dac72c853d29c4 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 05:59:36 -0400 Subject: [PATCH 70/82] Frenemies example --- rfcs/min-relations.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 58173121..3ac88141 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -403,8 +403,18 @@ fn purity_testing( We can filter on multiple types of relations at once by chaining together our `.filter_relation` methods before we call `.build()`. ```rust -fn - +fn frenemies( + query: Query, With>)>, + player: Res +){ + query + .filter_relation::( + RelationFilter::target(player.entity) + ) + .filter_relation::( + RelationFilter::target(player.entity) + ).map(|entity| println!("{} is frenemies with the player!", entity)); +} ``` Filters chained in this way will operate on the restricted list produces by the previous filter (following "and semantics"). @@ -412,8 +422,9 @@ We can even combine positive and negative filters by including multiple copies o This advanced technique is helpful when you really need to be specific, as shown in these examples: ```rust -fn +fn herbivores(){ +} ``` ### Grouping entities From 9f5f62cfdba7d0fa3acf23e995eb3e2a148216c8 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 06:13:35 -0400 Subject: [PATCH 71/82] Herbivores example --- rfcs/min-relations.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 3ac88141..f1402ffc 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -422,11 +422,27 @@ We can even combine positive and negative filters by including multiple copies o This advanced technique is helpful when you really need to be specific, as shown in these examples: ```rust -fn herbivores(){ - +fn herbivores( + commands: mut Commands, + plants_query: Query>, + animals_query: Query>, + species_query: Query, Without>)> +){ + let plants = plants_query.iter().collect(); + let animals = animals_query.iter().collect(); + + species_query + // The second type parameter controls which relation filter of that type is being modified + .filter_relation::>(RelationFilter::any_of().targets(plants)) + // Because this is a without filter, this means that we exclude all entities + // with even one Consumes relation that targets an animal + .filter_relation::>(RelationFilter::any_of().targets(animals)) + .map(|entity| commands.entity(entity).insert(Herbivore)); } ``` +Check out **The second relation filter type parameter** section below for more details on how and why this works. + ### Grouping entities By targeting a common entity, relations work fantastically as an ergonomic way to group entities. @@ -470,9 +486,9 @@ Let's take a look at how you could use relations to build an API for a tree-shap TODO: Boxy explains the magic. -### Data Storage Model +### Data storage model -### The Second Relation Filter Type Parameter +### The second relation filter type parameter ## Drawbacks From a8c2f52a841e50dc60c339e9be6a3d9800896c5f Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sun, 25 Apr 2021 13:29:14 +0300 Subject: [PATCH 72/82] rustfmt + minor fix `map` ing an iterator does nothing unless its consumed, so I switched a `map` to a `for_each` --- rfcs/min-relations.md | 275 ++++++++++++++++++++++-------------------- 1 file changed, 142 insertions(+), 133 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index f1402ffc..fb08afbc 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -57,7 +57,7 @@ let boxy = world.spawn().id(); commands.entity(alice).insert_relation(FriendsWith, boxy); // uwu:3 // You can remove them with commands! -commands.entity(alice).remove_relation(FriendsWith, boxy) // not uwu :( +commands.entity(alice).remove_relation(FriendsWith, boxy); // not uwu :( // You can use direct World access! world.entity_mut(alice).insert_relation(FriendsWith, boxy); // uwu:3 @@ -69,7 +69,7 @@ commands.entity(boxy).insert_relation(FriendsWith, alice); // one way friendship commands.entity(alice).insert_relation(Owes(9999), boxy); // :))))) // You can add mulitple relations of the same kind to a single source entity! -let cart = world.spawn().id(); +let cart = world.spawn().id(); commands.entity(alice).insert_relation(FriendsWith, cart); // Hi! ``` @@ -80,25 +80,25 @@ Just like with ordinary components, you can request them in your queries: // Relations can be queried for immutably fn friendship_is_magic(mut query: Query<(&mut Magic, &Relation), With>) { // Each entity can have their own friends - for (mut magic, friends) in query.iter_mut(){ + for (mut magic, friends) in query.iter_mut() { // Relations return an iterator when unpacked magic.power += friends.count(); } } // Or you can request mutable access to modify the relation's data -fn interest(query: Query<&mut Relation>){ - for debts in query.iter(){ +fn interest(query: Query<&mut Relation>) { + for debts in query.iter() { for (owed_to, amount_owed) in debts { println!("we owe {} some money", owed_to); - amount_owed *= 1.05; + amount_owed *= 1.05; } } } // You can look for relations with a specific kind, source and target -fn unrequited_love(query: Query<(Entity, &Relation)>){ - for (lover, crushes) in query.iter(){ +fn unrequited_love(query: Query<(Entity, &Relation)>) { + for (lover, crushes) in query.iter() { for (crush, _) in crushes { let reciprocal_crush = query.get_relation::(crush, lover); reciprocal_crush.expect("Surely they must feel the same way!"); @@ -107,50 +107,51 @@ fn unrequited_love(query: Query<(Entity, &Relation)>){ } // You can query for entities that target a specific entity -fn friend_of_dorothy( - query: Query>>, - dorothy: Res, -) { - let filtered = query.filter_relation::( - RelationFilter::target(dorothy.entity) - // .build() causes the relation filters to be applied - ).build(); +fn friend_of_dorothy(query: Query>>, dorothy: Res) { + let filtered = query + .filter_relation::( + RelationFilter::target(dorothy.entity), // .build() causes the relation filters to be applied + ) + .build(); - for source_entity in filtered.iter(){ + for source_entity in filtered.iter() { println!("{} is friends with Dorothy!", source_entity); } } // Or even look for some combination of targets! -fn caught_in_the_middle(mut query: Query<&mut Stress, With>>, +fn caught_in_the_middle( + mut query: Query<&mut Stress, With>>, dorothy: Res, - elphaba: Res){ - + elphaba: Res, +) { // Note that we can set relation filters even if the relation is in a query filter - query.filter_relation::( - RelationFilter::all_of() - .target(dorothy.entity) - .target(elphaba.entity) - // You can directly chain this as filter_relations returns &mut Query - ).build().for_each(|mut stress| { - println!("{} is friends with Dorothy and Elphaba!", source_entity); - stress.val = 1000; - }) + query + .filter_relation::( + RelationFilter::all_of() + .target(dorothy.entity) + .target(elphaba.entity), // You can directly chain this as filter_relations returns &mut Query + ) + .build() + .for_each(|mut stress| { + println!("{} is friends with Dorothy and Elphaba!", source_entity); + stress.val = 1000; + }) } // Query filters work too! fn not_alone( - commands: mut Commands, + mut commands: Commands, query: Query>>, - puppy: Res + puppy: Res, ) { - for lonely_entity in query.iter(){ + for lonely_entity in query.iter() { commands.insert_relation(FriendsWith, puppy.entity); } } // So does change detection with Added and Changed! -fn new_friends(query: Query<&mut Excitement, Added>>){ +fn new_friends(query: Query<&mut Excitement, Added>>) { query.for_each(|(mut excitement, new_friends)| { excitement.value += 10 * new_friends.count(); }); @@ -162,18 +163,21 @@ You can use the `Entity` returned by your relations to fetch data from the targe ```rust fn debts_come_due( - mut debt_query: Query<(Entity, &Relation)>, + mut debt_query: Query<(Entity, &Relation)>, mut money_query: Query<&mut Money>, ) { for (debtor, debt) in debt.query() { for (lender, amount_owed) in debt { - *money_query.get(debtor).unwrap() + *money_query + .get(debtor) + .unwrap() .checked_sub(amount_owed) .expect("Nice kneecaps you got there..."); *money_query.get(lender).unwrap() += amount_owed; } } } + ``` ### Nuances of relations @@ -186,21 +190,21 @@ This makes can be relevant to the performance of your game; accelerating target As a result, changing the source or target of a relation can only be done while the app has exclusive access to the world, via `Commands` or in exclusive systems. Here's an example of how you might do so: -```rust - -fn love_potion( +```rustfn love_potion( mut commands: Commands, query: Query, With>, - player_query: Query>){ - + player_query: Query>, +) { let player = player.single().unwrap(); - for victim, former_love in query.iter(){ + for (victim, former_love) in query.iter() { // Only one relation of a given kind can exist between a source and a target; // like always, new relations overwrite existing relations // with the same source, kind and target for (_, former_lover) in former_love { - commands.entity(victim).change_target::(former_lover, player); // This is unethical!! + commands + .entity(victim) + .change_target::(former_lover, player); // This is unethical!! } } } @@ -210,23 +214,28 @@ fn adoption( // As with components, you can query for relations that may or may not exist query: Query<(Entity, &NewOwner, Option<&Relation>), With>, ) { - for (kitten, new_owner, previous_ownership) in query.iter(){ - // Changing the target or the source will fail silently if no appropriate relation exists - match previous_ownership { - // We can change sources by controlling which entity owns the relation - // move_relation is directly analagous to move_component - Some(old_owner, _) => commands.entity(old_owner).move_relation::(new_owner, kitten); // uwu :3 - None => commands.entity(new_owner).insert_relation(Owns::default(), kitten); // uwu!! - } - } + for (kitten, new_owner, previous_ownership) in query.iter() { + // Changing the target or the source will fail silently if no appropriate relation exists + match previous_ownership { + // We can change sources by controlling which entity owns the relation + // move_relation is directly analagous to move_component + Some(old_owner, _) => commands + .entity(old_owner) + .move_relation::(new_owner, kitten), // uwu :3 + None => commands + .entity(new_owner) + .insert_relation(Owns::default(), kitten), // uwu!! + } + } } // `change_target` and `move_relation` preserve the relation's data // This system removes all springs attached to mass 1, and adds them to mass 2 instead -fn reattach_springs(mut commands: Commands, - selected_mass1: Res, - selected_mass2: Res, - query: Query> +fn reattach_springs( + mut commands: Commands, + selected_mass1: Res, + selected_mass2: Res, + query: Query>, ) { let m1 = selelected_mass1.entity; let m2 = selelected_mass2.entity; @@ -256,27 +265,28 @@ while `all_of` uses **and semantics**, rejecting entities who do not meet every ```rust -// In this example, possible movess are stored on each piece +// In this example, possible moves are stored on each piece // as a relation to a specific board position -fn next_move(mut commands: Commands, - board_state: Res, - query: Query<(Entity, &Relation), With>){ - +fn next_move( + mut commands: Commands, + board_state: Res, + query: Query<(Entity, &Relation), With>, +) { let valid_positions: Vec = board_state.compute_valid_positions(); - let filtered = query.filter_relation::( - RelationFilter::any_of().targets(valid_positions) - ).build(); + let filtered = query + .filter_relation::(RelationFilter::any_of().targets(valid_positions)) + .build(); - for (piece, valid_moves) in filtered.iter(){ + for (piece, valid_moves) in filtered.iter() { for (potential_position, current_move) in valid_moves { // Our heuristic is always >= 0 let mut current_best = -1.0; // This is just a dummy to be replaced let best_move = Entity::new(); - let current = current_move.compute_heuristic() + let current = current_move.compute_heuristic(); - if current > current_best{ + if current > current_best { current_best = current; best_move = current_move; } @@ -286,18 +296,20 @@ fn next_move(mut commands: Commands, } // We can use this with either all_of or any_of to get the effect we need -fn all_roads_lead_to_rome(commands: mut Commands, - cities_query: Query>, - roads_query: Query<(Entity, (With, With>)>){ +fn all_roads_lead_to_rome( + mut commands: Commands, + cities_query: Query>, + roads_query: Query, With>)>, +) { let all_cities: Vec = cities_query.iter().collect(); - roads_query.filter_relation::( - RelationFilter::all_of().targets(all_cities) - ) - .build() - // The cities still left are connected to all other cities by roads - // We're tagging them with a Hub marker component so we can find them again quickly - .map(|city| commands.entity(city).insert(Hub)); + roads_query + .filter_relation::(RelationFilter::all_of().targets(all_cities)) + .build() + // The cities still left are connected to all other cities by roads + // We're tagging them with a Hub marker component so we can find them again quickly + .map(|city| commands.entity(city).insert(Hub)); } + ``` As your filters grow in complexity, it can be useful to filter by the source entity, rather than target entity. @@ -305,70 +317,69 @@ Note that you can get the same sort of effect by using `Query::get`; this functionality just makes it more ergonomic to specify certain complex relation filters. ```rust -fn paths_to_choose(location: Res, - mut query: Query<&mut Relation>){ +fn paths_to_choose(location: Res, mut query: Query<&mut Relation>) { // We only care about paths that lead away from our current position query - .filter_relation::( - RelationFilter::source(location.entity) - ) + .filter_relation::(RelationFilter::source(location.entity)) .build() - .map(|_, mut path|{ + .map(|_, mut path| { path.highlight(); }); // You can accomplish the same thing by subsetting your query // using query.get, query.single or branching on Entity // This is equivalent because queries are composed of a collection of source entities - let mut paths_from_player_location = query.get_mut(location.entity).unwrap() + let mut paths_from_player_location = query.get_mut(location.entity).unwrap(); for (path, _) in paths_from_player_location { - println!("It will take {} minutes to get to {} from here.", - path.travel_time, - path.destination_name + println!( + "It will take {} minutes to get to {} from here.", + path.travel_time, path.destination_name ); } } // With more complex logic that blends sources and targets, // the value of this approach is more evident -fn sever_groups(commands: mut Commands, - selection_query: Query, - mass_query: Query, With>)> -){ - // Our goal is to remove all of the springs +fn sever_groups( + mut commands: Commands, + selection_query: Query, + mass_query: Query, With>)>, +) { + // Our goal is to remove all of the springs // connecting these two groups of point masses - let mut group_1 = Vec::new(); - let mut group_2 = Vec::new(); + let mut group_1 = Vec::::new(); + let mut group_2 = Vec::::new(); - for (entity, selection) in selection_query.iter(){ + for (entity, selection) in selection_query.iter() { match selection { Selection::Group1 => group_1.push(entity), Selection::Group2 => group_2.push(entity), } } - let 1_to_2 = mass_query.filter_relation::( - RelationFilter::any_of().sources(group_1).targets(group_2) - ).build(); + let one_to_two = mass_query + .filter_relation::(RelationFilter::any_of().sources(group_1).targets(group_2)) + .build(); - let 2_to_1 = mass_query.filter_relation::( - RelationFilter::any_of().sources(group_2).targets(group_1) - ).build(); + let two_to_one = mass_query + .filter_relation::(RelationFilter::any_of().sources(group_2).targets(group_1)) + .build(); // First we remove the springs that point in one direction - for (source, springs) in 1_to_2.iter(){ - for (target, _) in springs{ + for (source, springs) in one_to_two.iter() { + for (target, _) in springs { commands.entity(source).remove_relation::(target) } } // And then, because the relation is symmetric, the other - for (source, springs) in 2_to_1.iter(){ - for (target, _) in springs{ + for (source, springs) in two_to_one.iter() { + for (target, _) in springs { commands.entity(source).remove_relation::(target) } } } + ``` From time-to-time, we may care about *excluding* entities who have relations to certain targets. @@ -377,43 +388,42 @@ To do so, we set our relation filter on a `Without>` query parameter ```rust // Fraternizing with communists is a strict disqualifcation! fn purity_testing( - commands: mut Commands, + mut commands: Commands, candidate_query: Query, Without>)>, - communist_query: Query<(Entity, With>, + communist_query: Query>, ) { let communists = communist_query.iter().collect(); // Normally, Without relation filters disqualify all entities with that relation. // But by narrowing them to only affect certain sources or targets, // we can control which entities are affected! - candidate_query.filter_relation::( - // Even a single communist friend is disqualifying! - RelationFilter::any_of().targets(communists) - ) - .build() - // Candidates who are not friends with any communists have been filtered - // Leaving only suspect candidates to be stripped of their candidacy - .map(|candidate| commands.entity(candidate).remove::(); + candidate_query + .filter_relation::( + // Even a single communist friend is disqualifying! + RelationFilter::any_of().targets(communists), + ) + .build() + // Candidates who are not friends with any communists have been filtered + // Leaving only suspect candidates to be stripped of their candidacy + .for_each(|candidate| commands.entity(candidate).remove::()); // Note that candidates with no friends at all are always excluded from the query, // sparing them from our arbitrary interrogation } + ``` We can filter on multiple types of relations at once by chaining together our `.filter_relation` methods before we call `.build()`. ```rust fn frenemies( - query: Query, With>)>, - player: Res -){ + query: Query>, With>)>, + player: Res, +) { query - .filter_relation::( - RelationFilter::target(player.entity) - ) - .filter_relation::( - RelationFilter::target(player.entity) - ).map(|entity| println!("{} is frenemies with the player!", entity)); + .filter_relation::(RelationFilter::target(player.entity)) + .filter_relation::(RelationFilter::target(player.entity)) + .for_each(|entity| println!("{} is frenemies with the player!", entity)); } ``` @@ -423,25 +433,24 @@ This advanced technique is helpful when you really need to be specific, as shown ```rust fn herbivores( - commands: mut Commands, + mut commands: Commands, plants_query: Query>, animals_query: Query>, - species_query: Query, Without>)> -){ + species_query: Query>, Without>)>, +) { let plants = plants_query.iter().collect(); let animals = animals_query.iter().collect(); species_query // The second type parameter controls which relation filter of that type is being modified - .filter_relation::>(RelationFilter::any_of().targets(plants)) + .filter_relation::>(RelationFilter::any_of().targets(plants)) // Because this is a without filter, this means that we exclude all entities // with even one Consumes relation that targets an animal - .filter_relation::>(RelationFilter::any_of().targets(animals)) - .map(|entity| commands.entity(entity).insert(Herbivore)); + .filter_relation::>(RelationFilter::any_of().targets(animals)) + .for_each(|entity| commands.entity(entity).insert(Herbivore)); } -``` -Check out **The second relation filter type parameter** section below for more details on how and why this works. +``` ### Grouping entities @@ -486,9 +495,9 @@ Let's take a look at how you could use relations to build an API for a tree-shap TODO: Boxy explains the magic. -### Data storage model +### Data Storage Model -### The second relation filter type parameter +### The Second Relation Filter Type Parameter ## Drawbacks From add64c0a1a9ddc29940fddd2e09d3ce8264006a3 Mon Sep 17 00:00:00 2001 From: TheRawMeatball Date: Sun, 25 Apr 2021 13:36:40 +0300 Subject: [PATCH 73/82] hotfix --- rfcs/min-relations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index fb08afbc..1634b2f4 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -190,7 +190,8 @@ This makes can be relevant to the performance of your game; accelerating target As a result, changing the source or target of a relation can only be done while the app has exclusive access to the world, via `Commands` or in exclusive systems. Here's an example of how you might do so: -```rustfn love_potion( +```rust +fn love_potion( mut commands: Commands, query: Query, With>, player_query: Query>, From 7b79ff25f3f550963287beb1332f7c3c7b3aee37 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sun, 25 Apr 2021 13:23:02 -0400 Subject: [PATCH 74/82] Typo fix Co-authored-by: bjorn3 --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 1634b2f4..927d751c 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -387,7 +387,7 @@ From time-to-time, we may care about *excluding* entities who have relations to To do so, we set our relation filter on a `Without>` query parameter. ```rust -// Fraternizing with communists is a strict disqualifcation! +// Fraternizing with communists is a strict disqualification! fn purity_testing( mut commands: Commands, candidate_query: Query, Without>)>, From a24d20be4cf6e495cf72da4120dba28a894aaf21 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 13:27:17 -0400 Subject: [PATCH 75/82] Use .max_by_key for example --- rfcs/min-relations.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 927d751c..81ff3c81 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -264,7 +264,6 @@ In concert with this, `any_of` and `all_of` can be used to collect groups of ent `any_of` uses **or semantics**, returning any entity if any of the filters are met, while `all_of` uses **and semantics**, rejecting entities who do not meet every specified filter. - ```rust // In this example, possible moves are stored on each piece // as a relation to a specific board position @@ -279,19 +278,10 @@ fn next_move( .filter_relation::(RelationFilter::any_of().targets(valid_positions)) .build(); - for (piece, valid_moves) in filtered.iter() { - for (potential_position, current_move) in valid_moves { - // Our heuristic is always >= 0 - let mut current_best = -1.0; - // This is just a dummy to be replaced - let best_move = Entity::new(); - let current = current_move.compute_heuristic(); - - if current > current_best { - current_best = current; - best_move = current_move; - } - } + let best_move = valid_moves + .max_by_key(|(potential_position, _)| potential_position.compute_heuristic()); + + if let Some((_, best_move)) = best_move { commands.insert_relation::(piece, best_move); } } From 90d9436b98f4d5be3fb3999a628e3df623ab3556 Mon Sep 17 00:00:00 2001 From: Alice Date: Sun, 25 Apr 2021 14:48:22 -0400 Subject: [PATCH 76/82] Added convenience function ideas to Future Work --- rfcs/min-relations.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 81ff3c81..544dc6c7 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -589,22 +589,25 @@ macro_rules Relation { Relation enhancements beyond the scope of `min-relations`: -1. Sugar for accessing data on the target entity in a single query. +1. Convenience functions of all sorts: + 1. `remove_relations::`, for removing a `Vec` of relations at once. + 2. `clear_relation::`, for removing all relations of that type at once. + 3. Generalized `despawn_recursive::`, parameterizing by relation kind. +2. Sugar for accessing data on the target entity in a single query. Proposed API: `Relation` -2. Graph shape guarantees (e.g tree, acyclic, 1-depth, non-self-referential). +3. Graph shape guarantees (e.g tree, acyclic, 1-depth, non-self-referential). Likely implemented using archetype invariants. -3. Graph traversals API: breadth-first, depth-first, root of tree etc. -4. Arbitrary target types instead of just `Entity` combined with `KindedEntity` proposal to ensure your targets are valid. -5. Automatically symmetric (or anti-symmetric) relations to model undirected edges. -6. `Noitaler`*, for relations that point in the opposite direction. -7. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s -8. Assorted performance optimizations. For example: +4. Graph traversals API: breadth-first, depth-first, root of tree etc. +5. Arbitrary target types instead of just `Entity` combined with `KindedEntity` proposal to ensure your targets are valid. +6. Automatically symmetric (or anti-symmetric) relations to model undirected edges. +7. `Noitaler`*, for relations that point in the opposite direction. +8. Streaming iters to allow for queries like: `Query<&mut Money, Relation>` and potentially use invariants on graph shape to allow for skipping soundness checks for the aliasing `&mut Money`'s +9. Assorted performance optimizations. For example: 1. Reducing the cost of having many archetypes. 2. Relation storage type options to cause archetype fragmentation based on whether *any* relations of that type are present. 3. Index-backed relations look-up. -9. Relative ordering between relations of the same kind on the same entity. +10. Relative ordering between relations of the same kind on the same entity. This would enable the `Styles` proposal from #1 to use relations. -10. Generalized `despawn_recursive` by parameterizing on relation type. 11. Compound relation filters, letting you nest logic arbitrarily deep. These are just sugar / perf for very advanced users, and can wait for a while. 12. \[Controversial\] A full graph constraint solver DSL ala [Flecs](https://github.com/SanderMertens/flecs) for advanced querying. From d84c1d0299eb4b6fbd0696fd3b211a464c0ceb7c Mon Sep 17 00:00:00 2001 From: Ellen Date: Tue, 27 Apr 2021 01:02:08 +0100 Subject: [PATCH 77/82] tweak example ordering and add some comments --- rfcs/min-relations.md | 72 ++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 544dc6c7..d9a2dbab 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -80,8 +80,8 @@ Just like with ordinary components, you can request them in your queries: // Relations can be queried for immutably fn friendship_is_magic(mut query: Query<(&mut Magic, &Relation), With>) { // Each entity can have their own friends - for (mut magic, friends) in query.iter_mut() { - // Relations return an iterator when unpacked + for (mut magic, friends: RelationIter) in query.iter_mut() { + // Relations return an iterator over the target and data of each relation on an entity magic.power += friends.count(); } } @@ -89,19 +89,40 @@ fn friendship_is_magic(mut query: Query<(&mut Magic, &Relation), Wi // Or you can request mutable access to modify the relation's data fn interest(query: Query<&mut Relation>) { for debts in query.iter() { - for (owed_to, amount_owed) in debts { + for (owed_to: Entity, amount_owed: &mut Owes) in debts { println!("we owe {} some money", owed_to); amount_owed *= 1.05; } } } +// Query filters work too! +fn not_alone( + mut commands: Commands, + query: Query>>, + puppy: Res, +) { + for lonely_entity in query.iter() { + commands.insert_relation(FriendsWith, puppy.entity); + } +} + +// So does change detection with Added and Changed! You can use `Relation` +// instead of a component type in all of the query filters! +fn new_friends(query: Query<&mut Excitement, Added>>) { + for (excitement, new_friends) in query { + excitement.value += 10 * new_friends.count(); + } +} + // You can look for relations with a specific kind, source and target fn unrequited_love(query: Query<(Entity, &Relation)>) { for (lover, crushes) in query.iter() { for (crush, _) in crushes { + // The `get_relation` method on Query is used just like `get_component` + // except you have to specify the target of the relation you want to get! let reciprocal_crush = query.get_relation::(crush, lover); - reciprocal_crush.expect("Surely they must feel the same way!"); + reciprocal_crush.expect("Surely they must feel the same way! u-uwu"); } } } @@ -109,10 +130,14 @@ fn unrequited_love(query: Query<(Entity, &Relation)>) { // You can query for entities that target a specific entity fn friend_of_dorothy(query: Query>>, dorothy: Res) { let filtered = query - .filter_relation::( - RelationFilter::target(dorothy.entity), // .build() causes the relation filters to be applied + // Relation filters make the `Relation` in our query type + // only be for the given targets specified- by default `Relation` + // will apply to any target + .filter_relation( + RelationFilter::::target(dorothy.entity) ) - .build(); + // You must call .apply_filters() before you can access the query again + .apply_filters(); for source_entity in filtered.iter() { println!("{} is friends with Dorothy!", source_entity); @@ -125,43 +150,27 @@ fn caught_in_the_middle( dorothy: Res, elphaba: Res, ) { - // Note that we can set relation filters even if the relation is in a query filter query - .filter_relation::( - RelationFilter::all_of() + // Note that we can set relation filters even if the relation is in a query filter + .filter_relation( + RelationFilter::::all_of() .target(dorothy.entity) - .target(elphaba.entity), // You can directly chain this as filter_relations returns &mut Query + .target(elphaba.entity), ) - .build() + .apply_filters() + // apply_filters() returns `&mut Query` so we can just iterate straight away .for_each(|mut stress| { println!("{} is friends with Dorothy and Elphaba!", source_entity); stress.val = 1000; }) } -// Query filters work too! -fn not_alone( - mut commands: Commands, - query: Query>>, - puppy: Res, -) { - for lonely_entity in query.iter() { - commands.insert_relation(FriendsWith, puppy.entity); - } -} - -// So does change detection with Added and Changed! -fn new_friends(query: Query<&mut Excitement, Added>>) { - query.for_each(|(mut excitement, new_friends)| { - excitement.value += 10 * new_friends.count(); - }); -} - ``` -You can use the `Entity` returned by your relations to fetch data from the target's components by combining it with `query::get()` or `query::get_component()`. ```rust +// You can use the `Entity` target returned by your relations to fetch data from the +// target's components by combining it with `query::get()` or `query::get_component()`. fn debts_come_due( mut debt_query: Query<(Entity, &Relation)>, mut money_query: Query<&mut Money>, @@ -173,6 +182,7 @@ fn debts_come_due( .unwrap() .checked_sub(amount_owed) .expect("Nice kneecaps you got there..."); + // getting the money on the target of our `Owes` relation! *money_query.get(lender).unwrap() += amount_owed; } } From 4081fe0e00c5a33ad7f7261967208b6082ea1430 Mon Sep 17 00:00:00 2001 From: Ellen Date: Tue, 27 Apr 2021 01:29:47 +0100 Subject: [PATCH 78/82] sowwy made changes --- rfcs/min-relations.md | 56 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index d9a2dbab..ebdbf272 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -209,7 +209,8 @@ fn love_potion( let player = player.single().unwrap(); for (victim, former_love) in query.iter() { - // Only one relation of a given kind can exist between a source and a target; + // Only one relation of a given kind can exist between a source and a target + // (although the opposite direction can exist on the target entity) // like always, new relations overwrite existing relations // with the same source, kind and target for (_, former_lover) in former_love { @@ -220,6 +221,10 @@ fn love_potion( } } +/* + +FIXME: This example is a bit borked but if we fix it the we no longer need `move_relation` + fn adoption( mut commands: Commands, // As with components, you can query for relations that may or may not exist @@ -239,6 +244,7 @@ fn adoption( } } } +*/ // `change_target` and `move_relation` preserve the relation's data // This system removes all springs attached to mass 1, and adds them to mass 2 instead @@ -285,14 +291,16 @@ fn next_move( let valid_positions: Vec = board_state.compute_valid_positions(); let filtered = query - .filter_relation::(RelationFilter::any_of().targets(valid_positions)) - .build(); + .filter_relation( + RelationFilter::::any_of().targets(valid_positions) + ) + .apply_filters(); let best_move = valid_moves - .max_by_key(|(potential_position, _)| potential_position.compute_heuristic()); + .max_by_key(|(potential_position, _)| potential_position.compute_heuristic()); if let Some((_, best_move)) = best_move { - commands.insert_relation::(piece, best_move); + commands.entity(piece).insert_relation::(best_move); } } @@ -304,8 +312,10 @@ fn all_roads_lead_to_rome( ) { let all_cities: Vec = cities_query.iter().collect(); roads_query - .filter_relation::(RelationFilter::all_of().targets(all_cities)) - .build() + .filter_relation( + RelationFilter::::all_of().targets(all_cities) + ) + .apply_filters() // The cities still left are connected to all other cities by roads // We're tagging them with a Hub marker component so we can find them again quickly .map(|city| commands.entity(city).insert(Hub)); @@ -321,8 +331,8 @@ this functionality just makes it more ergonomic to specify certain complex relat fn paths_to_choose(location: Res, mut query: Query<&mut Relation>) { // We only care about paths that lead away from our current position query - .filter_relation::(RelationFilter::source(location.entity)) - .build() + .filter_relation(RelationFilter::::source(location.entity)) + .apply_filters() .map(|_, mut path| { path.highlight(); }); @@ -359,12 +369,12 @@ fn sever_groups( } let one_to_two = mass_query - .filter_relation::(RelationFilter::any_of().sources(group_1).targets(group_2)) - .build(); + .filter_relation(RelationFilter::::any_of().sources(group_1).targets(group_2)) + .apply_filters(); let two_to_one = mass_query - .filter_relation::(RelationFilter::any_of().sources(group_2).targets(group_1)) - .build(); + .filter_relation(RelationFilter::::any_of().sources(group_2).targets(group_1)) + .apply_filters(); // First we remove the springs that point in one direction for (source, springs) in one_to_two.iter() { @@ -399,11 +409,11 @@ fn purity_testing( // But by narrowing them to only affect certain sources or targets, // we can control which entities are affected! candidate_query - .filter_relation::( + .filter_relation( // Even a single communist friend is disqualifying! - RelationFilter::any_of().targets(communists), + RelationFilter::::any_of().targets(communists), ) - .build() + .apply_filters() // Candidates who are not friends with any communists have been filtered // Leaving only suspect candidates to be stripped of their candidacy .for_each(|candidate| commands.entity(candidate).remove::()); @@ -414,16 +424,16 @@ fn purity_testing( ``` -We can filter on multiple types of relations at once by chaining together our `.filter_relation` methods before we call `.build()`. +We can filter on multiple types of relations at once by chaining together our `.filter_relation` methods before we call `.apply_filters()`. ```rust fn frenemies( - query: Query>, With>)>, + query: Query>, With>)>, player: Res, ) { query - .filter_relation::(RelationFilter::target(player.entity)) - .filter_relation::(RelationFilter::target(player.entity)) + .filter_relation(RelationFilter::::target(player.entity)) + .filter_relation(RelationFilter::::target(player.entity)) .for_each(|entity| println!("{} is frenemies with the player!", entity)); } ``` @@ -442,12 +452,14 @@ fn herbivores( let plants = plants_query.iter().collect(); let animals = animals_query.iter().collect(); + // The generics on `filter_relation` are normally inferred but we can manually + // specify them to disambiguate which `Relation` we are filtering in the query species_query // The second type parameter controls which relation filter of that type is being modified - .filter_relation::>(RelationFilter::any_of().targets(plants)) + .filter_relation::>(RelationFilter::any_of().targets(plants)) // Because this is a without filter, this means that we exclude all entities // with even one Consumes relation that targets an animal - .filter_relation::>(RelationFilter::any_of().targets(animals)) + .filter_relation::>(RelationFilter::any_of().targets(animals)) .for_each(|entity| commands.entity(entity).insert(Herbivore)); } From ab0c8bd7ea172ed78601722713a9cc1c50a5461c Mon Sep 17 00:00:00 2001 From: Ellen Date: Tue, 27 Apr 2021 01:32:05 +0100 Subject: [PATCH 79/82] Fix Rel -> Relation --- rfcs/min-relations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index ebdbf272..c2d0df55 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -308,7 +308,7 @@ fn next_move( fn all_roads_lead_to_rome( mut commands: Commands, cities_query: Query>, - roads_query: Query, With>)>, + roads_query: Query, With>)>, ) { let all_cities: Vec = cities_query.iter().collect(); roads_query @@ -354,7 +354,7 @@ fn paths_to_choose(location: Res, mut query: Query<&mut Relation fn sever_groups( mut commands: Commands, selection_query: Query, - mass_query: Query, With>)>, + mass_query: Query, With>)>, ) { // Our goal is to remove all of the springs // connecting these two groups of point masses From b1ce346b9eb90b706097ce2cbe73d4b68d8c58ee Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Sat, 1 May 2021 23:58:55 -0400 Subject: [PATCH 80/82] Fixed typo in example Co-authored-by: tigregalis <38416468+tigregalis@users.noreply.github.com> --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index c2d0df55..2294d9b7 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -175,7 +175,7 @@ fn debts_come_due( mut debt_query: Query<(Entity, &Relation)>, mut money_query: Query<&mut Money>, ) { - for (debtor, debt) in debt.query() { + for (debtor, debt) in debt_query.iter() { for (lender, amount_owed) in debt { *money_query .get(debtor) From 85519a33ffae271e11245bba2baf4d51ffb97bd9 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 5 May 2021 23:23:36 -0400 Subject: [PATCH 81/82] Add relation event channels idea --- rfcs/min-relations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 2294d9b7..8f6220e1 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -632,6 +632,7 @@ Likely implemented using archetype invariants. This would enable the `Styles` proposal from #1 to use relations. 11. Compound relation filters, letting you nest logic arbitrarily deep. These are just sugar / perf for very advanced users, and can wait for a while. 12. \[Controversial\] A full graph constraint solver DSL ala [Flecs](https://github.com/SanderMertens/flecs) for advanced querying. +13. Entity-to-entity event channels using relations (blocked on [bevy #2116](https://github.com/bevyengine/bevy/pull/2116) Relation applications in the engine: From 3e06286da547f7eabc2d011bfbe8fbebd51a7fb3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 11 May 2021 14:48:51 -0400 Subject: [PATCH 82/82] Add new FLECS docs to prior art --- rfcs/min-relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/min-relations.md b/rfcs/min-relations.md index 8f6220e1..ae0c3a14 100644 --- a/rfcs/min-relations.md +++ b/rfcs/min-relations.md @@ -582,7 +582,7 @@ See also: the question directly above. [`Flecs`](https://github.com/SanderMertens/flecs), an advanced C++ ECS framework, has a similar feature, which they call "relationships". These are somewhat different, they use an elaborate query domain-specific language along with being more feature rich. -You read more about them in the [corresponding PR](https://github.com/SanderMertens/flecs/pull/358). +You can read more about them in the [docs](https://github.com/SanderMertens/flecs/blob/master/docs/Queries.md) or [corresponding PR](https://github.com/SanderMertens/flecs/pull/358). You can, of course, build similar data structures using the ECS itself. Here's a look at the complexities involved in doing so in [EnTT](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/).