From 2cb0e580eb912cada64e9f13b0e0ac302e9e2c70 Mon Sep 17 00:00:00 2001 From: Nick Eaton Date: Tue, 3 Sep 2024 20:25:58 -0400 Subject: [PATCH] Rollback time resource during rollback --- lightyear/src/client/prediction/plugin.rs | 13 ++- .../client/prediction/predicted_history.rs | 11 ++- lightyear/src/client/prediction/rollback.rs | 80 ++++++++++++++++++- lightyear/src/protocol/component.rs | 3 +- 4 files changed, 98 insertions(+), 9 deletions(-) diff --git a/lightyear/src/client/prediction/plugin.rs b/lightyear/src/client/prediction/plugin.rs index d401daf6..8976903d 100644 --- a/lightyear/src/client/prediction/plugin.rs +++ b/lightyear/src/client/prediction/plugin.rs @@ -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, @@ -200,7 +200,18 @@ pub fn add_non_networked_rollback_systems(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` 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` 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(app: &mut App) { + app.insert_resource(ResourceHistory::::default()); app.add_systems( PreUpdate, prepare_rollback_resource::.in_set(PredictionSet::PrepareRollback), diff --git a/lightyear/src/client/prediction/predicted_history.rs b/lightyear/src/client/prediction/predicted_history.rs index 1fbfe372..2cb91da3 100644 --- a/lightyear/src/client/prediction/predicted_history.rs +++ b/lightyear/src/client/prediction/predicted_history.rs @@ -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 @@ -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 diff --git a/lightyear/src/client/prediction/rollback.rs b/lightyear/src/client/prediction/rollback.rs index 201f276a..3082b05f 100644 --- a/lightyear/src/client/prediction/rollback.rs +++ b/lightyear/src/client/prediction/rollback.rs @@ -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}; @@ -568,6 +569,38 @@ pub(crate) fn prepare_rollback_resource( 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, num_rollback_ticks: i16) -> Time { + let mut rollback_fixed_time = Time::::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::().unwrap(); let rollback = world.get_resource::().unwrap(); @@ -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::