-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Unflushed commands before exclusive system when configured with *_ignore_deferred
#17828
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
Comments
haven't looked at the source of bevy to know but my hypothesis is that taking |
*_ignore_deferred
*_ignore_deferred
Commands should always be flushed before and after an exclusive system. I think that assert should always be failing 🤔 Sys3's role here is really weird! |
are these the cases you have? //! SILENCE
use bevy::prelude::*;
#[derive(Resource)]
struct InsertTest;
fn sys1(mut commands: Commands) {
commands.insert_resource(InsertTest);
}
fn sys1_exclusive(world: &mut World) {
world.insert_resource(InsertTest);
}
fn sys2(world: &mut World) {
// this fails, the resource is already visible to this system
assert!(!world.contains_resource::<InsertTest>());
}
fn sys2_non_exclusive(resource: Option<Res<InsertTest>>) {
// this fails, the resource is already visible to this system
assert!(resource.is_none());
}
fn sys3() {}
#[derive(SystemSet, Copy, Clone, Debug, Hash, PartialEq, Eq)]
struct MySet(u8);
fn base_app() -> App {
let mut app = App::new();
app.configure_sets(Update, (MySet(1), MySet(2)).chain());
app
}
#[test]
#[should_panic]
fn three_systems() {
base_app()
.add_systems(
Update,
(sys1, sys2).chain_ignore_deferred().in_set(MySet(1)),
)
.add_systems(Update, sys3.in_set(MySet(2)))
.update();
}
#[test]
#[should_panic]
fn three_systems_sys1_exclusive() {
base_app()
.add_systems(
Update,
(sys1_exclusive, sys2)
.chain_ignore_deferred()
.in_set(MySet(1)),
)
.add_systems(Update, sys3.in_set(MySet(2)))
.update();
}
#[test]
#[should_panic]
fn three_systems_sys2_non_exclusive() {
base_app()
.add_systems(
Update,
(sys1, sys2_non_exclusive)
.chain_ignore_deferred()
.in_set(MySet(1)),
)
.add_systems(Update, sys3.in_set(MySet(2)))
.update();
}
#[test]
#[should_panic]
fn three_systems_chained() {
base_app()
.add_systems(Update, (sys1, sys2, sys3).chain())
.update();
}
#[test]
fn three_systems_chained_no_deferred() {
base_app()
.add_systems(Update, (sys1, sys2, sys3).chain_ignore_deferred())
.update();
}
#[test]
fn two_systems() {
base_app()
.add_systems(Update, (sys1, sys2).chain_ignore_deferred())
.update();
} the ones with |
*_ignore_deferred
*_ignore_deferred
again, as i and alice said, taking |
*_ignore_deferred
*_ignore_deferred
Okay I spent some time doing extensive testing for the case of two systems and adding a third system. I updated the first post with all findings including a file that can be used to test fixes. |
I skimmed through the bevy source to get an idea where the intended logic is put at.
I looked at a field So I checked where Besides checking for conflicting access, it matters for when Currently I run out of ideas where to look for this "commands are always applied before exclusive systems". I mean this issue shows that it is not really working. I suspect my system |
I don't think they currently are, but I do think they should be. |
I found another bug with the way automatic sync points are calculated in regard of If there is a "pending" sync point at a node that itself has no deferred, even non- #[derive(Resource, Default)]
struct TestRes;
App::new()
.add_systems(
Update,
(
(
|mut commands: Commands| commands.init_resource::<TestRes>(),
|| () // has no deferred
).chain_ignore_deferred(), // last node still has no deferred
|world: &mut World| assert!(world.contains_resource::<TestRes>()) // fails, does not need to be exclusive
).chain() // note that this is not `chain_ignore_deferred`!
)
.update(); |
…em is exclusive (#17880) # Objective Fixes #17828 This fixes two bugs: 1. Exclusive systems should see the effect of all commands queued to that point. That does not happen when the system is configured with `*_ignore_deferred` which may lead to surprising situations. These configurations should not behave like that. 2. If `*_ignore_deferred` is used, no sync point may be added at all **after** the config. Currently this can happen if the last nodes in that config have no deferred parameters themselves. Instead, sync points should always be added after such a config, so long systems have deferred parameters. ## Solution 1. When adding sync points on edges, do not consider `AutoInsertApplyDeferredPass::no_sync_edges` if the target is an exclusive system. 2. when going through the nodes in a directed way, store the information that `AutoInsertApplyDeferredPass::no_sync_edges` suppressed adding a sync point at the target node. Then, when the target node is evaluated later by the iteration and that prior suppression was the case, the target node will behave like it has deferred parameters even if the system itself does not. ## Testing I added a test for each bug, please let me know if more are wanted and if yes, which cases you would want to see. These tests also can be read as examples how the current code would fail.
Bevy version
I saw this behavior at around 0.14 I think but only now got around making a minimal reproducible example on
main
, or at least a fairly recent commit behindmain
.What you did
I ran this code:
What went wrong
assert_system
is an exclusive system, therefore it is expected that the command ininsert_system
is flushed before it runs. The assertion should not fail but it does.Additional information
I did some extensive testing and noticed that this bug occurs no matter how I do this configuration as long I make use of the
_ignore_deferred
variants. The following table lists these configurations and gives each a number because that matters for another observation I elaborate further down.after_ignore_deferred
1
before_ignore_deferred
2
chain_ignore_deferred
3
after_ignore_deferred
4
before_ignore_deferred
5
after_ignore_deferred
6
before_ignore_deferred
7
after_ignore_deferred
8
before_ignore_deferred
9
chain_ignore_deferred
10
For example the configuration...
... is the third row in the table, where "inserting config by" with "system" means that the first element in the chain tuple is the system, and "asserting config by" with "system" means that the second element in the tuple is also the system itself.
"set" means if the argument is a set, so a
configure_sets
with this configuration...... is the last row.
chain
cannot mix systems and sets, butbefore
andafter
can mix, therefore these have more rows.General observations regarding the two system:
insert_system
is exclusiveinsert_system
is exclusive andassert_system
is not exclusiveThird system affecting the outcome of the tests
Now I am adding a third, empty system and order them after the two above. I observed that this results in some test going green suddenly.
after
1
3
4
7
8
9
10
before
1
3
4
7
8
9
10
chain
4
7
8
9
10
after
1
2
3
5
6
8
9
10
before
1
3
4
7
8
9
10
after
1
2
3
4
5
7
8
9
10
before
1
2
3
5
6
after
1
2
3
5
6
before
1
2
3
5
6
chain
1
2
3
5
6
For example this is the first row with
3
...... and this the second row with
3
...... which both no longer fail.
General observations regarding the third system:
third
is exclusive or notthird
is configured with_ignore_deferred
variants, it does not affect the results of the first teststhird
is put before the two, not after, it does not affect the result of the first testsTest file
I generated the tests with this file which also tests many other combinations that do not fail. It might come useful for testing fixes for this issue. Sorry if it looks horribly, that is how I generate tests. 🫣 Also worth noting that I double-checked that like 5 times but mistakes may only come up with a sixth time. I am just comfortable enough to list the results.
The text was updated successfully, but these errors were encountered: