Skip to content
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

Clean up events more rigorously #163

Merged
merged 3 commits into from
Jan 10, 2024
Merged

Conversation

UkoeHB
Copy link
Collaborator

@UkoeHB UkoeHB commented Jan 10, 2024

  • Moves ClientSet::ResetEvents before renet updates, to ensure events are cleared immediately before connecting.
  • ClientSet::ResetEvents runs every tick while waiting to connect to ensure the tick where you connect is as clean as possible.
  • Add system to discard client events while the client is waiting to connect. This is equivalent to letting client events hit the renet client, which will drop them.

@codecov-commenter
Copy link

codecov-commenter commented Jan 10, 2024

Codecov Report

Attention: 3 lines in your changes are missing coverage. Please review.

Comparison is base (a6609a5) 83.37% compared to head (63d98bb) 90.35%.
Report is 4 commits behind head on master.

Files Patch % Lines
src/network_event/server_event.rs 60.00% 2 Missing ⚠️
src/network_event/client_event.rs 80.00% 1 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #163      +/-   ##
==========================================
+ Coverage   83.37%   90.35%   +6.98%     
==========================================
  Files          20       19       -1     
  Lines        1287     1192      -95     
==========================================
+ Hits         1073     1077       +4     
+ Misses        214      115      -99     

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

@Shatur
Copy link
Contributor

Shatur commented Jan 10, 2024

But why not just drain events once at disconnect?
Also running before the Renet's update will introduce one frame delay.
Here is how I would see it: #166.

@UkoeHB
Copy link
Collaborator Author

UkoeHB commented Jan 10, 2024

But why not just drain events once at disconnect?

Because it's best to mimic the renet interface as much as possible. Messages sent to renet while connecting will be dropped. It might seem like a good idea to be lenient and allow some message buffering, but in my experience that just makes reconnects ambiguous and way more difficult to handle properly (I ran into this with bevy_simplenet). It's best to make reconnects as clean and precise as possible, which means pessimistically dropping messages.

Also running before the Renet's update will introduce one frame delay.

Delay does not matter for dropping events. For precision, we drop everything until the moment renet connects.

@Shatur
Copy link
Contributor

Shatur commented Jan 10, 2024

In Replicon we reset everything right after disconnect. I would prefer to keep it consistent. I don't think that #166 will affect robustness in any way.

@UkoeHB
Copy link
Collaborator Author

UkoeHB commented Jan 10, 2024

In Replicon we reset everything right after disconnect.

We only reset internal caches of the client that won't be used until the next replication message. Event resetting is an entirely different subject.

The only reason to not reset every tick is because you want to allow message buffering while connecting. In my opinion and experience that is not justified and makes the API harder to reason about.

@Shatur
Copy link
Contributor

Shatur commented Jan 10, 2024

Resetting events every tick is just weird. Why doing so?
We either reset it before connect or after disconnect. And I prefer the later one since it's consistent with the rest of the codebase and what I would expect as a user.

@UkoeHB
Copy link
Collaborator Author

UkoeHB commented Jan 10, 2024

Resetting events every tick is just weird. Why doing so?

Resetting events is just a means to an end - we are discarded messages sent to a client that isn't connected. The semantics of a disconnected client need to be very precise: messages will fail to send in the span of time between a disconnect (which happens implicitly - we don't know precisely when messages start to fail) and a connect event. This ensures connect events cleanly mark the start of a new session.

@Shatur
Copy link
Contributor

Shatur commented Jan 10, 2024

I understand your point, but I'm not sure if it's justifies running a system for each event to cleanup the event queue. It's not a cost-free check.
I think that a single cleanup after disconnect is fine, just advise users to avoid sending events before the connection.

@UkoeHB
Copy link
Collaborator Author

UkoeHB commented Jan 10, 2024

I think that a single cleanup after disconnect is fine, just advise users to avoid sending events before the connection.

This is a great example of my point. If we need to say "make an effort to do things right", then the API is suboptimal. The API should be right automatically.

I understand your point, but I'm not sure if it's justifies running a system for each event to cleanup the event queue. It's not a cost-free check.

We only run it when the client is not connected, which should definitely not be the hot path. If you are really concerned then we can .run_if(client_just_connected()) between RenetReceive and ClientSet::Receive.

@Shatur
Copy link
Contributor

Shatur commented Jan 10, 2024

We only run it when the client is not connected

Right, I actually forgot about it. Then performance-wise it should be fine.
But I would prefer to run it before the connection, I think it will be a good middle-ground while still solves the mentioned problem.

@UkoeHB UkoeHB requested a review from Shatur January 10, 2024 21:32
Comment on lines 356 to 365
/// Gets the event queue length.
pub fn len(&mut self) -> usize {
self.0.values_len()
}

/// Tests whether there are any events queued.
pub fn is_empty(&mut self) -> bool {
self.len() == 0
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Since we agree on #164, let's remove these methods and just use .0.

src/client.rs Outdated
ClientSet::ResetEvents
.after(NetcodeClientPlugin::update_system)
.before(ClientSet::Receive)
.run_if(not(has_authority()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need not(has_authority())?

Copy link
Collaborator Author

@UkoeHB UkoeHB Jan 10, 2024

Choose a reason for hiding this comment

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

One of the tests was breaking because local clients don't have a RenetClient (I guess?) so not(is_connected()) was always true and local resending wasn't working. The new run condition should not have that problem though, so I will remove this.

Comment on lines -496 to -498
/// This is a separate set from `ClientSet::Reset` in case you want to enable/disable it separately.
/// In general it is best practice to clear queued server events after a disconnect, whereas you may want to
/// preserve replication state (in which case `ClientSet::Reset` should be disabled).
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe keep this part? Looks like a useful explanation.

Copy link
Contributor

@Shatur Shatur left a comment

Choose a reason for hiding this comment

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

Thanks!
Could you rebase to the latest master? Because there are some conflitcs because of my PR. And while you are at it, I left two more pure-style suggestions.

Will away from the computer for a couple hours, so I approving now, feel free to merge.

src/client.rs Outdated
Comment on lines 7 to 8
use bevy_renet::{client_connected, client_just_connected, client_just_disconnected, renet::Bytes};
use bevy_renet::{renet::RenetClient, transport::NetcodeClientPlugin, RenetClientPlugin};
Copy link
Contributor

Choose a reason for hiding this comment

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

While you are at it, could you group these imports, please?

@@ -139,6 +139,7 @@ impl ClientEventAppExt for App {
self.add_event::<T>()
.init_resource::<Events<FromClient<T>>>()
.insert_resource(ClientEventChannel::<T>::new(channel_id))
.add_systems(PreUpdate, reset_system::<T>.in_set(ClientSet::ResetEvents))
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you group all PreUpdate systems here and in server_event?
Just for consistency with Bevy and the rest of the codebase.

@UkoeHB UkoeHB enabled auto-merge (squash) January 10, 2024 23:28
@UkoeHB UkoeHB merged commit 1871aff into projectharmonia:master Jan 10, 2024
5 checks passed
@UkoeHB UkoeHB deleted the event_cleanup branch January 11, 2024 00:05
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