-
Notifications
You must be signed in to change notification settings - Fork 31
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
Marker-based API #227
Marker-based API #227
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #227 +/- ##
==========================================
+ Coverage 90.62% 91.51% +0.88%
==========================================
Files 28 31 +3
Lines 1718 1909 +191
==========================================
+ Hits 1557 1747 +190
- Misses 161 162 +1 ☔ View full report in Codecov by Sentry. |
afc96be
to
f5aa6c8
Compare
Into separate `write` function to customize it independently.
f5aa6c8
to
f6a640f
Compare
Causes UB.
This reverts commit 61c3150.
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 PR feels like it's working against ReplicationRules
a bit. Replication rules now provide quite a bit less power since deserialize can only be used for serialization optimizations, without any additional logic.
Maybe you could somehow tie replication rule ids to which write command gets selected.
src/client.rs
Outdated
|
||
let end_pos = cursor.position() + data_size as u64; | ||
let mut components_len = 0u32; | ||
while cursor.position() < end_pos { | ||
let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?; | ||
let fns = replication_fns.component_fns(fns_id); | ||
let (serde_fns, command_fns) = replication_fns.get(fns_id); |
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.
let (serde_fns, command_fns) = replication_fns.get(fns_id); | |
let (command_fns, serde_fns) = replication_fns.get(fns_id); |
Nit: this order seems more consistent with how they are used.
src/client.rs
Outdated
cursor.set_position(cursor.position() + data_size as u64); | ||
continue; | ||
} | ||
*entity_tick = message_tick; | ||
|
||
let (mut entity_markers, mut commands, mut query) = state.get_mut(world); | ||
let mut client_entity = query.get_mut(client_entity).unwrap(); |
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.
Is there a race condition where the unwrap
can fire? I.e. despawning in PreUpdate
.
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.
We have test for combining insertions or updates with despawns, they pass.
But yes, if not server, but client itself despawn an entity in PreUpdate
, it will panic. This is how it was before (World::entity_mut
panics if there is no such entity).
I probably should replace it with expect
with something like "replicated entities can be despawned only by server"?
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.
Sure that would work
src/client.rs
Outdated
entity_ticks.insert(client_entity, replicon_tick); | ||
|
||
let (mut entity_markers, mut commands, mut query) = state.get_mut(world); | ||
let mut server_entity = query.get_mut(client_entity).unwrap(); |
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.
let mut server_entity = query.get_mut(client_entity).unwrap(); | |
let mut client_entity = query.get_mut(client_entity).unwrap(); |
My original plan was to just separate writing and just store it in Yes, it loses a little bit of power since writing is now component-based, rather then rule-based. But I think that it's an acceptable trade-off? Feel free to suggest anything about how we can improve it. Also in the comments I answered one of your questions about your use case. Let me know if the proposed solution works for you. |
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
The problem is it can't be guessed until after the first tick of replication, because
This isn't totally true - we know the replication rule
Now you are checking all entities for all marker components. |
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
And improve the description.
d51fb72
to
ffe51df
Compare
Discussed in Discord. We decided to provide |
Open resource-based access to the newly added API. I also bring back `ReplicationRules` resource to public. It was hidden in this PR earlier, but after the separation rework it can be returned back.
6a50ffa
to
a56912e
Compare
This PR have multiple purposes, but unfortunately they highly related, so I can't split it into multiple PRs.
Right for prediction crates like https://github.com/Bendzae/bevy_replicon_snap or https://github.com/RJ/bevy_timewarp we rely on overriding deserialization and remove functions. But this approach have 3 major drawbacks:
Marker-based API solves all of the above. Now for replication user register only
serialize
,deserialize
and newly addeddeserialize_in_place
. These functions assigned to rules as before. So instead ofComponentFns
, we now haveSerdeFns
. And these function now only do one thing - serialization/deserialization.All other logic now represented by
CommandFns
. UnlikeSerdeFns
, this struct created for each component only once and automatically when user register aSerdeFns
for a component. It uses defaultwrite
andremove
functions. To override it, user can register a marker and assign a custom functions for each component. We use markers instead of archetypes because clients do not know in advance the archetype of the entity being deserialized.Then on receive we collect a reusable
Vec<bool>
for each marker (by checking if a marker is present) and based on it pickwrite
/remove
functions for each component. We pick first marker that have a registration for the current component and present on an entity (true
in theVec
). Markers are sorted by priority customizable by user.Since for deserialization we call two functions now (
write
and thendeserialize
), we can do a nice trick to removeunsafe
from ser/de customization and make it even more flexible!Since
write
knows the component type ofdeserialize
, we can store a type-erased function pointers and letwrite
"restore" the type. "restoration" is done by callingtransmute
, but we abstract it insideSerdeFns
and check the type ifdebug_assertions
enabled.So
serialize
anddeserialize
now just a normal functions with a very simple signature. This also unlocks the usage ofdeserialize_in_place
that fallback intodeserialize
if not overridden by user.Similar trick is done for
serialize
, exceptread
is non-overridable by user and only used to remove extra unsafety from the public API.The mentioned
read
andwrite
are still unsafe since it's possible to passSerdeFns
that was created with a different type.And lastly, instead of using
EntityWorldMut
for writing and removal, we useEntityMut
andCommands
. UsingEntityWorldMut
have 2 major drawbacks:EntityWorldMut
for in-place deserialization andClientMapper
at the same time.With commands we can spawn new entities inside
ClientMapper
and it will be possible to batch insertions in the future, see: bevyengine/bevy#10154Huge thanks to @NiseVoid for the idea!