Skip to content

Commit

Permalink
Replication groups (#224)
Browse files Browse the repository at this point in the history
Co-authored-by: UkoeHB <[email protected]>
  • Loading branch information
Shatur and UkoeHB authored Apr 2, 2024
1 parent 33fdcb4 commit b9596b7
Show file tree
Hide file tree
Showing 17 changed files with 1,246 additions and 518 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `AppReplicationExt::replicate_group` and `GroupRegistration` trait to register and customize component groups.
- `ServerSet::StoreHierarchy` for systems that store hierarchy changes in `ParentSync`.
- `ReplicationRule` that describes how a component or a group of components will be serialized, deserialized and removed.

### Changed

- `AppReplicationExt::replicate_with` now accepts newly added `ReplicationFns`.
- Move `Replication` to `core` module.
- Move all functions-related logic from `ReplicationRules` into a new `ReplicationFns`.
- Rename `serialize_component` into `serialize` and move into `replication_fns` module.
- Rename `deserialize_component` into `deserialize` and move into `replication_fns` module.
- Rename `remove_component` into `remove` and move into `replication_fns` module.
- Move `despawn_recursive` into `replication_fns` module.

### Removed

- `dont_replicate` module. Use newtypes or [`bevy_bundlication`](https://github.com/NiseVoid/bevy_bundlication) for more complex cases.
- `dont_replicate` module. Use the newly added `AppReplicationExt::replicate_group` or newtypes.

## [0.24.1] - 2024-03-07

Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ authors = [
"koe <[email protected]>",
]
edition = "2021"
rust-version = "1.65"
description = "High level networking for the Bevy game engine"
readme = "README.md"
repository = "https://github.com/projectharmonia/bevy_replicon"
Expand All @@ -31,13 +30,18 @@ ordered-multimap = "0.7"

[dev-dependencies]
bevy = { version = "0.13", default-features = false, features = [
"serialize",
"bevy_asset",
"bevy_sprite",
] }
criterion = { version = "0.5", default-features = false, features = [
"cargo_bench_support",
] }

[lints.clippy]
type_complexity = "allow"
too_many_arguments = "allow"

[[bench]]
name = "replication"
harness = false
Expand Down
56 changes: 26 additions & 30 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use varint_rs::VarintReader;

use crate::core::{
common_conditions::{client_connected, client_just_connected, client_just_disconnected},
replication_rules::{Replication, ReplicationRules},
replicon_channels::ReplicationChannel,
replicon_channels::RepliconChannels,
replication_fns::ReplicationFns,
replicon_channels::{ReplicationChannel, RepliconChannels},
replicon_tick::RepliconTick,
Replication,
};
use client_mapper::ServerEntityMap;
use diagnostics::ClientStats;
Expand Down Expand Up @@ -82,7 +82,7 @@ impl ClientPlugin {
world.resource_scope(|world, mut entity_map: Mut<ServerEntityMap>| {
world.resource_scope(|world, mut entity_ticks: Mut<ServerEntityTicks>| {
world.resource_scope(|world, mut buffered_updates: Mut<BufferedUpdates>| {
world.resource_scope(|world, replication_rules: Mut<ReplicationRules>| {
world.resource_scope(|world, replication_fns: Mut<ReplicationFns>| {
let mut stats = world.remove_resource::<ClientStats>();
apply_replication(
world,
Expand All @@ -91,7 +91,7 @@ impl ClientPlugin {
&mut entity_ticks,
&mut buffered_updates,
stats.as_mut(),
&replication_rules,
&replication_fns,
)?;

if let Some(stats) = stats {
Expand Down Expand Up @@ -129,7 +129,7 @@ fn apply_replication(
entity_ticks: &mut ServerEntityTicks,
buffered_updates: &mut BufferedUpdates,
mut stats: Option<&mut ClientStats>,
replication_rules: &ReplicationRules,
replication_fns: &ReplicationFns,
) -> Result<(), Box<bincode::ErrorKind>> {
while let Some(message) = client.receive(ReplicationChannel::Init) {
apply_init_message(
Expand All @@ -138,7 +138,7 @@ fn apply_replication(
entity_map,
entity_ticks,
stats.as_deref_mut(),
replication_rules,
replication_fns,
)?;
}

Expand All @@ -151,7 +151,7 @@ fn apply_replication(
entity_ticks,
buffered_updates,
stats.as_deref_mut(),
replication_rules,
replication_fns,
replicon_tick,
)?;

Expand All @@ -171,7 +171,7 @@ fn apply_replication(
entity_map,
entity_ticks,
stats.as_deref_mut(),
replication_rules,
replication_fns,
update.message_tick,
) {
result = Err(e);
Expand All @@ -191,7 +191,7 @@ fn apply_init_message(
entity_map: &mut ServerEntityMap,
entity_ticks: &mut ServerEntityTicks,
mut stats: Option<&mut ClientStats>,
replication_rules: &ReplicationRules,
replication_fns: &ReplicationFns,
) -> bincode::Result<()> {
let end_pos: u64 = message.len().try_into().unwrap();
let mut cursor = Cursor::new(message);
Expand All @@ -216,7 +216,7 @@ fn apply_init_message(
entity_map,
entity_ticks,
stats.as_deref_mut(),
replication_rules,
replication_fns,
replicon_tick,
)?;
if cursor.position() == end_pos {
Expand All @@ -230,7 +230,7 @@ fn apply_init_message(
entity_ticks,
stats.as_deref_mut(),
ComponentsKind::Removal,
replication_rules,
replication_fns,
replicon_tick,
)?;
if cursor.position() == end_pos {
Expand All @@ -244,7 +244,7 @@ fn apply_init_message(
entity_ticks,
stats,
ComponentsKind::Insert,
replication_rules,
replication_fns,
replicon_tick,
)?;

Expand All @@ -257,15 +257,14 @@ fn apply_init_message(
/// corresponding tick hasn't arrived), it will be buffered.
///
/// Returns update index to be used for acknowledgment.
#[allow(clippy::too_many_arguments)]
fn apply_update_message(
message: Bytes,
world: &mut World,
entity_map: &mut ServerEntityMap,
entity_ticks: &mut ServerEntityTicks,
buffered_updates: &mut BufferedUpdates,
mut stats: Option<&mut ClientStats>,
replication_rules: &ReplicationRules,
replication_fns: &ReplicationFns,
replicon_tick: RepliconTick,
) -> bincode::Result<u16> {
let end_pos: u64 = message.len().try_into().unwrap();
Expand Down Expand Up @@ -293,7 +292,7 @@ fn apply_update_message(
entity_map,
entity_ticks,
stats,
replication_rules,
replication_fns,
message_tick,
)?;

Expand Down Expand Up @@ -328,15 +327,14 @@ fn apply_entity_mappings(
}

/// Deserializes replicated components of `components_kind` and applies them to the `world`.
#[allow(clippy::too_many_arguments)]
fn apply_init_components(
cursor: &mut Cursor<&[u8]>,
world: &mut World,
entity_map: &mut ServerEntityMap,
entity_ticks: &mut ServerEntityTicks,
mut stats: Option<&mut ClientStats>,
components_kind: ComponentsKind,
replication_rules: &ReplicationRules,
replication_fns: &ReplicationFns,
replicon_tick: RepliconTick,
) -> bincode::Result<()> {
let entities_len: u16 = bincode::deserialize_from(&mut *cursor)?;
Expand All @@ -349,14 +347,13 @@ fn apply_init_components(
let end_pos = cursor.position() + data_size as u64;
let mut components_len = 0u32;
while cursor.position() < end_pos {
let replication_id = DefaultOptions::new().deserialize_from(&mut *cursor)?;
// SAFETY: server and client have identical `ReplicationRules` and server always sends valid IDs.
let replication_info = unsafe { replication_rules.get_info_unchecked(replication_id) };
let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?;
let fns = replication_fns.component_fns(fns_id);
match components_kind {
ComponentsKind::Insert => {
(replication_info.deserialize)(&mut entity, entity_map, cursor, replicon_tick)?
(fns.deserialize)(&mut entity, entity_map, cursor, replicon_tick)?
}
ComponentsKind::Removal => (replication_info.remove)(&mut entity, replicon_tick),
ComponentsKind::Removal => (fns.remove)(&mut entity, replicon_tick),
}
components_len += 1;
}
Expand All @@ -376,7 +373,7 @@ fn apply_despawns(
entity_map: &mut ServerEntityMap,
entity_ticks: &mut ServerEntityTicks,
stats: Option<&mut ClientStats>,
replication_rules: &ReplicationRules,
replication_fns: &ReplicationFns,
replicon_tick: RepliconTick,
) -> bincode::Result<()> {
let entities_len: u16 = bincode::deserialize_from(&mut *cursor)?;
Expand All @@ -393,7 +390,7 @@ fn apply_despawns(
.and_then(|entity| world.get_entity_mut(entity))
{
entity_ticks.remove(&client_entity.id());
(replication_rules.despawn_fn)(client_entity, replicon_tick);
(replication_fns.despawn)(client_entity, replicon_tick);
}
}

Expand All @@ -409,7 +406,7 @@ fn apply_update_components(
entity_map: &mut ServerEntityMap,
entity_ticks: &mut ServerEntityTicks,
mut stats: Option<&mut ClientStats>,
replication_rules: &ReplicationRules,
replication_fns: &ReplicationFns,
message_tick: RepliconTick,
) -> bincode::Result<()> {
let message_end = cursor.get_ref().len() as u64;
Expand All @@ -435,10 +432,9 @@ fn apply_update_components(
let end_pos = cursor.position() + data_size as u64;
let mut components_count = 0u32;
while cursor.position() < end_pos {
let replication_id = DefaultOptions::new().deserialize_from(&mut *cursor)?;
// SAFETY: server and client have identical `ReplicationRules` and server always sends valid IDs.
let replication_info = unsafe { replication_rules.get_info_unchecked(replication_id) };
(replication_info.deserialize)(&mut entity, entity_map, cursor, message_tick)?;
let fns_id = DefaultOptions::new().deserialize_from(&mut *cursor)?;
let fns = replication_fns.component_fns(fns_id);
(fns.deserialize)(&mut entity, entity_map, cursor, message_tick)?;
components_count += 1;
}
if let Some(stats) = &mut stats {
Expand Down
2 changes: 1 addition & 1 deletion src/client/client_mapper.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bevy::{ecs::entity::EntityHashMap, prelude::*, utils::hashbrown::hash_map::Entry};

use crate::core::replication_rules::Replication;
use crate::core::Replication;

/// Maps server entities into client entities inside components.
///
Expand Down
10 changes: 9 additions & 1 deletion src/core.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pub mod common_conditions;
pub mod replication_fns;
pub mod replication_rules;
pub mod replicon_channels;
pub mod replicon_tick;

use bevy::prelude::*;

use replication_rules::{Replication, ReplicationRules};
use replication_fns::ReplicationFns;
use replication_rules::ReplicationRules;
use replicon_channels::RepliconChannels;
use replicon_tick::RepliconTick;
use serde::{Deserialize, Serialize};
Expand All @@ -17,10 +19,16 @@ impl Plugin for RepliconCorePlugin {
app.register_type::<Replication>()
.init_resource::<RepliconTick>()
.init_resource::<RepliconChannels>()
.init_resource::<ReplicationFns>()
.init_resource::<ReplicationRules>();
}
}

/// Marks entity for replication.
#[derive(Component, Clone, Copy, Default, Reflect, Debug)]
#[reflect(Component)]
pub struct Replication;

/// Unique client ID.
///
/// Could be a client or a dual server-client.
Expand Down
Loading

0 comments on commit b9596b7

Please sign in to comment.