Skip to content

Commit

Permalink
Rollback time resource during rollback
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick Eaton committed Sep 5, 2024
1 parent 2cd8cf8 commit 2cb0e58
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 9 deletions.
13 changes: 12 additions & 1 deletion lightyear/src/client/prediction/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::shared::sets::{ClientMarker, InternalMainSet};

use super::pre_prediction::PrePredictionPlugin;
use super::predicted_history::{add_component_history, apply_confirmed_update};
use super::resource_history::update_resource_history;
use super::resource_history::{update_resource_history, ResourceHistory};
use super::rollback::{
check_rollback, increment_rollback_tick, prepare_rollback, prepare_rollback_non_networked,
prepare_rollback_prespawn, prepare_rollback_resource, run_rollback, Rollback, RollbackState,
Expand Down Expand Up @@ -200,7 +200,18 @@ pub fn add_non_networked_rollback_systems<C: Component + PartialEq + Clone>(app:
);
}

/// Enables rollbacking a resource. As a rule of thumb, only use on resources
/// that are only modified by systems in the `FixedMain` schedule. This is
/// because rollbacks only run the `FixedMain` schedule. For example, the
/// `Time<Fixed>` resource is modified by
/// `bevy_time::fixed::run_fixed_main_schedule()` which is run outside of the
/// `FixedMain` schedule and so it should not be used in this function.
///
/// As a side note, the `Time<Fixed>` resource is already rollbacked internally
/// by lightyear so that it can be used accurately within systems within the
/// `FixedMain` schedule during a rollback.
pub fn add_resource_rollback_systems<R: Resource + Clone>(app: &mut App) {
app.insert_resource(ResourceHistory::<R>::default());
app.add_systems(
PreUpdate,
prepare_rollback_resource::<R>.in_set(PredictionSet::PrepareRollback),
Expand Down
11 changes: 9 additions & 2 deletions lightyear/src/client/prediction/predicted_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,10 @@ mod tests {
let confirmed = stepper
.client_app
.world_mut()
.spawn(Confirmed::default())
.spawn(Confirmed {
tick,
..Default::default()
})
.id();
let predicted = stepper
.client_app
Expand Down Expand Up @@ -548,10 +551,14 @@ mod tests {
let mut stepper = BevyStepper::default();

// add predicted, component
let tick = stepper.client_tick();
let confirmed = stepper
.client_app
.world_mut()
.spawn(Confirmed::default())
.spawn(Confirmed {
tick,
..Default::default()
})
.id();
let predicted = stepper
.client_app
Expand Down
80 changes: 76 additions & 4 deletions lightyear/src/client/prediction/rollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy::prelude::{
Resource, With, Without, World,
};
use bevy::reflect::Reflect;
use bevy::time::{Fixed, Time};
use parking_lot::RwLock;
use tracing::{debug, error, trace, trace_span};

Expand Down Expand Up @@ -568,6 +569,38 @@ pub(crate) fn prepare_rollback_resource<R: Resource + Clone>(
history.clear();
}

/// Return a fixed time that represents rollbacking `current_fixed_time` by
/// `num_rollback_ticks` ticks. The returned fixed time's overstep is zero.
///
/// This function assumes that `current_fixed_time`'s timestep remained the
/// same for the past `num_rollback_ticks` ticks.
fn rollback_fixed_time(current_fixed_time: &Time<Fixed>, num_rollback_ticks: i16) -> Time<Fixed> {
let mut rollback_fixed_time = Time::<Fixed>::from_duration(current_fixed_time.timestep());
if num_rollback_ticks <= 0 {
debug!("Cannot rollback fixed time by {} ticks", num_rollback_ticks);
return rollback_fixed_time;
}
// Fixed time's elapsed time's is set to the fixed time's delta before any
// fixed system has run in an app, see
// `bevy_time::fixed::run_fixed_main_schedule()`. If elapsed time is zero
// that means no tick has run.
if current_fixed_time.elapsed() < current_fixed_time.timestep() {
error!("Current elapsed fixed time is less than the fixed timestep");
return rollback_fixed_time;
}

// Difference between the current time and the time of the first tick of
// the rollback.
let rollback_time_offset = (num_rollback_ticks - 1) as u32 * rollback_fixed_time.timestep();

let rollback_elapsed_time = current_fixed_time.elapsed() - rollback_time_offset;
rollback_fixed_time.advance_to(rollback_elapsed_time - rollback_fixed_time.timestep());
// This sets the fixed time's delta to the correct amount.
rollback_fixed_time.advance_by(rollback_fixed_time.timestep());

rollback_fixed_time
}

pub(crate) fn run_rollback(world: &mut World) {
let tick_manager = world.get_resource::<TickManager>().unwrap();
let rollback = world.get_resource::<Rollback>().unwrap();
Expand All @@ -590,13 +623,44 @@ pub(crate) fn run_rollback(world: &mut World) {
current_rollback_tick, current_tick
);

// run the physics fixed update schedule (which should contain ALL predicted/rollback components)
// Keep track of the generic time resource so it can be restored after the
// rollback.
let time_resource = *world.resource::<Time>();

// Rollback the fixed time resource in preparation for the rollback.
let current_fixed_time = *world.resource::<Time<Fixed>>();
*world.resource_mut::<Time<Fixed>>() =
rollback_fixed_time(&current_fixed_time, num_rollback_ticks);

// Run the fixed update schedule (which should contain ALL
// predicted/rollback components and resources). This is similar to what
// `bevy_time::fixed::run_fixed_main_schedule()` does
for i in 0..num_rollback_ticks {
debug!("Rollback tick: {:?}", current_rollback_tick + i);

// Set the rollback tick's generic time resource to the fixed time
// resource that was just advanced.
*world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic();

// TODO: if we are in rollback, there are some FixedUpdate systems that we don't want to re-run ??
// for example we only want to run the physics on non-confirmed entities
world.run_schedule(FixedMain)
world.run_schedule(FixedMain);

// Manually advanced fixed time because `run_schedule(FixedMain)` does
// not.
let timestep = world.resource_mut::<Time<Fixed>>().timestep();
world.resource_mut::<Time<Fixed>>().advance_by(timestep);
}

// Restore the fixed time resource.
// `current_fixed_time` and the fixed time resource in use (e.g. the
// rollback fixed time) should be the same after the rollback except that
// `current_fixed_time` may have an overstep. Use `current_fixed_time` so
// its overstep isn't lost.
*world.resource_mut::<Time<Fixed>>() = current_fixed_time;

// Restore the generic time resource.
*world.resource_mut::<Time>() = time_resource;
debug!("Finished rollback. Current tick: {:?}", current_tick);

let mut metrics = world.get_resource_mut::<PredictionMetrics>().unwrap();
Expand Down Expand Up @@ -665,10 +729,14 @@ mod unit_tests {
let mut stepper = BevyStepper::default();

// add predicted/confirmed entities
let tick = stepper.client_tick();
let confirmed = stepper
.client_app
.world_mut()
.spawn(Confirmed::default())
.spawn(Confirmed {
tick,
..Default::default()
})
.id();
let predicted = stepper
.client_app
Expand Down Expand Up @@ -830,10 +898,14 @@ mod integration_tests {
.client_app
.add_systems(FixedUpdate, increment_component);
// add predicted/confirmed entities
let tick = stepper.client_tick();
let confirmed = stepper
.client_app
.world_mut()
.spawn(Confirmed::default())
.spawn(Confirmed {
tick,
..Default::default()
})
.id();
let predicted = stepper
.client_app
Expand Down
3 changes: 1 addition & 2 deletions lightyear/src/protocol/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::client::interpolation::{add_interpolation_systems, add_prepare_interp
use crate::client::prediction::plugin::{
add_non_networked_rollback_systems, add_prediction_systems, add_resource_rollback_systems,
};
use crate::client::prediction::resource_history::ResourceHistory;
use crate::prelude::client::SyncComponent;
use crate::prelude::server::ServerConfig;
use crate::prelude::{ChannelDirection, Message, Tick};
Expand Down Expand Up @@ -1108,10 +1107,10 @@ impl AppComponentExt for App {
}
}

/// Do not use `Time<Fixed>` for `R`. `Time<Fixed>` is already rollbacked.
fn add_resource_rollback<R: Resource + Clone + Debug>(&mut self) {
let is_client = self.world().get_resource::<ClientConfig>().is_some();
if is_client {
self.insert_resource(ResourceHistory::<R>::default());
add_resource_rollback_systems::<R>(self);
}
}
Expand Down

0 comments on commit 2cb0e58

Please sign in to comment.