elura_netcode/
entity_prediction.rs1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{NetcodeError, NetcodeResult};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub struct PredictionKey(pub u64);
10
11#[derive(Debug, Clone)]
13pub struct PredictionKeyGenerator {
14 next: u64,
15}
16
17impl Default for PredictionKeyGenerator {
18 fn default() -> Self {
19 Self { next: 1 }
20 }
21}
22
23impl PredictionKeyGenerator {
24 pub fn with_next(next: u64) -> NetcodeResult<Self> {
26 if next == 0 {
27 return Err(NetcodeError::InvalidConfig(
28 "next prediction key must be positive",
29 ));
30 }
31 Ok(Self { next })
32 }
33
34 pub fn generate(&mut self) -> NetcodeResult<PredictionKey> {
36 let key = self.next;
37 self.next = self
38 .next
39 .checked_add(1)
40 .ok_or(NetcodeError::SequenceExhausted)?;
41 Ok(PredictionKey(key))
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(default, deny_unknown_fields)]
48#[non_exhaustive]
49pub struct PredictedEntityConfig {
50 pub capacity: usize,
52 pub timeout_ticks: u64,
54}
55
56impl Default for PredictedEntityConfig {
57 fn default() -> Self {
58 Self {
59 capacity: 128,
60 timeout_ticks: 120,
61 }
62 }
63}
64
65impl PredictedEntityConfig {
66 pub fn validate(&self) -> NetcodeResult<()> {
68 if self.capacity == 0 || self.timeout_ticks == 0 {
69 return Err(NetcodeError::InvalidConfig(
70 "predicted entity limits must be positive",
71 ));
72 }
73 Ok(())
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct PredictedEntity<I> {
80 pub key: PredictionKey,
82 pub temporary_entity: I,
84 pub spawned_tick: u64,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct EntityMatch<I> {
91 pub key: PredictionKey,
93 pub temporary_entity: I,
95 pub authoritative_entity: I,
97 pub age_ticks: u64,
99}
100
101#[derive(Debug, Clone)]
103pub struct PredictedEntityMatcher<I> {
104 config: PredictedEntityConfig,
105 pending: BTreeMap<PredictionKey, PredictedEntity<I>>,
106 temporary: BTreeMap<I, PredictionKey>,
107}
108
109impl<I> PredictedEntityMatcher<I>
110where
111 I: Ord + Clone,
112{
113 pub fn new(config: PredictedEntityConfig) -> NetcodeResult<Self> {
115 config.validate()?;
116 Ok(Self {
117 config,
118 pending: BTreeMap::new(),
119 temporary: BTreeMap::new(),
120 })
121 }
122
123 pub fn register(
125 &mut self,
126 key: PredictionKey,
127 temporary_entity: I,
128 spawned_tick: u64,
129 ) -> NetcodeResult<()> {
130 if key.0 == 0 || spawned_tick == 0 {
131 return Err(NetcodeError::InvalidInput(
132 "prediction key and spawn Tick must be positive",
133 ));
134 }
135 if self.pending.contains_key(&key) || self.temporary.contains_key(&temporary_entity) {
136 return Err(NetcodeError::InvalidInput(
137 "prediction key or temporary entity is already registered",
138 ));
139 }
140 if self.pending.len() >= self.config.capacity {
141 return Err(NetcodeError::PredictedEntityLimit);
142 }
143 self.temporary.insert(temporary_entity.clone(), key);
144 self.pending.insert(
145 key,
146 PredictedEntity {
147 key,
148 temporary_entity,
149 spawned_tick,
150 },
151 );
152 Ok(())
153 }
154
155 pub fn resolve(
157 &mut self,
158 key: PredictionKey,
159 authoritative_entity: I,
160 current_tick: u64,
161 ) -> NetcodeResult<Option<EntityMatch<I>>> {
162 let Some(predicted) = self.pending.get(&key) else {
163 return Ok(None);
164 };
165 if current_tick < predicted.spawned_tick {
166 return Err(NetcodeError::InvalidInput(
167 "authoritative match Tick precedes predicted spawn",
168 ));
169 }
170 if current_tick - predicted.spawned_tick > self.config.timeout_ticks {
171 let predicted = self.pending.remove(&key).unwrap();
172 self.temporary.remove(&predicted.temporary_entity);
173 return Ok(None);
174 }
175 let predicted = self.pending.remove(&key).unwrap();
176 self.temporary.remove(&predicted.temporary_entity);
177 Ok(Some(EntityMatch {
178 key,
179 temporary_entity: predicted.temporary_entity,
180 authoritative_entity,
181 age_ticks: current_tick - predicted.spawned_tick,
182 }))
183 }
184
185 pub fn expire(&mut self, current_tick: u64) -> Vec<PredictedEntity<I>> {
187 let expired_keys = self
188 .pending
189 .iter()
190 .filter(|(_, predicted)| {
191 current_tick.saturating_sub(predicted.spawned_tick) > self.config.timeout_ticks
192 })
193 .map(|(key, _)| *key)
194 .collect::<Vec<_>>();
195 expired_keys
196 .into_iter()
197 .filter_map(|key| {
198 let predicted = self.pending.remove(&key)?;
199 self.temporary.remove(&predicted.temporary_entity);
200 Some(predicted)
201 })
202 .collect()
203 }
204
205 pub fn len(&self) -> usize {
207 self.pending.len()
208 }
209
210 pub fn is_empty(&self) -> bool {
212 self.pending.is_empty()
213 }
214
215 pub fn get(&self, key: PredictionKey) -> Option<&PredictedEntity<I>> {
217 self.pending.get(&key)
218 }
219
220 pub fn clear(&mut self) {
222 self.pending.clear();
223 self.temporary.clear();
224 }
225}