Skip to content

Conversation

@Shatur
Copy link
Contributor

@Shatur Shatur commented Sep 6, 2025

Recently I took a quick look at how backends handle system ordering, and they didn't do it the way I intended. For example, no one uses the PrepareSend sets. This isn't the authors' fault - it just means our API sucks and we need to improve it.

This PR resurrects (and extends) my previous attempt to migrate to states (#487), which solves the problem. I closed it last time because of the inevitable one-frame delay: StateTransitions runs after PreUpdate, but backend receive systems (and ours) run in PreUpdate. Since our systems rely on the state, they wouldn't run until the next frame.
But I found a simple solution: run our receive systems additionally in OnEnter(ClientState::Connected)!

Pros of this approach:

  • States let users to use things like StateScoped, and run systems more efficiently using OnEnter or OnExit instead of evaluating conditions every frame.
  • No more PrepareSend set or tricky state updates split between PreUpdate and PostUpdate. Backends just update the state in PreUpdate during ReceivePackets.
  • No more RepliconServer and RepliconClient. Since they only hold messages, they're now renamed to ServerMessages and ClientMessages.
  • The state can now be queried in observers during replication receive. Needed for bevy_rewind.

Cons:

  • We now depend on bevy_state, which means we have to use it everywhere in our tests since we rely on MinimalPlugins. But I think that's fine.

To make the review simpler, I split each change into a separate commit. I'd recommend reviewing them one by one. The only important change is 71e97b1. Everything else is just renames/refactors (which take most of the PR diff).

@Shatur Shatur requested a review from UkoeHB September 6, 2025 12:45
@Shatur Shatur force-pushed the states-v2 branch 2 times, most recently from 92f62b6 to b8fa30d Compare September 6, 2025 12:49
@Shatur Shatur marked this pull request as draft September 6, 2025 12:59
@Shatur Shatur force-pushed the states-v2 branch 2 times, most recently from 50a9742 to a6b97fa Compare September 6, 2025 13:19
@codecov
Copy link

codecov bot commented Sep 6, 2025

Codecov Report

❌ Patch coverage is 86.81319% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.45%. Comparing base (aca414e) to head (dc5c386).
⚠️ Report is 13 commits behind head on master.

Files with missing lines Patch % Lines
src/shared/backend/client_messages.rs 83.33% 7 Missing ⚠️
src/shared/event/server_event.rs 45.45% 6 Missing ⚠️
src/server.rs 78.94% 4 Missing ⚠️
src/client/event.rs 92.30% 2 Missing ⚠️
src/server/event.rs 83.33% 2 Missing ⚠️
src/client.rs 96.15% 1 Missing ⚠️
src/shared/backend/server_messages.rs 75.00% 1 Missing ⚠️
src/test_app.rs 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #565      +/-   ##
==========================================
+ Coverage   78.34%   78.45%   +0.10%     
==========================================
  Files          57       56       -1     
  Lines        3187     3133      -54     
==========================================
- Hits         2497     2458      -39     
+ Misses        690      675      -15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Shatur Shatur marked this pull request as ready for review September 6, 2025 13:32
This allows users to utilize things like `StateScoped` and run systems
more efficiently using `OnEnter` or `OnExit` without evaluating
conditions every frame.

As a result, we now require `StatesPlugin` to be present. It's included
by default in `DefaultPlugins`, but with `MinimalPlugins` you have to
add it manually. In tests, I had to add `StatesPlugin` everywhere.
It's associated with both connected client on the server and the client
itself.
Makes it more clear that it's only for clients.
Because it holds only messages now.
Because it holds only messages now.
@Shatur
Copy link
Contributor Author

Shatur commented Sep 6, 2025

Migrated bevy_replicon_renet to test the changes more:
simgine/bevy_replicon_renet#46

Works great, no logical changes to any tests needed.

@UkoeHB
Copy link
Collaborator

UkoeHB commented Sep 14, 2025

The PrepareSend sets would be used in renet2. I just forgot/haven't updated yet.

set_stopped.run_if(resource_removed::<ExampleServer>),
)
.chain(),
set_running.run_if(resource_added::<ExampleServer>),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be before receive_packets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary, the packets can be passed into ServerMessages before we change the state.

/// System that sends [`ProtocolHash`].
///
/// Can be used by backends to add custom logic before sending data, such as transition to a disconnected or connecting state.
PrepareSend,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep this for renet2 to use.

Copy link
Contributor Author

@Shatur Shatur Sep 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against, but how do you plan to use it? I ported bevy_renet, and PrepareSend wasn't needed because of the states (I used it previously in bevy_renet).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No necessary anymore. Just like with the server everything can be done in ClientSet::ReceivePackets. Here is how I did it in renet:
https://github.com/simgine/bevy_replicon_renet/blob/9b45a0bb84f4d47d47cc595b5cfbb6309fc139db/src/client.rs#L23-L24

Comment on lines 19 to 26
add_measurements
.in_set(ClientSet::Diagnostics)
.run_if(client_connected),
.run_if(in_state(ClientState::Connected)),
)
.add_systems(
OnEnter(ClientState::Connected),
add_measurements.in_set(ClientSet::Diagnostics),
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very awkward to me. Adding states means there are systems in the main schedule and in state schedules, which is an increase in complexity not a decrease.

Copy link
Contributor Author

@Shatur Shatur Sep 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran Replicon systems upon entering the schedule and within the schedule. This lets me process packets immediately after connection. It's not ideal, but I think it's worth it.

/// [`SyncRelatedAppExt::sync_related_entities`].
///
/// Can be used by backends to add custom logic before sending data, such as transition to a stopped state.
PrepareSend,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep this for use in renet2.

@Shatur Shatur merged commit 466ac88 into master Sep 15, 2025
10 checks passed
@Shatur Shatur deleted the states-v2 branch September 15, 2025 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants