-
Notifications
You must be signed in to change notification settings - Fork 33
Value-diff compression #178
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
Current stateWe now pass ticks to serializers and send different data to different clients. We just need to add a way to get an old value. Component-based approachWe can create a component The overhead of this solution will be an add additional lookup for I also considered making it generic to avoid dealing with type-magic, but it makes it harder to clean when a client disconnects, requires to query for Resource-based approachExactly the same as component-based approach, but with additional internal hash map for entities, no The iteration overhead will be near-zero (we lookup for the resource once instead of per-entity), but if a component serialization function needs the history, it's a bit slower due to the entity-based lookup. Also simpler cleanup on disconnect. QuestionsI would appreciate opinions.
|
Resource-based sounds better. Leads to better memory management as entities are spawned and despawned.
Can't we just map to ComponentId? |
Yeah, that's what I suggesting: map |
Additional notesInstead of providing the resource from context, we provide a resource adapter that is scoped to the current entity. Just to avoid passing entity each time, will be nicer to use. Forgot to talk about ticks. We will need to store values with ticks and return only the last acked. So instead of API showcaseConsidering all the above, here is how the API might look like: #[derive(Component, Serialize, Deserialize, Clone, Copy)]
struct Example {
a: u32,
b: u32,
c: u32,
}
// Let's write the diff logic as methods for clarity.
impl Example {
fn delta(&self, _other: &Self) -> ExampleDelta {
todo!();
}
fn apply(&mut self, _delta: ExampleDelta) {
todo!();
}
}
/// Depends on how user want to compress, but let's consider the simplest example.
#[derive(Serialize, Deserialize, Clone, Copy)]
struct ExampleDelta {
a: Option<u32>,
b: Option<u32>,
c: Option<u32>,
}
/// User overrides the serialization function that checks last received value using context.
fn serialize(
ctx: &SerializeCtx,
component: &Example,
cursor: &mut Cursor<Vec<u8>>,
) -> bincode::Result<()> {
if let Some(acked_component) = ctx.component_values.last_acked::<Example>() {
let delta = component.delta(acked_component);
DefaultOptions::new().serialize_into(cursor, &delta)?;
} else {
// We can call the default serialization function when there are no delta.
default_serialize(ctx, component, cursor)?;
}
ctx.component_values.add_unacked(*component, ctx.server_tick);
Ok(())
}
/// Deserialization in place called only for already received values, so we override it to use delta.
///
/// Regular deserialization function in this example will be the default one.
fn deserialize_in_place(
_deserialize: DeserializeFn<Example>,
_ctx: &mut WriteCtx,
component: &mut Example,
cursor: &mut Cursor<&[u8]>,
) -> bincode::Result<()> {
let delta: ExampleDelta = DefaultOptions::new().deserialize_from(cursor)?;
component.apply(delta);
Ok(())
} Built-in traitWe can provide a trait to automate registration. But user will have to write it manually when mapping is needed (unless we also provide a helper for diff+mapping) and for third-party types. So I don't think that we need to provide it. |
Some networked games try to compress diffs between updates by networking only the value difference at a component level. Replicon doesn't currently support that.
Here is a first-draft idea for how to support it:
If we pass the change-limit tick + current tick into serializers, then the serializer can have a local that tracks historical state and performs a diff internally. The deserializer would also need to track historical state and apply diffs to the correct older value. We might be able to provide a pre-baked value-diff compressor for types that implement certain traits.
Note that supporting this would require changes to the shared copy buffer, since different clients could get different component serializations.
The text was updated successfully, but these errors were encountered: