Skip to content
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

Support custom ticks from FixedUpdate games #68

Merged
merged 29 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
af6a5d1
Make changes to NetworkTick, now a Resource, trigger sends.
RJ Oct 2, 2023
4ae3339
remove unused derive_more dep
RJ Oct 2, 2023
61ba5ed
add custom deserialization with NetworkTick test
RJ Oct 2, 2023
bfae493
typos and tweaks
RJ Oct 2, 2023
0076a0a
Bring derive_more back
Shatur Oct 2, 2023
001865f
Remove Default for ReplicationPlugins
Shatur Oct 2, 2023
feeb463
Return `Result<Option<NetworkTick>>` from `deserialize_tick`
Shatur Oct 2, 2023
5c0d5c3
Reorder imports
Shatur Oct 2, 2023
7bdae96
Put network tick increment inside `ServerPlugin` for consistency
Shatur Oct 2, 2023
26287c9
Rename `ServerTicks` into `AckedTicks`
Shatur Oct 2, 2023
f8413f9
Rename despawn_entity_fn into despawn_fn
Shatur Oct 2, 2023
314f3bc
Rework despawn API
Shatur Oct 2, 2023
2dc568f
Use singular
Shatur Oct 2, 2023
b50fc31
Add removal function to `replicate_with` and remove helpers
Shatur Oct 2, 2023
d4a6a7d
Remove tests for custom removals
Shatur Oct 2, 2023
6f818c0
Use dedicated newtype resource for current tick
Shatur Oct 2, 2023
11ce556
Update docs
Shatur Oct 2, 2023
bb6ebd5
Merge branch 'master' into user_tick
Shatur Oct 2, 2023
44ef227
Reduce diffs
Shatur Oct 2, 2023
9e22d9c
Better docs
Shatur Oct 2, 2023
f68b2a2
Use more consistent naming
Shatur Oct 2, 2023
bbd142a
Merge branch 'master' into user_tick
Shatur Oct 2, 2023
ef5b664
Use `NetworkTick` as resource directly
Shatur Oct 2, 2023
18b8bc4
Fix doctests
Shatur Oct 2, 2023
bb9d25e
Fix formatting
Shatur Oct 2, 2023
907cfc2
Fix typo
Shatur Oct 2, 2023
9c5c7c6
Fix doctests
Shatur Oct 2, 2023
108c817
Apply doc suggestions [skip ci]
Shatur Oct 2, 2023
27f0084
Apply suggestions from @UkoeHB
Shatur Oct 2, 2023
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
34 changes: 24 additions & 10 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ impl ClientPlugin {
let end_pos: u64 = message.len().try_into().unwrap();
let mut cursor = Cursor::new(message);

if !deserialize_tick(&mut cursor, world)? {
let Some(network_tick) = deserialize_tick(&mut cursor, world)? else {
continue;
}
};
if cursor.position() == end_pos {
continue;
}
Expand All @@ -70,6 +70,7 @@ impl ClientPlugin {
&mut entity_map,
&replication_rules,
DiffKind::Change,
network_tick,
)?;
if cursor.position() == end_pos {
continue;
Expand All @@ -81,12 +82,19 @@ impl ClientPlugin {
&mut entity_map,
&replication_rules,
DiffKind::Removal,
network_tick,
)?;
if cursor.position() == end_pos {
continue;
}

deserialize_despawns(&mut cursor, world, &mut entity_map)?;
deserialize_despawns(
&mut cursor,
world,
&mut entity_map,
&replication_rules,
network_tick,
)?;
}

Ok(())
Expand All @@ -109,16 +117,19 @@ impl ClientPlugin {

/// Deserializes server tick and applies it to [`LastTick`] if it is newer.
///
/// Returns true if [`LastTick`] has been updated.
fn deserialize_tick(cursor: &mut Cursor<Bytes>, world: &mut World) -> Result<bool, bincode::Error> {
/// Returns the tick if [`LastTick`] has been updated.
fn deserialize_tick(
cursor: &mut Cursor<Bytes>,
world: &mut World,
) -> Result<Option<NetworkTick>, bincode::Error> {
let tick = bincode::deserialize_from(cursor)?;

let mut last_tick = world.resource_mut::<LastTick>();
if last_tick.0 < tick {
last_tick.0 = tick;
Ok(true)
Ok(Some(tick))
} else {
Ok(false)
Ok(None)
}
}

Expand All @@ -129,6 +140,7 @@ fn deserialize_component_diffs(
entity_map: &mut NetworkEntityMap,
replication_rules: &ReplicationRules,
diff_kind: DiffKind,
tick: NetworkTick,
) -> Result<(), bincode::Error> {
let entities_count: u16 = bincode::deserialize_from(&mut *cursor)?;
for _ in 0..entities_count {
Expand All @@ -141,9 +153,9 @@ fn deserialize_component_diffs(
let replication_info = unsafe { replication_rules.get_info_unchecked(replication_id) };
match diff_kind {
DiffKind::Change => {
(replication_info.deserialize)(&mut entity, entity_map, cursor)?
(replication_info.deserialize)(&mut entity, entity_map, cursor, tick)?
}
DiffKind::Removal => (replication_info.remove)(&mut entity),
DiffKind::Removal => (replication_info.remove)(&mut entity, tick),
}
}
}
Expand All @@ -156,6 +168,8 @@ fn deserialize_despawns(
cursor: &mut Cursor<Bytes>,
world: &mut World,
entity_map: &mut NetworkEntityMap,
replication_rules: &ReplicationRules,
tick: NetworkTick,
) -> Result<(), bincode::Error> {
let entities_count: u16 = bincode::deserialize_from(&mut *cursor)?;
for _ in 0..entities_count {
Expand All @@ -167,7 +181,7 @@ fn deserialize_despawns(
.remove_by_server(server_entity)
.and_then(|entity| world.get_entity_mut(entity))
{
client_entity.despawn_recursive();
(replication_rules.despawn_fn)(client_entity, tick);
}
}

Expand Down
34 changes: 25 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ app.add_plugins(MinimalPlugins)
.add_plugins(ReplicationPlugins);
```

This group contains necessary replication stuff and setups server and client
plugins to let you host and join games from the same application. If you
planning to separate client and server you can use
This group contains necessary replication stuff and sets up the server and client
plugins to let you host and join games from the same application.

If you are planning to separate client and server you can use
`disable()` to disable [`ClientPlugin`] or
[`ServerPlugin`]. You can also configure how often updates are sent from
server to clients with [`ServerPlugin`]'s [`TickPolicy`].:
Expand Down Expand Up @@ -94,11 +95,11 @@ you can use [`AppReplicationExt::replicate_with`]:
```rust
# use std::io::Cursor;
# use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap};
# use bevy_replicon::{prelude::*, renet::Bytes};
# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replicaiton_rules};
# use serde::{Deserialize, Serialize};
# let mut app = App::new();
# app.add_plugins(ReplicationPlugins);
app.replicate_with::<Transform>(serialize_transform, deserialize_transform);
app.replicate_with::<Transform>(serialize_transform, deserialize_transform, replicaiton_rules::remove_component);

/// Serializes only translation.
fn serialize_transform(
Expand All @@ -115,6 +116,7 @@ fn deserialize_transform(
entity: &mut EntityMut,
_entity_map: &mut NetworkEntityMap,
cursor: &mut Cursor<Bytes>,
_tick: NetworkTick,
) -> Result<(), bincode::Error> {
let translation: Vec3 = bincode::deserialize_from(cursor)?;
entity.insert(Transform::from_translation(translation));
Expand All @@ -131,6 +133,18 @@ will be replicated.
If you need to disable replication for specific component for specific entity,
you can insert [`Ignored<T>`] component and replication will be skipped for `T`.

### NetworkTick, and fixed timestep games.

The [`ServerPlugin`] sends replication data in `PostUpdate` any time the [`NetworkTick`] resource
changes. By default, its incremented in `PostUpdate` per the [`TickPolicy`].
RJ marked this conversation as resolved.
Show resolved Hide resolved

If you set [`TickPolicy::Manual`], you can increment [`NetworkTick`] at the start of your
`FixedTimestep` game loop. This value can represent your simulation step, and is made available
to the client in the custom deserialization, despawn and component removal functions.

One use for this is rollback networking: you may want to rollback time and apply the update
for the tick frame, which is in the past, then resimulate.

### "Blueprints" pattern

The idea was borrowed from [iyes_scene_tools](https://github.com/IyesGames/iyes_scene_tools#blueprints-pattern).
Expand All @@ -147,11 +161,11 @@ your initialization systems to [`ClientSet::Receive`]:
```rust
# use std::io::Cursor;
# use bevy::{ecs::world::EntityMut, prelude::*, ptr::Ptr, utils::HashMap};
# use bevy_replicon::{prelude::*, renet::Bytes};
# use bevy_replicon::{prelude::*, renet::Bytes, replicon_core::replicaiton_rules};
# use serde::{Deserialize, Serialize};
# let mut app = App::new();
# app.add_plugins(ReplicationPlugins);
app.replicate_with::<Transform>(serialize_transform, deserialize_transform)
app.replicate_with::<Transform>(serialize_transform, deserialize_transform, replicaiton_rules::remove_component)
.replicate::<Player>()
.add_systems(PreUpdate, player_init_system.after(ClientSet::Receive));

Expand All @@ -175,7 +189,7 @@ fn player_init_system(
#[derive(Component, Deserialize, Serialize)]
struct Player;
# fn serialize_transform(_: Ptr, _: &mut Cursor<Vec<u8>>) -> Result<(), bincode::Error> { unimplemented!() }
# fn deserialize_transform(_: &mut EntityMut, _: &mut NetworkEntityMap, _: &mut Cursor<Bytes>) -> Result<(), bincode::Error> { unimplemented!() }
# fn deserialize_transform(_: &mut EntityMut, _: &mut NetworkEntityMap, _: &mut Cursor<Bytes>, _: NetworkTick) -> Result<(), bincode::Error> { unimplemented!() }
```

This pairs nicely with server state serialization and keeps saves clean.
Expand Down Expand Up @@ -380,10 +394,11 @@ pub mod prelude {
replicon_core::{
replication_rules::{
AppReplicationExt, Ignored, MapNetworkEntities, Mapper, Replication,
ReplicationRules,
},
NetworkChannels, NetworkTick, RepliconCorePlugin,
},
server::{has_authority, ServerPlugin, ServerSet, ServerTicks, TickPolicy, SERVER_ID},
server::{has_authority, AckedTicks, ServerPlugin, ServerSet, TickPolicy, SERVER_ID},
ReplicationPlugins,
};
}
Expand All @@ -393,6 +408,7 @@ pub use bevy_renet::*;
pub use bincode;
use prelude::*;

/// Plugin Group for all replicon plugins.
pub struct ReplicationPlugins;

impl PluginGroup for ReplicationPlugins {
Expand Down
5 changes: 3 additions & 2 deletions src/replicon_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ fn channel_configs(channels: &[SendType]) -> Vec<ChannelConfig> {
channel_configs
}

/// Corresponds to the number of server update.
/// A tick that increments each time we need the server to compute and send an update.
/// This is mapped to the bevy Tick in [`crate::server::AckedTicks`].
///
/// See also [`crate::server::TickPolicy`].
#[derive(Clone, Copy, Default, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Resource, Serialize)]
pub struct NetworkTick(u32);

impl NetworkTick {
Expand Down
69 changes: 53 additions & 16 deletions src/replicon_core/replication_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bevy_renet::renet::Bytes;
use bincode::{DefaultOptions, Options};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use super::NetworkTick;
use crate::client::{ClientMapper, NetworkEntityMap};

pub trait AppReplicationExt {
Expand All @@ -28,11 +29,12 @@ pub trait AppReplicationExt {
where
C: Component + Serialize + DeserializeOwned + MapNetworkEntities;

/// Same as [`Self::replicate`], but uses the specified functions for serialization and deserialization.
/// Same as [`Self::replicate`], but uses the specified functions for serialization, deserialization and removal.
Shatur marked this conversation as resolved.
Show resolved Hide resolved
fn replicate_with<C>(
&mut self,
serialize: SerializeFn,
deserialize: DeserializeFn,
remove: RemoveComponentFn,
) -> &mut Self
where
C: Component;
Expand All @@ -43,17 +45,30 @@ impl AppReplicationExt for App {
where
C: Component + Serialize + DeserializeOwned,
{
self.replicate_with::<C>(serialize_component::<C>, deserialize_component::<C>)
self.replicate_with::<C>(
serialize_component::<C>,
deserialize_component::<C>,
remove_component::<C>,
)
}

fn replicate_mapped<C>(&mut self) -> &mut Self
where
C: Component + Serialize + DeserializeOwned + MapNetworkEntities,
{
self.replicate_with::<C>(serialize_component::<C>, deserialize_mapped_component::<C>)
self.replicate_with::<C>(
serialize_component::<C>,
deserialize_mapped_component::<C>,
remove_component::<C>,
)
}

fn replicate_with<C>(&mut self, serialize: SerializeFn, deserialize: DeserializeFn) -> &mut Self
fn replicate_with<C>(
&mut self,
serialize: SerializeFn,
deserialize: DeserializeFn,
remove: RemoveComponentFn,
) -> &mut Self
where
C: Component,
{
Expand All @@ -63,7 +78,7 @@ impl AppReplicationExt for App {
ignored_id,
serialize,
deserialize,
remove: remove_component::<C>,
remove,
};

let mut replication_rules = self.world.resource_mut::<ReplicationRules>();
Expand All @@ -77,10 +92,14 @@ impl AppReplicationExt for App {
}

/// Stores information about which components will be serialized and how.
///
/// See also [`replicate_into_scene`].
#[derive(Resource)]
pub(crate) struct ReplicationRules {
pub struct ReplicationRules {
/// Custom function to handle entity despawning.
///
/// By default uses [`despawn_recursive`].
/// May need to intercept the despawn and do it differently.
Shatur marked this conversation as resolved.
Show resolved Hide resolved
pub despawn_fn: EntityDespawnFn,

/// Maps component IDs to their replication IDs.
ids: HashMap<ComponentId, ReplicationId>,

Expand Down Expand Up @@ -133,6 +152,7 @@ impl FromWorld for ReplicationRules {
infos: Default::default(),
ids: Default::default(),
marker_id: world.init_component::<Replication>(),
despawn_fn: despawn_recursive,
}
}
}
Expand All @@ -141,8 +161,18 @@ impl FromWorld for ReplicationRules {
pub type SerializeFn = fn(Ptr, &mut Cursor<Vec<u8>>) -> Result<(), bincode::Error>;

/// Signature of component deserialization functions.
pub type DeserializeFn =
fn(&mut EntityMut, &mut NetworkEntityMap, &mut Cursor<Bytes>) -> Result<(), bincode::Error>;
pub type DeserializeFn = fn(
&mut EntityMut,
&mut NetworkEntityMap,
&mut Cursor<Bytes>,
NetworkTick,
) -> Result<(), bincode::Error>;

/// Signature of component removal functions.
pub type RemoveComponentFn = fn(&mut EntityMut, NetworkTick);

/// Signature of the entity despawn function.
pub type EntityDespawnFn = fn(EntityMut, NetworkTick);

/// Stores meta information about replicated component.
pub(crate) struct ReplicationInfo {
Expand All @@ -156,7 +186,7 @@ pub(crate) struct ReplicationInfo {
pub(crate) deserialize: DeserializeFn,

/// Function that removes specific component from [`EntityMut`].
pub(crate) remove: fn(&mut EntityMut),
pub(crate) remove: RemoveComponentFn,
}

/// Marks entity for replication.
Expand Down Expand Up @@ -192,7 +222,7 @@ pub trait Mapper {
}

/// Default serialization function.
fn serialize_component<C: Component + Serialize>(
pub fn serialize_component<C: Component + Serialize>(
component: Ptr,
cursor: &mut Cursor<Vec<u8>>,
) -> Result<(), bincode::Error> {
Expand All @@ -202,10 +232,11 @@ fn serialize_component<C: Component + Serialize>(
}

/// Default deserialization function.
fn deserialize_component<C: Component + DeserializeOwned>(
pub fn deserialize_component<C: Component + DeserializeOwned>(
entity: &mut EntityMut,
_entity_map: &mut NetworkEntityMap,
cursor: &mut Cursor<Bytes>,
_tick: NetworkTick,
) -> Result<(), bincode::Error> {
let component: C = DefaultOptions::new().deserialize_from(cursor)?;
entity.insert(component);
Expand All @@ -214,10 +245,11 @@ fn deserialize_component<C: Component + DeserializeOwned>(
}

/// Like [`deserialize_component`], but also maps entities before insertion.
fn deserialize_mapped_component<C: Component + DeserializeOwned + MapNetworkEntities>(
pub fn deserialize_mapped_component<C: Component + DeserializeOwned + MapNetworkEntities>(
entity: &mut EntityMut,
entity_map: &mut NetworkEntityMap,
cursor: &mut Cursor<Bytes>,
_tick: NetworkTick,
) -> Result<(), bincode::Error> {
let mut component: C = DefaultOptions::new().deserialize_from(cursor)?;

Expand All @@ -230,7 +262,12 @@ fn deserialize_mapped_component<C: Component + DeserializeOwned + MapNetworkEnti
Ok(())
}

/// Removes specified component from entity.
fn remove_component<C: Component>(entity: &mut EntityMut) {
/// Default component removal function.
pub fn remove_component<C: Component>(entity: &mut EntityMut, _tick: NetworkTick) {
entity.remove::<C>();
}

/// Default entity despawn function.
pub fn despawn_recursive(entity: EntityMut, _tick: NetworkTick) {
entity.despawn_recursive();
}
Loading
Loading