-
Notifications
You must be signed in to change notification settings - Fork 32
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
Store connected clients and their data as entities #423
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #423 +/- ##
==========================================
+ Coverage 84.56% 85.84% +1.28%
==========================================
Files 53 51 -2
Lines 2941 2756 -185
==========================================
- Hits 2487 2366 -121
+ Misses 454 390 -64 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few notes to simplify the review process.
/// | ||
/// It's not replicated and present only on server or singleplayer. | ||
#[derive(Clone, Copy, Component, Deref)] | ||
struct PlayerClient(Entity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reworked examples to avoid replicating components identifiers.
Client don't need know them and it's extra traffic. So we use a separate component that associates spawned entities with connected clients.
packet_loss: f64, | ||
sent_bps: f64, | ||
received_bps: f64, | ||
stats: NetworkStats, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replaced with a struct since we use it as a component on connected entities.
} | ||
|
||
/// Returns `true` if the client is connecting. | ||
/// | ||
/// See also [`Self::status`]. | ||
#[inline] | ||
pub fn is_connecting(&self) -> bool { | ||
matches!(self.status, RepliconClientStatus::Connecting) | ||
self.status == RepliconClientStatus::Connecting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added PartialEq
since we no longer store ClientId
in Connected
.
@@ -7,64 +7,45 @@ use bevy::{ | |||
use super::VisibilityPolicy; | |||
|
|||
/// Entity visibility settings for a client. | |||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this module under server
since it's no longer needed in core.
/// Inserted in [`ServerTestAppExt::connect_client`] and removed by [`ServerTestAppExt::disconnect_client`]. | ||
/// Used to track which client corresponds to which connection. | ||
#[derive(Resource, Deref, Clone, Copy, Debug)] | ||
pub struct TestClientEntity(Entity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since clients no longer store ClientId
in connected state, I need some mechanism to identify which client app belongs to which connected entity. This is why I introduced this resource.
|
||
let replicated_clients = server_app.world().resource::<ReplicatedClients>(); | ||
assert_eq!(replicated_clients.len(), 1); | ||
server_app |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's no longer a trigger, I just check if ReplicatedClient
is not inserted if replicate_after_connect
is inserted.
@@ -110,12 +110,10 @@ fn mapped_existing_entity() { | |||
|
|||
server_app.connect_client(&mut client_app); | |||
|
|||
// Make client and server have different entity IDs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Client and server already have different IDs due to observers and now connected clients.
So I just added an assertion that they are different below.
// Make client and server have different entity IDs. | ||
server_app.world_mut().spawn_empty(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed for this test.
@@ -997,6 +977,53 @@ fn confirm_history() { | |||
assert_eq!(event.tick, tick); | |||
} | |||
|
|||
#[test] | |||
fn after_disconnect() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test to ensure that after disconnect mutation acks are dropped before processing.
@@ -307,7 +306,7 @@ fn event_queue_and_mapping() { | |||
..Default::default() | |||
}), | |||
)) | |||
.add_server_event::<EntityEvent>(ChannelKind::Ordered) | |||
.add_mapped_server_event::<EntityEvent>(ChannelKind::Ordered) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have been a mapped event. Test passed previously because we had the same entity identifiers.
Added an assertion below to prevent this from happening in the future.
feb8e64
to
1241f5a
Compare
Opened projectharmonia/bevy_replicon_renet#24 to test how the new API works with |
Unfortunately I don't have the mental bandwidth to do a proper in-depth review atm. My only comment is eliminating client ids seems like a bad move. Entity ids are essentially unintelligible keys into a big hashmap, not persistent network identifiers. If a client connects, disconnects, and reconnects, then they will end up having two different entity ids throughout the game's lifetime. Client id, on the other hand, clearly identifies the same client at all times. For example, I have no idea how I would make |
Good point! What if we make client entities persistent? Instead of despawning them, we could just remove Replicon's components using That's what Bevy does for gamepads. This will just require updating the docs in Replicon and adjusting the example backend implementation. |
This would be a memory leak for long-running game servers. It also doesn't solve how to make |
Maybe provide a setting to control the behavior (despawn or remove)? 🤔
Right now, backends deterministically convert their client ID into our It's possible to keep our Another option is to use client entities everywhere and provide This is why I decided to reduce the number of ID types to just two: the backend's ID and Utilizing the ECS to store connected clients opens up a lot of possibilities:
This is why I'm trying to migrate to clients as entities in the first place. |
Reworked the Tic-Tac-Toe example to showcase it 🙂 |
This is only somewhat advantageous for a small subset of games. Most games you will know the client list before game start, and can include it in the game start pack.
Game servers can spawn entities for clients as needed, providing
Resources are also inspectable.
This appears to be an advantage, but lookups are only O(1) if you know the entity id. An entity lookup is equivalent in perf to a hashmap lookup, so we would get the same perf by storing a
Again, it's only nice if you know the entity id. Pretty much every single user will end up implementing a |
For games with a matchmaking system - sure. But there are plenty of games where users can host, and the list of clients isn't known (mine included).
You're right - we probably could have reflected our resources and added
Sure, there are other ways to achieve this, ECS just provides it out of the box.
You mean for things like mapping player entities to controlled entities? |
This PR replaces
ConnectedEntities
,ReplicatedEntities
,ClientEntityMap
, andMessages
resources with entities and data stored as components.Connected clients are now represented by entities with a
ConnectedEntity
component. Connected entities become replicated after the insertion ofReplicatedClient
(which replaces theStartReplication
trigger). It's just a marker now, I've split the originalReplicatedClient
into multiple components. Visibility and statistics are now accessible via theClientVisibility
andClientStats
components on replicated entities. TheClientEntityMap
is now a component on replicated entities that stores mappings for them.ClientTicks
is no longer accessible to users, but it isn't needed for the public API - even prediction crates don't use it.UpdateMessage
andMutateMessages
are now crate-private components, just to speed up iteration. Usingzip
is slower, but if everything is on an entity, the performance is similar to what we had before this PR. It's still a bit slower, but I believe the improved ergonomics are worth it.Backends are now responsible for spawning and despawning connection entities. If we merge this, it'll close #176 because it won't be possible to implement.
Since connected clients now have a unique identifier (
Entity
), we no longer needClientId
. Users were often confused by the conversion between Replicon's identifiers and their backend's identifiers. Now, backends can simply insert their identifier as a component on connected entities. Backends that already use entities as identifiers (such asaeronet
) can just insert Replicon's components on their entities. This also gives us both fast iteration and fast lookup (closes #326).The server ID is nowAddedEntity::PLACEHOLDER
. Maybe we should provide an alias to make it more explicit, such asEntity::SERVER
?SERVER
constant.I also removed the
ClientConnected
andClientDisconnected
events. Users can observe forTrigger<OnAdd, ConnectedClient>
orTrigger<OnRemove, ConnectedClient>
.DisconnectReason
is also gone. We don't care about it in Replicon and can't provide a nice representation for it anyway. Users can access proper information from the used backend. There is no need to duplicate it for us.Originally I tried to provide backend-independend API. But it doesn't make much sense because users don't user multiple backends. And some API limit backend implementation in some way, such as our
ClientId
orDisconnectReason
. Changes to entities fixes it.Any suggestions (or even objections!) about the API or naming are welcome.
Requires #421 to be merged first forRebased.tic_tac_toe
example.