Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
cBournhonesque committed Jan 13, 2025
1 parent cccf8ed commit 7280e23
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 45 deletions.
4 changes: 2 additions & 2 deletions lightyear/src/client/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ pub struct Confirmed {
pub tick: Tick,
}

pub trait SyncComponent: Component + Clone + PartialEq + Message {}
impl<T> SyncComponent for T where T: Component + Clone + PartialEq + Message {}
pub trait SyncComponent: Component + Clone + PartialEq + Message + Debug {}
impl<T> SyncComponent for T where T: Component + Clone + PartialEq + Message + Debug {}

/// Function that will interpolate between two values
pub trait LerpFn<C> {
Expand Down
2 changes: 1 addition & 1 deletion lightyear/src/client/networking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ pub(crate) fn sync_update(
&connection.ping_manager,
&config.prediction,
) {
debug!("Triggering TickSync event: {tick_event:?}");
info!("Triggering TickSync event: {tick_event:?}");
commands.trigger(tick_event);
}
let relative_speed = time_manager.get_relative_speed();
Expand Down
58 changes: 35 additions & 23 deletions lightyear/src/client/prediction/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::prelude::Tick;
use bevy::prelude::{Component, Reflect, Resource};
use bevy::prelude::{ReflectComponent, ReflectResource};
use std::collections::VecDeque;
use std::fmt::Debug;
use tracing::{error, info};

/// Stores a past value in the history buffer
#[derive(Debug, PartialEq, Clone, Default, Reflect)]
Expand Down Expand Up @@ -66,7 +68,19 @@ impl<R> HistoryBuffer<R> {
}

/// Add a value to the history buffer
/// The tick must be strictly more recent than the most recent update in the buffer
pub(crate) fn add(&mut self, tick: Tick, value: Option<R>) {
if let Some(last_tick) = self.peek().map(|(tick, _)| tick) {
assert!(
tick >= *last_tick,
"Tick must be more recent than the last update in the buffer"
);
if *last_tick == tick {
error!("Adding update to history buffer for tick: {:?} but it already had a value for that tick!", tick);
// in this case, let's pop back the update to replace it with the new value
self.buffer.pop_back();
}
}
self.buffer.push_back((
tick,
match value {
Expand Down Expand Up @@ -97,8 +111,9 @@ impl<R: Clone> HistoryBuffer<R> {
/// the history will only contain the ticks where the value got updated, and otherwise
/// contains gaps. Therefore, we need to always leave a value in the history buffer so that we can
/// get the values for the future ticks.
/// (i.e. if the buffer contains values at tick 4 and 8. If we pop_until_tick(6),
/// we still need to include for tick 4 in the buffer, so that if we call pop_until_tick(7) we still have the correct value
/// (i.e. if the buffer contains values at tick 4 and 8. If we pop_until_tick(6), we cannot delete the value for tick 4
/// because we still need in case we call pop_until_tick(7). What we'll do is remove the value for tick 4 and re-insert it
/// for tick 6)
pub(crate) fn pop_until_tick(&mut self, tick: Tick) -> Option<HistoryState<R>> {
// self.buffer[partition] is the first element where the buffer_tick > tick
let partition = self
Expand Down Expand Up @@ -137,54 +152,51 @@ mod tests {
/// Test adding and removing updates to the resource history
#[test]
fn test_add_remove_history() {
let mut resource_history = HistoryBuffer::<TestValue>::default();
let mut history = HistoryBuffer::<TestValue>::default();

// check when we try to access a value when the buffer is empty
assert_eq!(resource_history.pop_until_tick(Tick(0)), None);
assert_eq!(history.pop_until_tick(Tick(0)), None);

// check when we try to access an exact tick
resource_history.add_update(Tick(1), TestValue(1.0));
resource_history.add_update(Tick(2), TestValue(2.0));
history.add_update(Tick(1), TestValue(1.0));
history.add_update(Tick(2), TestValue(2.0));
assert_eq!(
resource_history.pop_until_tick(Tick(2)),
history.pop_until_tick(Tick(2)),
Some(HistoryState::Updated(TestValue(2.0)))
);
// check that we cleared older ticks, and that the most recent value still remains
assert_eq!(resource_history.buffer.len(), 1);
assert_eq!(history.buffer.len(), 1);
assert_eq!(
resource_history.buffer,
history.buffer,
VecDeque::from(vec![(Tick(2), HistoryState::Updated(TestValue(2.0)))])
);

// check when we try to access a value in-between ticks
resource_history.add_update(Tick(4), TestValue(4.0));
history.add_update(Tick(4), TestValue(4.0));
// we retrieve the most recent value older or equal to Tick(3)
assert_eq!(
resource_history.pop_until_tick(Tick(3)),
history.pop_until_tick(Tick(3)),
Some(HistoryState::Updated(TestValue(2.0)))
);
assert_eq!(resource_history.buffer.len(), 2);
assert_eq!(history.buffer.len(), 2);
// check that the most recent value got added back to the buffer at the popped tick
assert_eq!(
resource_history.buffer,
history.buffer,
VecDeque::from(vec![
(Tick(3), HistoryState::Updated(TestValue(2.0))),
(Tick(4), HistoryState::Updated(TestValue(4.0)))
])
);

// check that nothing happens when we try to access a value before any ticks
assert_eq!(resource_history.pop_until_tick(Tick(0)), None);
assert_eq!(resource_history.buffer.len(), 2);
assert_eq!(history.pop_until_tick(Tick(0)), None);
assert_eq!(history.buffer.len(), 2);

resource_history.add_remove(Tick(5));
assert_eq!(resource_history.buffer.len(), 3);
assert_eq!(
resource_history.peek(),
Some(&(Tick(5), HistoryState::Removed))
);
history.add_remove(Tick(5));
assert_eq!(history.buffer.len(), 3);
assert_eq!(history.peek(), Some(&(Tick(5), HistoryState::Removed)));

resource_history.clear();
assert_eq!(resource_history.buffer.len(), 0);
history.clear();
assert_eq!(history.buffer.len(), 0);
}
}
27 changes: 17 additions & 10 deletions lightyear/src/client/prediction/predicted_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy::prelude::{
Added, Commands, Component, DetectChanges, Entity, OnRemove, Or, Query, Ref, Res, Trigger,
With, Without,
};
use tracing::{debug, trace};
use tracing::{debug, info, trace};

use crate::client::components::{ComponentSyncMode, Confirmed, SyncComponent};
use crate::client::prediction::history::HistoryBuffer;
Expand Down Expand Up @@ -96,11 +96,11 @@ pub(crate) fn add_component_history<C: SyncComponent>(
match component_registry.prediction_mode::<C>() {
ComponentSyncMode::Full => {
debug!("Adding history for {:?}", std::any::type_name::<C>());
// insert history, it will be quickly filled by a rollback (since it starts empty before the current client tick)
// insert history, no need to add any value to it because we run the UpdateHistory system set after the SpawnHistory
// it will be quickly filled by a rollback (since it starts empty before the current client tick)
// or will it? because the component just got spawned anyway..
// TODO: then there's no need to add the component here, since it's going to get added during rollback anyway?
let mut history = PredictionHistory::<C>::default();
history.add_update(tick, confirmed_component.deref().clone());
let history = PredictionHistory::<C>::default();
predicted_entity_mut.insert((new_component, history));
}
ComponentSyncMode::Simple => {
Expand Down Expand Up @@ -135,7 +135,7 @@ pub(crate) fn add_prespawned_component_history<C: SyncComponent>(
mut commands: Commands,
tick_manager: Res<TickManager>,
prespawned_query: Query<
(Entity, Option<Ref<C>>),
(Entity, Ref<C>),
(
Without<PredictionHistory<C>>,
Without<Confirmed>,
Expand All @@ -144,13 +144,20 @@ pub(crate) fn add_prespawned_component_history<C: SyncComponent>(
),
>,
) {
let tick = tick_manager.tick();
// add component history for pre-spawned entities right away
for (predicted_entity, predicted_component) in prespawned_query.iter() {
trace!(
?tick,
"Potentially adding prediction history for component {:?} for pre-spawned entity {:?}",
std::any::type_name::<C>(),
predicted_entity
);
add_history::<C>(
component_registry.as_ref(),
tick_manager.tick(),
tick,
predicted_entity,
&predicted_component,
&Some(predicted_component),
&mut commands,
);
}
Expand All @@ -170,9 +177,9 @@ fn add_history<C: SyncComponent>(
// component got added on predicted side, add history
if predicted_component.is_added() {
debug!(?kind, ?tick, ?predicted_entity, "Adding prediction history");
// insert history, it will be quickly filled by a rollback (since it starts empty before the current client tick)
let mut history = PredictionHistory::<C>::default();
history.add_update(tick, predicted_component.deref().clone());
// insert history component
// no need to add any value to it because we run the UpdateHistory system set after the SpawnHistory
let history = PredictionHistory::<C>::default();
commands.entity(predicted_entity).insert(history);
}
}
Expand Down
15 changes: 8 additions & 7 deletions lightyear/src/client/prediction/rollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub(crate) fn check_rollback<C: SyncComponent>(
// info!(
// ?tick,
// ?current_tick,
// ?p,
// "History before popping until tick. {:?}",
// predicted_history
// );
Expand All @@ -210,10 +211,10 @@ pub(crate) fn check_rollback<C: SyncComponent>(
// confirm exist. rollback if history value is different
Some(c) => history_value.map_or_else(
|| {
// info!(
// ?tick, ?current_tick,
// "Component {} present on confirmed but missing on predicted! History: {:?}", std::any::type_name::<C>(), predicted_history
// );
info!(
?tick, ?current_tick, ?p,
"Component {} present on confirmed but missing on predicted! History: {:?}", std::any::type_name::<C>(), predicted_history
);
#[cfg(feature = "metrics")]
metrics::counter!(format!(
"prediction::rollbacks::causes::{}::missing_on_predicted",
Expand Down Expand Up @@ -248,10 +249,10 @@ pub(crate) fn check_rollback<C: SyncComponent>(
),
};
if should_rollback {
debug!(
info!(
?predicted_exist, ?confirmed_exist,
"Rollback check: mismatch for component between predicted and confirmed {:?} on tick {:?} for component {:?}. Current tick: {:?}",
confirmed_entity, tick, kind, current_tick
"Rollback check: mismatch for component between predicted {:?} and confirmed {:?} on tick {:?} for component {:?}. Current tick: {:?}",
p, confirmed_entity, tick, kind, current_tick
);
// in `prepare_rollback`, we will reset the state to what the server sends us for the confirmed.tick
// The server sends the packet in PostUpdate after the confirmed.tick is done.
Expand Down
6 changes: 5 additions & 1 deletion lightyear/src/utils/avian2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ impl Plugin for Avian2dPlugin {
// }
app.configure_sets(
FixedPostUpdate,
// run physics before updating the prediction history
(
// run physics before spawning the prediction history for prespawned entities
// we want all avian-added components (Rotation, etc.) to be inserted before we try
// to spawn the history, so that the history is spawned at the correct time for all components
PredictionSet::SpawnHistory,
// run physics before updating the prediction history
PredictionSet::UpdateHistory,
PredictionSet::IncrementRollbackTick,
InterpolationSet::UpdateVisualInterpolationState,
Expand Down
6 changes: 5 additions & 1 deletion lightyear/src/utils/avian3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ impl Plugin for Avian3dPlugin {
// }
app.configure_sets(
FixedPostUpdate,
// run physics before updating the prediction history
(
// run physics before spawning the prediction history for prespawned entities
// we want all avian-added components (Rotation, etc.) to be inserted before we try
// to spawn the history, so that the history is spawned at the correct time for all components
PredictionSet::SpawnHistory,
// run physics before updating the prediction history
PredictionSet::UpdateHistory,
PredictionSet::IncrementRollbackTick,
InterpolationSet::UpdateVisualInterpolationState,
Expand Down

0 comments on commit 7280e23

Please sign in to comment.