Skip to main content

lightyear_core/
timeline.rs

1use crate::prelude::Tick;
2use crate::tick::TickDuration;
3use crate::time::{Overstep, TickDelta, TickInstant};
4use bevy_app::{App, FixedFirst, Plugin};
5use bevy_derive::{Deref, DerefMut};
6use bevy_ecs::component::{Component, Mutable};
7use bevy_ecs::entity::Entity;
8use bevy_ecs::event::{EntityEvent, Event};
9use bevy_ecs::prelude::{On, Resource};
10use bevy_ecs::query::With;
11use bevy_ecs::system::{Query, ResMut};
12use bevy_reflect::Reflect;
13use bevy_time::{Fixed, Time};
14use core::ops::{Deref, DerefMut};
15use core::time::Duration;
16#[allow(unused_imports)]
17use tracing::trace;
18
19/// A timeline defines an independent progression of time.
20///
21/// A given entity can be associated with multiple timelines.
22/// Each Timeline is associated with a [`TimelineConfig`] component that is used to
23/// configure the timeline.
24#[derive(Default, Debug, Clone, Reflect)]
25pub struct Timeline<T: TimelineConfig> {
26    pub context: T::Context,
27    pub now: TickInstant,
28    #[reflect(ignore)]
29    pub marker: core::marker::PhantomData<T>,
30}
31
32/// Configuration for a [`Timeline`].
33///
34/// The user should only modify the configuration.
35pub trait TimelineConfig: Component + Send + Sync + Sized + 'static {
36    /// Contextual data associated with this timeline configuration; used by the timeline's internals
37    type Context;
38
39    type Timeline: NetworkTimeline + Default;
40}
41
42// TODO: should we get rid of this trait and just use the Timeline<T> struct?
43//  maybe a trait gives us more options in the future
44pub trait NetworkTimeline: Component<Mutability = Mutable> {
45    type Config: TimelineConfig;
46    const PAUSED_DURING_ROLLBACK: bool = true;
47
48    /// Estimate of the current time in the [`Timeline`]
49    fn now(&self) -> TickInstant;
50
51    fn tick(&self) -> Tick;
52
53    fn overstep(&self) -> Overstep;
54
55    fn set_now(&mut self, now: TickInstant);
56
57    fn apply_delta(&mut self, delta: TickDelta);
58
59    fn apply_duration(&mut self, duration: Duration, tick_duration: Duration) {
60        self.apply_delta(TickDelta::from_duration(duration, tick_duration));
61    }
62}
63
64impl<C: TimelineConfig, T: Component<Mutability = Mutable> + DerefMut<Target = Timeline<C>>>
65    NetworkTimeline for T
66{
67    type Config = C;
68
69    /// Estimate of the current time in the [`Timeline`]
70    fn now(&self) -> TickInstant {
71        self.now
72    }
73
74    fn tick(&self) -> Tick {
75        self.now().tick()
76    }
77
78    fn overstep(&self) -> Overstep {
79        self.now().overstep()
80    }
81
82    fn set_now(&mut self, now: TickInstant) {
83        self.now = now;
84    }
85
86    fn apply_delta(&mut self, delta: TickDelta) {
87        self.now = self.now + delta;
88    }
89}
90
91impl<T: TimelineConfig> Deref for Timeline<T> {
92    type Target = T::Context;
93
94    fn deref(&self) -> &Self::Target {
95        &self.context
96    }
97}
98
99impl<T: TimelineConfig> DerefMut for Timeline<T> {
100    fn deref_mut(&mut self) -> &mut Self::Target {
101        &mut self.context
102    }
103}
104
105/// The local timeline that matches [`Time<Virtual>`]
106/// - the Tick is incremented every FixedUpdate (including during rollback)
107/// - the overstep is set by the overstep of [`Time<Fixed>`]
108#[derive(Resource, Deref, DerefMut, Default, Clone, Reflect)]
109pub struct LocalTimeline {
110    tick: Tick,
111}
112
113impl LocalTimeline {
114    /// Get the current tick
115    pub fn tick(&self) -> Tick {
116        self.tick
117    }
118
119    /// Increment the LocalTimeline by `delta`
120    pub fn apply_delta(&mut self, delta: i32) {
121        self.tick = self.tick + delta;
122    }
123}
124
125/// Increment the local tick at each FixedUpdate
126pub(crate) fn increment_local_tick(mut timeline: ResMut<LocalTimeline>) {
127    timeline.tick += 1;
128    trace!(
129        target: "lightyear_debug::timeline",
130        kind = "local_tick",
131        sample_point = "FixedFirst",
132        schedule = "FixedFirst",
133        local_tick = timeline.tick.0,
134        "local timeline tick advanced"
135    );
136}
137
138pub struct NetworkTimelinePlugin<T> {
139    pub(crate) _marker: core::marker::PhantomData<T>,
140}
141
142impl<T> Default for NetworkTimelinePlugin<T> {
143    fn default() -> Self {
144        Self {
145            _marker: core::marker::PhantomData,
146        }
147    }
148}
149
150impl<T: NetworkTimeline> Plugin for NetworkTimelinePlugin<T> {
151    fn build(&self, _: &mut App) {}
152}
153
154/// Event that can be triggered to update the tick duration.
155///
156/// If the trigger is global, it will update:
157/// - [`Time<Fixed>`]
158/// - the various Timelines
159///
160/// The event can also be triggered for a specific target to update only the components of that target.
161#[derive(Event)]
162pub struct SetTickDuration(pub Duration);
163
164pub struct TimelinePlugin {
165    pub(crate) tick_duration: Duration,
166}
167
168impl TimelinePlugin {
169    fn update_tick_duration(trigger: On<SetTickDuration>, mut time: ResMut<Time<Fixed>>) {
170        time.set_timestep(trigger.0);
171    }
172}
173
174impl Plugin for TimelinePlugin {
175    fn build(&self, app: &mut App) {
176        app.init_resource::<LocalTimeline>();
177        app.insert_resource(TickDuration(self.tick_duration));
178        app.world_mut()
179            .resource_mut::<Time<Fixed>>()
180            .set_timestep(self.tick_duration);
181        app.add_observer(Self::update_tick_duration);
182
183        app.add_systems(FixedFirst, increment_local_tick);
184    }
185
186    fn finish(&self, app: &mut App) {
187        // After timelines and PingManager are created, trigger a TickDuration event
188        app.world_mut().trigger(SetTickDuration(self.tick_duration));
189    }
190}
191
192#[derive(EntityEvent, Debug)]
193pub struct SyncEvent<T: TimelineConfig> {
194    /// Entity holding a [`Timeline`]
195    pub entity: Entity,
196    // NOTE: it's inconvenient to re-sync the Timeline from a TickInstant to another TickInstant,
197    //  so instead we will apply a delta number of ticks with no overstep (so that it's easy
198    //  to update the LocalTimeline
199    /// Delta in number of ticks to apply to the timeline
200    pub tick_delta: i32,
201    marker: core::marker::PhantomData<T>,
202}
203
204impl<T: TimelineConfig> SyncEvent<T> {
205    pub fn new(entity: Entity, tick_delta: i32) -> Self {
206        SyncEvent {
207            entity,
208            tick_delta,
209            marker: core::marker::PhantomData,
210        }
211    }
212}
213
214impl<T: TimelineConfig> Clone for SyncEvent<T> {
215    fn clone(&self) -> Self {
216        *self
217    }
218}
219
220impl<T: TimelineConfig> Copy for SyncEvent<T> {}
221
222/// Marker component inserted on the Link if we are currently in rollback
223///
224/// This is in `lightyear_core` to avoid circular dependencies. Many other plugins behave differently during rollback
225#[derive(Component, Debug, Clone, Copy)]
226pub enum Rollback {
227    /// The rollback is initiated because we have received new Confirmed state from the server
228    /// that doesn't match our prediction history.
229    FromState,
230    /// The rollback is initiated because we have received new Inputs for remote clients
231    ///
232    /// We should still check if there are state mismatches
233    FromInputs,
234}
235
236/// Run condition to check if we are in rollback
237pub fn is_in_rollback(client: Query<(), With<Rollback>>) -> bool {
238    client.single().is_ok()
239}