Skip to content

Commit

Permalink
Implement serialization of diff in one go
Browse files Browse the repository at this point in the history
  • Loading branch information
Shatur committed Sep 17, 2023
1 parent a85b564 commit de33a62
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 141 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Functions in `AppReplicationExt::replicate_with` now accept bytes cursor for memory reuse and return serialization errors.
- Rename `ReplicationCore` into `RepliconCore` with its module for clarity.
- Store changes in `WorldDiff` in `Vec` instead of `HashMap` to increase performance.
- `MapNetworkEntities` now accepts generic `Mapper` and doesn't have error handling and deserialiation functions now accept `NetworkEntityMap`. This allowed us to lazily map entities on client without extra allocation.
Expand Down
71 changes: 8 additions & 63 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy::{
ecs::{component::Tick, system::Command, world::EntityMut},
ecs::{component::Tick, system::SystemState, world::EntityMut},
prelude::*,
utils::{Entry, HashMap},
};
Expand All @@ -8,8 +8,7 @@ use bevy_renet::{renet::RenetClient, transport::NetcodeClientPlugin, RenetClient
use serde::{Deserialize, Serialize};

use crate::{
prelude::ReplicationRules,
replicon_core::{ComponentDiff, Mapper, WorldDiff, REPLICATION_CHANNEL_ID},
replicon_core::{Mapper, WorldDiff, REPLICATION_CHANNEL_ID},
Replication,
};

Expand Down Expand Up @@ -47,21 +46,16 @@ impl Plugin for ClientPlugin {
}

impl ClientPlugin {
fn diff_receiving_system(
mut commands: Commands,
mut last_tick: ResMut<LastTick>,
mut client: ResMut<RenetClient>,
) {
fn diff_receiving_system(world: &mut World, state: &mut SystemState<ResMut<RenetClient>>) {
let mut client = state.get_mut(world);
let mut last_message = None;
while let Some(message) = client.receive_message(REPLICATION_CHANNEL_ID) {
last_message = Some(message);
}

if let Some(last_message) = last_message {
let world_diff: WorldDiff = bincode::deserialize(&last_message)
.expect("server should send only world diffs over replication channel");
*last_tick = world_diff.tick.into();
commands.apply_world_diff(world_diff);
WorldDiff::deserialize_to_world(world, last_message)
.expect("server should send only valid world diffs");
}
}

Expand Down Expand Up @@ -95,55 +89,6 @@ impl From<LastTick> for Tick {
}
}

trait ApplyWorldDiffExt {
fn apply_world_diff(&mut self, world_diff: WorldDiff);
}

impl ApplyWorldDiffExt for Commands<'_, '_> {
fn apply_world_diff(&mut self, world_diff: WorldDiff) {
self.add(ApplyWorldDiff(world_diff));
}
}

struct ApplyWorldDiff(WorldDiff);

impl Command for ApplyWorldDiff {
fn apply(self, world: &mut World) {
world.resource_scope(|world, mut entity_map: Mut<NetworkEntityMap>| {
world.resource_scope(|world, replication_rules: Mut<ReplicationRules>| {
for (entity, components) in self.0.entities {
let mut entity = entity_map.get_by_server_or_spawn(world, entity);
for component_diff in components {
match component_diff {
ComponentDiff::Changed((replication_id, component)) => {
let replication_info = replication_rules.get_info(replication_id);
(replication_info.deserialize)(
&mut entity,
&mut entity_map,
&component,
);
}
ComponentDiff::Removed(replication_id) => {
let replication_info = replication_rules.get_info(replication_id);
(replication_info.remove)(&mut entity);
}
}
}
}
});

for server_entity in self.0.despawns {
// The entity might have already been deleted with the last diff,
// but the server might not yet have received confirmation from the
// client and could include the deletion in the latest diff.
if let Some(client_entity) = entity_map.remove_by_server(server_entity) {
world.entity_mut(client_entity).despawn_recursive();
}
}
});
}
}

/// Set with replication and event systems related to client.
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum ClientSet {
Expand Down Expand Up @@ -172,7 +117,7 @@ impl NetworkEntityMap {
self.client_to_server.insert(client_entity, server_entity);
}

fn get_by_server_or_spawn<'a>(
pub(super) fn get_by_server_or_spawn<'a>(
&mut self,
world: &'a mut World,
server_entity: Entity,
Expand All @@ -189,7 +134,7 @@ impl NetworkEntityMap {
}
}

fn remove_by_server(&mut self, server_entity: Entity) -> Option<Entity> {
pub(super) fn remove_by_server(&mut self, server_entity: Entity) -> Option<Entity> {
let client_entity = self.server_to_client.remove(&server_entity);
if let Some(client_entity) = client_entity {
self.client_to_server.remove(&client_entity);
Expand Down
31 changes: 18 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,30 +89,34 @@ If your component doesn't implement serde traits or you want to serialize it par
you can use [`AppReplicationExt::replicate_with`]:
```rust
use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap};
# use bevy_replicon::prelude::*;
# use std::io::Cursor;
# use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap};
# use bevy_replicon::{prelude::*, renet::Bytes};
# use serde::{Deserialize, Serialize};
# let mut app = App::new();
# app.add_plugins(ReplicationPlugins);
app.replicate_with::<Transform>(serialize_transform, deserialize_transform);
/// Serializes only translation.
fn serialize_transform(component: Ptr) -> Vec<u8> {
fn serialize_transform(
component: Ptr,
cursor: &mut Cursor<&mut Vec<u8>>,
) -> Result<(), bincode::Error> {
// SAFETY: Function called for registered `ComponentId`.
let transform: &Transform = unsafe { component.deref() };
bincode::serialize(&transform.translation)
.unwrap_or_else(|e| panic!("Vec3 should be serialzable: {e}"))
bincode::serialize_into(cursor, &transform.translation)
}
/// Deserializes translation and creates [`Transform`] from it.
fn deserialize_transform(
entity: &mut EntityMut,
_entity_map: &mut NetworkEntityMap,
component: &[u8],
) {
let translation: Vec3 = bincode::deserialize(component)
.unwrap_or_else(|e| panic!("bytes from server should be Vec3: {e}"));
cursor: &mut Cursor<Bytes>,
) -> Result<(), bincode::Error> {
let translation: Vec3 = bincode::deserialize_from(cursor)?;
entity.insert(Transform::from_translation(translation));
Ok(())
}
```
Expand All @@ -138,8 +142,9 @@ necessary components after replication. If you want to avoid one frame delay, pu
your initialization systems to [`ClientSet::Receive`]:
```rust
use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap};
# use bevy_replicon::prelude::*;
# use std::io::Cursor;
# use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap};
# use bevy_replicon::{prelude::*, renet::Bytes};
# use serde::{Deserialize, Serialize};
# let mut app = App::new();
# app.add_plugins(ReplicationPlugins);
Expand All @@ -166,8 +171,8 @@ fn player_init_system(
#[derive(Component, Deserialize, Serialize)]
struct Player;
# fn serialize_transform(_: Ptr) -> Vec<u8> { unimplemented!() }
# fn deserialize_transform(_: &mut EntityMut, _: &mut NetworkEntityMap, _: &[u8]) {}
# fn serialize_transform(_: Ptr, _: &mut Cursor<&mut Vec<u8>>) -> Result<(), bincode::Error> { unimplemented!() }
# fn deserialize_transform(_: &mut EntityMut, _: &mut NetworkEntityMap, _: &mut Cursor<Bytes>) -> Result<(), bincode::Error> { unimplemented!() }
```
If your game have save states you probably want to re-use the same logic to
Expand Down
Loading

0 comments on commit de33a62

Please sign in to comment.