naia_shared/
game_time.rs

1use naia_serde::{BitReader, BitWrite, ConstBitLength, Serde, SerdeErr, UnsignedInteger};
2use naia_socket_shared::Instant;
3
4const GAME_INSANT_BITS: u8 = 22;
5pub const GAME_TIME_LIMIT: u32 = 4194304; // 2^22
6const GAME_TIME_LIMIT_U128: u128 = 4194304;
7const GAME_TIME_MAX: u32 = 4194303; // 2^22 - 1
8const TIME_OFFSET_MAX: i32 = 2097151; // 2^21 - 1
9const TIME_OFFSET_MIN: i32 = -2097152; // 2^21 * -1
10
11// GameInstant measures the # of milliseconds since the start of the Server
12// GameInstant wraps around at 2^22 milliseconds (around one hour)
13#[derive(PartialEq, Debug, Clone, Copy)]
14pub struct GameInstant {
15    millis: u32,
16}
17
18impl GameInstant {
19    pub fn new(start_instant: &Instant) -> Self {
20        let now = Instant::now();
21        let millis = (start_instant.elapsed(&now).as_millis() % GAME_TIME_LIMIT_U128) as u32;
22
23        // start_instant should mark the initialization of the Server's TimeManager
24        Self { millis }
25    }
26
27    // This method assumes that `previous_instant` is known to be from the past
28    pub fn time_since(&self, previous_instant: &GameInstant) -> GameDuration {
29        let previous_millis = previous_instant.millis;
30        let current_millis = self.millis;
31
32        if previous_millis == current_millis {
33            return GameDuration { millis: 0 };
34        }
35
36        if previous_millis < current_millis {
37            return GameDuration::from_millis(current_millis - previous_millis);
38        } else {
39            return GameDuration::from_millis(GAME_TIME_MAX - previous_millis + current_millis);
40        }
41    }
42
43    // Returns offset to target time, in milliseconds (possibly negative)
44    // 10.offset_from(12) = 2
45    // 12.offset_from(10) = -2
46    pub fn offset_from(&self, other: &GameInstant) -> i32 {
47        const MAX: i32 = TIME_OFFSET_MAX;
48        const MIN: i32 = TIME_OFFSET_MIN;
49        const ADJUST: i32 = GAME_TIME_LIMIT as i32;
50
51        let a: i32 = self.millis as i32;
52        let b: i32 = other.millis as i32;
53
54        let mut result = b - a;
55        if (MIN..=MAX).contains(&result) {
56            result
57        } else if b > a {
58            result = b - (a + ADJUST);
59            if (MIN..=MAX).contains(&result) {
60                result
61            } else {
62                panic!("integer overflow, this shouldn't happen");
63            }
64        } else {
65            result = (b + ADJUST) - a;
66            if (MIN..=MAX).contains(&result) {
67                result
68            } else {
69                panic!("integer overflow, this shouldn't happen");
70            }
71        }
72    }
73
74    pub fn is_more_than(&self, other: &GameInstant) -> bool {
75        return self.offset_from(other) < 0;
76    }
77
78    pub fn as_millis(&self) -> u32 {
79        self.millis
80    }
81
82    pub fn add_millis(&self, millis: u32) -> Self {
83        Self {
84            millis: (self.millis + millis) % GAME_TIME_LIMIT,
85        }
86    }
87
88    pub fn sub_millis(&self, millis: u32) -> Self {
89        if self.millis >= millis {
90            Self {
91                millis: self.millis - millis,
92            }
93        } else {
94            // my millis is less than your millis
95            let delta = millis - self.millis;
96            Self {
97                millis: GAME_TIME_LIMIT - delta,
98            }
99        }
100    }
101
102    pub fn add_signed_millis(&self, millis: i32) -> Self {
103        if millis >= 0 {
104            return self.add_millis(millis as u32);
105        } else {
106            return self.sub_millis((millis * -1) as u32);
107        }
108    }
109}
110
111impl Serde for GameInstant {
112    fn ser(&self, writer: &mut dyn BitWrite) {
113        let integer = UnsignedInteger::<GAME_INSANT_BITS>::new(self.millis as u64);
114        integer.ser(writer);
115    }
116
117    fn de(reader: &mut BitReader) -> Result<Self, SerdeErr> {
118        let integer = UnsignedInteger::<GAME_INSANT_BITS>::de(reader)?;
119        let millis = integer.get() as u32;
120        Ok(Self { millis })
121    }
122
123    fn bit_length(&self) -> u32 {
124        <Self as ConstBitLength>::const_bit_length()
125    }
126}
127
128impl ConstBitLength for GameInstant {
129    fn const_bit_length() -> u32 {
130        <UnsignedInteger<GAME_INSANT_BITS> as ConstBitLength>::const_bit_length()
131    }
132}
133
134// GameDuration measures the duration between two GameInstants, in milliseconds
135#[derive(PartialEq, PartialOrd, Eq, Clone)]
136pub struct GameDuration {
137    millis: u32,
138}
139
140impl GameDuration {
141    pub fn from_millis(millis: u32) -> Self {
142        Self { millis }
143    }
144
145    pub fn as_millis(&self) -> u32 {
146        return self.millis;
147    }
148
149    pub fn add_millis(&self, millis: u32) -> Self {
150        Self {
151            millis: self.millis + millis,
152        }
153    }
154
155    pub fn sub_millis(&self, millis: u32) -> Self {
156        Self {
157            millis: self.millis - millis,
158        }
159    }
160}
161
162// Tests
163#[cfg(test)]
164mod wrapping_diff_tests {
165    use super::GameInstant;
166    use crate::game_time::{GAME_TIME_LIMIT, GAME_TIME_MAX};
167
168    #[test]
169    fn simple() {
170        let a = GameInstant { millis: 10 };
171        let b = GameInstant { millis: 12 };
172
173        let result = a.offset_from(&b);
174
175        assert_eq!(result, 2);
176    }
177
178    #[test]
179    fn simple_backwards() {
180        let a = GameInstant { millis: 10 };
181        let b = GameInstant { millis: 12 };
182
183        let result = b.offset_from(&a);
184
185        assert_eq!(result, -2);
186    }
187
188    #[test]
189    fn max_wrap() {
190        let a = GameInstant {
191            millis: GAME_TIME_MAX,
192        };
193        let b = a.add_millis(2);
194
195        let result = a.offset_from(&b);
196
197        assert_eq!(result, 2);
198    }
199
200    #[test]
201    fn min_wrap() {
202        let a = GameInstant { millis: 0 };
203        let b = a.sub_millis(2);
204
205        let result = a.offset_from(&b);
206
207        assert_eq!(result, -2);
208    }
209
210    #[test]
211    fn max_wrap_backwards() {
212        let a = GameInstant {
213            millis: GAME_TIME_MAX,
214        };
215        let b = a.add_millis(2);
216
217        let result = b.offset_from(&a);
218
219        assert_eq!(result, -2);
220    }
221
222    #[test]
223    fn min_wrap_backwards() {
224        let a = GameInstant { millis: 0 };
225        let b = a.sub_millis(2);
226
227        let result = b.offset_from(&a);
228
229        assert_eq!(result, 2);
230    }
231
232    #[test]
233    fn medium_min_wrap() {
234        let diff = GAME_TIME_LIMIT / 2;
235        let a = GameInstant { millis: 0 };
236        let b = a.sub_millis(diff);
237
238        let result = a.offset_from(&b);
239
240        assert_eq!(result as i64, -i64::from(diff));
241    }
242
243    #[test]
244    fn medium_min_wrap_backwards() {
245        let diff = (GAME_TIME_LIMIT / 2) - 1;
246        let a = GameInstant { millis: 0 };
247        let b = a.sub_millis(diff);
248
249        let result = b.offset_from(&a);
250
251        assert_eq!(result as i64, i64::from(diff));
252    }
253
254    #[test]
255    fn medium_max_wrap() {
256        let diff = (GAME_TIME_LIMIT / 2) - 1;
257        let a = GameInstant {
258            millis: GAME_TIME_MAX,
259        };
260        let b = a.add_millis(diff);
261
262        let result = a.offset_from(&b);
263
264        assert_eq!(result as i64, i64::from(diff));
265    }
266
267    #[test]
268    fn medium_max_wrap_backwards() {
269        let diff = GAME_TIME_LIMIT / 2;
270        let a = GameInstant {
271            millis: GAME_TIME_MAX,
272        };
273        let b = a.add_millis(diff);
274
275        let result = b.offset_from(&a);
276
277        assert_eq!(result as i64, -i64::from(diff));
278    }
279}