nimble_rectify/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/nimble-rust/nimble
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod prelude;
6
7use err_rs::{ErrorLevel, ErrorLevelProvider};
8use log::trace;
9use nimble_assent::{Assent, AssentCallback, UpdateState};
10use nimble_seer::{Seer, SeerCallback, SeerError};
11use std::fmt::{Debug, Display};
12use tick_id::TickId;
13use tick_queue::QueueError;
14
15#[derive(Debug)]
16pub enum RectifyError {
17    WrongTickId {
18        expected: TickId,
19        encountered: TickId,
20    },
21    SeerError(SeerError),
22    QueueError(QueueError),
23}
24
25impl From<SeerError> for RectifyError {
26    fn from(value: SeerError) -> Self {
27        Self::SeerError(value)
28    }
29}
30
31impl From<QueueError> for RectifyError {
32    fn from(value: QueueError) -> Self {
33        Self::QueueError(value)
34    }
35}
36
37impl ErrorLevelProvider for RectifyError {
38    fn error_level(&self) -> ErrorLevel {
39        match self {
40            Self::WrongTickId { .. } | Self::QueueError(_) => ErrorLevel::Critical,
41            Self::SeerError(err) => err.error_level(),
42        }
43    }
44}
45
46/// A callback trait that allows a game to handle the event when the authoritative state
47pub trait RectifyCallback {
48    fn on_copy_from_authoritative(&mut self);
49}
50
51pub trait RectifyCallbacks<StepT>:
52    AssentCallback<StepT> + SeerCallback<StepT> + RectifyCallback
53{
54}
55
56impl<T, StepT> RectifyCallbacks<StepT> for T where
57    T: AssentCallback<StepT> + SeerCallback<StepT> + RectifyCallback
58{
59}
60
61/// The `Rectify` struct coordinates between the [`Assent`] and [`Seer`] components, managing
62/// authoritative and predicted game states.
63#[derive(Debug)]
64pub struct Rectify<Game: RectifyCallbacks<StepT>, StepT: Clone + Debug> {
65    assent: Assent<Game, StepT>,
66    seer: Seer<Game, StepT>,
67    settings: Settings,
68}
69
70impl<Game: RectifyCallbacks<StepT>, StepT: Clone + Debug + Display> Default
71    for Rectify<Game, StepT>
72{
73    fn default() -> Self {
74        Self::new(Settings::default())
75    }
76}
77
78#[derive(Default, Debug, Copy, Clone)]
79pub struct Settings {
80    pub assent: nimble_assent::Settings,
81    pub seer: nimble_seer::Settings,
82}
83
84impl<Game: RectifyCallbacks<StepT>, StepT: Clone + Debug + Display> Rectify<Game, StepT> {
85    /// Creates a new `Rectify` instance, initializing both [`Assent`] and [`Seer`] components.
86    ///
87    /// # Returns
88    ///
89    /// A new `Rectify` instance.
90    #[must_use]
91    pub fn new(settings: Settings) -> Self {
92        let assent = Assent::new(settings.assent);
93        let seer = Seer::new(settings.seer);
94
95        Self {
96            assent,
97            seer,
98            settings,
99        }
100    }
101
102    #[must_use]
103    pub const fn seer(&self) -> &Seer<Game, StepT> {
104        &self.seer
105    }
106
107    #[must_use]
108    pub const fn settings(&self) -> Settings {
109        self.settings
110    }
111
112    #[must_use]
113    pub const fn assent(&self) -> &Assent<Game, StepT> {
114        &self.assent
115    }
116
117    /// Pushes a predicted step into the [`Seer`] component.
118    ///
119    /// # Arguments
120    ///
121    /// * `step` - The predicted step to be pushed.
122    ///
123    /// # Errors
124    ///
125    /// `RectifyError` on error // TODO:
126    pub fn push_predicted(&mut self, tick_id: TickId, step: StepT) -> Result<(), RectifyError> {
127        if let Some(end_tick_id) = self.assent.end_tick_id() {
128            self.seer.received_authoritative(end_tick_id);
129        }
130        trace!("added predicted step {}", &step);
131        self.seer.push(tick_id, step)?;
132        Ok(())
133    }
134
135    #[must_use]
136    pub const fn waiting_for_authoritative_tick_id(&self) -> TickId {
137        self.assent.next_expected_tick_id()
138    }
139
140    ///
141    /// # Errors
142    ///
143    /// `RectifyError` on error // TODO:
144    pub fn push_authoritatives_with_check(
145        &mut self,
146        step_for_tick_id: TickId,
147        steps: &[StepT],
148    ) -> Result<(), RectifyError> {
149        let mut current_tick = step_for_tick_id;
150        for step in steps {
151            self.push_authoritative_with_check(current_tick, step.clone())?;
152            current_tick = TickId(current_tick.0 + 1);
153        }
154
155        Ok(())
156    }
157    /// Pushes an authoritative step into the [`Assent`] component. This method is used to
158    /// add new steps that have been determined by the authoritative host.
159    ///
160    /// # Arguments
161    ///
162    /// * `step` - The authoritative step to be pushed.
163    ///
164    /// # Errors
165    ///
166    /// `RectifyError` on error // TODO:
167
168    #[allow(clippy::missing_panics_doc)]
169    pub fn push_authoritative_with_check(
170        &mut self,
171        step_for_tick_id: TickId,
172        step: StepT,
173    ) -> Result<(), RectifyError> {
174        if let Some(end_tick_id) = self.assent.end_tick_id() {
175            if end_tick_id + 1 != step_for_tick_id {
176                Err(RectifyError::WrongTickId {
177                    encountered: step_for_tick_id,
178                    expected: end_tick_id + 1,
179                })?;
180            }
181        }
182        self.assent.push(step_for_tick_id, step)?;
183        self.seer.received_authoritative(
184            self.assent
185                .end_tick_id()
186                .expect("we know that there is an end tick, since we pushed to it previously"),
187        );
188
189        Ok(())
190    }
191
192    /// Updates the authoritative state. If all the authoritative state has been calculated
193    /// it predicts from the last authoritative state.
194    /// # Arguments
195    ///
196    /// * `game` - A mutable reference to the game implementing the necessary callback traits.
197    pub fn update(&mut self, game: &mut Game) {
198        let consumed_all_knowledge = self.assent.update(game);
199        if consumed_all_knowledge != UpdateState::DidNotConsumeAllKnowledge {
200            game.on_copy_from_authoritative();
201            self.seer.update(game);
202        }
203    }
204}