Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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