-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Non-blocking data structure creation and destruction: "instant commands" #30
Conversation
There's some serious "how on earth do we implement this" work to be done still: I'm very much open to comments, suggestions and critiques on that front. I want to examine the source more and do some light prototyping before attempting that however. The main implementation complexity is in handling table-storage components: we can't instantly add / remove them in ways that modify the archetypes. I think our two main options here are:
Obviously approach 1 would have dramatically better scaling with the number of component additions / removals, but I'm not sure if it's feasible. The set of possible archetypes is likely to be quite large, even with scoped access to component insertion. This is particularly true without archetype invariants to restrict the set further. |
Even if the existing commands had synchronous versions added, I'd prefer to keep the existing commands api around, as it's a neat way to defer complicated work, and enables apis such as bevyengine/bevy#2234. Furthermore, I think as an initial version we should not work on insertion to existing entities, but make it ergonomic to spawn new ones. This may or may not be a viable long-term strategy, but it'll greatly simplify initial implementation. |
Also btw, maybe add a rendered link? |
If I understand correctly the goal is to allow Forge tier changes to happen in the middle of a stage (instead of just at the end with commands). I think you don't explicitly state this but I assume the changes made through this api would be immediate. IMO there are also problems with that (maybe more complicated for the user to deal with than commands).
// this could be something like a projectile that duplicates
// when it hits an enemy
fn system(foundry: Foundry<&X>, query: Query<&X, With<Duplicate>>) {
// does the loop see the new entities
// (if yes this would be an infinite loop)
for x in query.iter() {
foundry.spawn().insert(x.clone());
}
} I see three solutions:
Just as @TheRawMeatball says. Even if you extract all the functionality from Commands into other system parameters. I think it is worth keeping commands. I know that you can basically replace them with events + exclusive systems but I think current Commands are a lot more convenient if you want to delay things to the end of the stage.
Also I don't 100% like the names but I also can't think of anything much better. I think I would prefer more "technical" terms. |
More discussion on this topic has occured on Discord. |
Correct :) The main motivation for this is to allow simpler, more local gameplay code.
I agree with this. My experience making games in Bevy is that this is already largely true; system ordering ambiguities are pretty reliably a source of bugs. However, I definitely think the existing tools are inadequate for this. I wouldn't want to see this merged until bevyengine/bevy#2381 or something comparable is merged.
Ah! That's a very interesting example. I think the right solution is to collect
Yeah, I wanted to propose the stronger version of this first, but I'd be happy to compromise on having two APIs depending on which characteristics you wanted. Not thrilled with the complexity increase, but the tradeoffs are real.
Not quite: right now, resources inserted via commands aren't added immediately :( This is a common source of frustration for beginners, and I think would be much cleaner to implement than the entity-component equivalents.
Yeah, I'm in agreement. I decide to pick something evocative and neutral over something misleading like "create". I think my preference would be something directly comparable to "mutable", but I've never seen this concept explicitly laid out before so I don't have existing naming conventions to draw on. |
I'm a bit worried about the scheduling of the Foundry API for removing or adding components from an existing entity. Spawning and despawning entities only need access to one archetype, but adding and removing components on an existing entitiy is a move operation and thus needs access to two archetypes. fn remove_honored(foundry: Foundry<&Honored>, player_entity: Res<PlayerEntity>){
// Removing components works the same way as inserting them;
// just use the .remove method
foundry.entity(player_entity.0).remove::<Honored>();
} The system graph is built from figuring out the potential component accesses from the function signature. The above function moves an entity from the set of archetypes with the As @TheRawMeatball suggested on discord, we could narrow our archetypes with filters. // You can access specific entities using Forge::entity()
fn remove_honored(foundry: Foundry<&Honored, With<Player>>, player_entity: Res<PlayerEntity>){
// Removing components works the same way as inserting them;
// just use the .remove method
foundry.entity(player_entity.0).remove::<Honored>();
} This would allow other systems to run in parallel. My worry is that beginning users would default to the first function as it is simpler. But In practice I'm not sure how problematic allowing this footgun to exist is. Immediate spawning, despawning, adds, and removes might be worth the trade off. Note that Queries with Forge's have the same problem. Though it may be more intuitive in this case as we're more used to dealing with queries. fn add_death(query: Query<Forge<Dead>>) {
for forge_dead in query.iter_mut() {
forge_dead.insert(Dead);
}
} On a related note I think you want to have a separate SystemParam for adding/removing components from spawning/despawning. As you can narrow the archetypes for spawning/despawning entities to just the archetypes with Component A. While adding and removing a component needs a bigger set of archetypes. |
I agree :)
Yeah; fundamentally this idea is predicated on a workaround for dense tables. I don't have the energy to devote to solving that problem at the moment, so I'm going to close this out. Thanks for the feedback; I am still optimistic that we can make this work, but the costs and complexity are high enough that it's not worth prioritizing. |
Sad to see this closed as it does seems like it is possible (just a lot of work). A couple of last thoughts:
.add_system(my_command_system.sync_on_run()) |
Just a matter of prioritization :) There's a lot of other work to improve Bevy that I want to prioritize first, and I suspect that some of it will help make this area clearer. If someone else wants to take up this mantle, feel free to use this RFC as a starting point!
Yes, this will also be required if we want any form of automatic batching.
I suspect we'll get something along these lines in various scheduler improvements that @Ratysz has planned. It matches the mental model well, but introduces complex nonlocal behavior as everything gets recalibrated by making changes. |
RENDERED
Motivation
Commands allow users to mutate the world in arbitrary ways, so long as they wait for the next hard-sync point to do so.
But this unscoped power has several serious implications for performance Commands have several serious issues:
Summary
By adding a fourth, maximally privileged tier of data access (addition and removal > mutation > read-only > detection), we can move virtually all common functionality out of
Commands
.This allows us to completely deprecate commands (and
FromWorld
) in favor of scoped data access plus exclusive systems which watch for specific events, simplifying the code base and API and significantly improving both performance and usability.