Skip to main content

borger/handwritten/
diff_ser.rs

1use crate::DiffOperation;
2use crate::multiplayer_tradeoff::{AnyTradeOff, Impl};
3use crate::networked_types::primitive::{PrimitiveSerDes, SliceSerDes, usize32};
4use crate::tick::TickType;
5use std::marker::PhantomData;
6use std::mem;
7use std::rc::Rc;
8
9#[cfg(feature = "server")]
10use {crate::NetVisibility, std::collections::HashMap};
11
12///All state-mutating function signatures that the code generator produces
13///require this struct to be passed in. It follows the opaque handle pattern,
14///so everything Just Works™. Internally, it's capturing and serializing the
15///before and after values, allowing the value to be both rolled back and
16///transmitted to clients.
17///
18///# Examples
19///```rust
20///let diff: &mut DiffSerializer = &mut ctx.diff;
21///let mut cur_time = ctx.state.get_time();
22///cur_time -= 1;
23///ctx.state.set_time(cur_time, diff);
24///```
25#[derive(Default)]
26pub struct DiffSerializer<TradeOff: AnyTradeOff> {
27	//write the PREVIOUS value of a state in order to
28	//undo+resimulate it later. will be read back to
29	//front (i=len to i=0). contains multiple ticks
30	//worth of state changes (however many are still
31	//considered unfinalized predictions). remember
32	//rollback is a LOCAL backup consisting only of
33	//fields that exist locally and will never be sent
34	//over wire
35	pub(crate) rollback_buffer: Vec<u8>, //raw packed bytes representing diffs to the state
36	rollback_enabled: bool,              //rollback is disabled during consensus/unrollbackable events
37	rollback_prv_path: Option<Rc<Vec<usize32>>>,
38
39	//write the CURRENT value of a state to transmit
40	//over the wire. will be read front to back (i=0
41	//to i=len). contains only one tick at a time
42	//before sending+resetting
43	#[cfg(feature = "server")]
44	tx: HashMap<usize32, TxData>,
45	#[cfg(feature = "client")]
46	tx: TxData,
47
48	//think of it like rollback = instructions for
49	//undoing changes to state, and tx = instructions
50	//for redoing/replicating/replaying changes to
51	//state on a different device on the network
52	phantom_menace: PhantomData<TradeOff>,
53}
54
55struct TxData {
56	buffer: Vec<u8>,
57
58	#[cfg(feature = "server")]
59	enabled: bool,
60	#[cfg(feature = "server")]
61	cur_path: Rc<Vec<usize32>>,
62}
63
64impl Default for TxData {
65	fn default() -> Self {
66		Self {
67			buffer: Vec::default(),
68
69			#[cfg(feature = "server")]
70			enabled: true, //<--important!
71			#[cfg(feature = "server")]
72			cur_path: Rc::default(),
73		}
74	}
75}
76
77impl DiffSerializer<Impl> {
78	//---state change tracking---//
79	//ser_rollback_begin and the server side version of ser_tx_begin
80	//should be called in pairs, one after the other. caller must
81	//serialize the diff operation
82
83	pub(crate) fn ser_rollback_begin(&mut self, path: &Rc<Vec<usize32>>) -> Option<&mut Vec<u8>> {
84		if self.rollback_enabled {
85			self.ser_rollback_navigate_to(path);
86			Some(&mut self.rollback_buffer)
87		} else {
88			None
89		}
90	}
91
92	//only the server sends authoritative state updates
93	#[cfg(feature = "server")]
94	pub(crate) fn ser_tx_begin(
95		&mut self,
96		path: &Rc<Vec<usize32>>,
97		visibility: NetVisibility,
98	) -> impl Iterator<Item = &mut Vec<u8>> {
99		self.tx
100			.iter_mut()
101			.filter(move |&(&tx_client_id, ref tx)| {
102				//scope filtering: skip sending this state to any
103				//client who doesn't need to know about this change
104				tx.enabled
105					&& match visibility {
106						NetVisibility::Public => true,
107						NetVisibility::Private => false,
108						NetVisibility::Owner => {
109							let modified_client_id = path[1];
110							tx_client_id == modified_client_id
111						}
112					}
113			})
114			.map(|(_, tx)| {
115				Self::ser_tx_navigate_to(tx, path);
116				&mut tx.buffer
117			})
118	}
119
120	//only the client sends authoritative input updates
121	#[cfg(feature = "client")]
122	pub(crate) fn ser_tx_begin(&mut self) -> &mut Vec<u8> {
123		&mut self.tx.buffer
124	}
125
126	//---tick lifecycle---//
127
128	pub(crate) fn rollback_begin_tick(&mut self, tick_type: TickType) {
129		self.rollback_enabled = tick_type == TickType::Predicted;
130		self.rollback_prv_path = None;
131
132		if self.rollback_enabled {
133			DiffOperation::RollbackTickSeparator.ser_rollback(&mut self.rollback_buffer);
134		}
135	}
136
137	pub(crate) fn rollback_end_tick(&mut self) {
138		if self.rollback_enabled {
139			self.ser_rollback_navigate_to(&Rc::default());
140		}
141	}
142
143	#[cfg(feature = "server")]
144	pub(crate) fn tx_begin_tick(&mut self, id: usize32, enable: bool) -> Option<&mut Vec<u8>> {
145		let client = self.tx.get_mut(&id).unwrap();
146		client.enabled = enable;
147		enable.then(|| &mut client.buffer)
148	}
149
150	//get the finalized data to send over the wire.
151	//called per-webtransport connection
152	pub(crate) fn tx_end_tick(&mut self, #[cfg(feature = "server")] client_id: usize32) -> Option<Vec<u8>> {
153		//server is sending state
154		#[cfg(feature = "server")]
155		let (tx, enabled) = {
156			let tx = self.tx.get_mut(&client_id).unwrap();
157			let enabled = tx.enabled;
158			(tx, enabled)
159		};
160
161		//client is sending input state
162		#[cfg(feature = "client")]
163		let (tx, enabled) = (&mut self.tx, true);
164
165		if enabled {
166			//server buffer is guaranteed to always start
167			//with a tick id. empty client buffer is valid,
168			//and the server still needs to know that
169			//nothing has changed since the last tick
170			#[cfg(feature = "server")]
171			{
172				debug_assert!(!tx.buffer.is_empty());
173
174				tx.cur_path = Rc::default();
175			}
176
177			let ret = mem::take(&mut tx.buffer);
178			Some(ret)
179		} else {
180			debug_assert!(tx.buffer.is_empty());
181			None
182		}
183	}
184
185	#[cfg(feature = "server")]
186	pub(crate) fn on_connect(&mut self, client_id: usize32) -> &mut Vec<u8> {
187		self.tx.insert(client_id, TxData::default()); //default = tx enabled
188		&mut self.tx.get_mut(&client_id).unwrap().buffer
189	}
190
191	#[cfg(feature = "server")]
192	pub(crate) fn on_disconnect(&mut self, client_id: usize32) {
193		self.tx.remove(&client_id).unwrap();
194	}
195
196	//---diff path navigation---//
197
198	//to avoid having to write the full path on every single
199	//serialize operation, only write when the path changes
200	//relative to the current path.
201	//path sizes are stored as u8. this forces a requirement that
202	//there cannot be more than 256 layers of nested collection
203	//types. deal with it
204	//[field id, element id, field id, element id...]
205	fn ser_rollback_navigate_to(&mut self, new_path: &Rc<Vec<usize32>>) {
206		//the thing that makes rollback navigation
207		//clunkier than tx navigation is that the
208		//path must be written to the buffer AFTER
209		//fields at this path have been modified.
210		//(remember parsing rollback happens in
211		//reverse order.)
212		//if prv_path is none, the tick is brand new.
213		//do not write to the rollback buffer.
214		if let Some(prv_path) = &self.rollback_prv_path {
215			if let Some(shared_len) = find_first_mismatch(&new_path, &prv_path) {
216				let new_len = new_path.len();
217				let prv_len = prv_path.len();
218				let buffer = &mut self.rollback_buffer;
219
220				if prv_len > shared_len {
221					prv_path[shared_len as usize..prv_len as usize].ser_rollback(buffer);
222					(((prv_len - shared_len) / 2) as u8).ser_rollback(buffer);
223					DiffOperation::NavigateDown.ser_rollback(buffer);
224				}
225
226				if new_len > shared_len {
227					if shared_len == 0 {
228						DiffOperation::NavigateReset.ser_rollback(buffer);
229					} else {
230						(((new_len - shared_len) / 2) as u8).ser_rollback(buffer);
231						DiffOperation::NavigateUp.ser_rollback(buffer);
232					}
233				}
234
235				self.rollback_prv_path = Some(new_path.clone());
236			}
237		} else {
238			self.rollback_prv_path = Some(new_path.clone());
239		}
240	}
241
242	#[cfg(feature = "server")]
243	fn ser_tx_navigate_to(tx_data: &mut TxData, new_path: &Rc<Vec<usize32>>) {
244		if let Some(shared_len) = find_first_mismatch(&tx_data.cur_path, &new_path) {
245			let cur_len = tx_data.cur_path.len();
246			let new_len = new_path.len();
247			let buffer = &mut tx_data.buffer;
248
249			if cur_len > shared_len {
250				if shared_len == 0 {
251					DiffOperation::NavigateReset.ser_tx(buffer);
252				} else {
253					DiffOperation::NavigateUp.ser_tx(buffer);
254					(((cur_len - shared_len) / 2) as u8).ser_rollback(buffer);
255				}
256			}
257
258			if new_len > shared_len {
259				DiffOperation::NavigateDown.ser_tx(buffer);
260				(((new_len - shared_len) / 2) as u8).ser_tx(buffer);
261				new_path[shared_len as usize..new_len as usize].ser_tx(buffer);
262			}
263
264			tx_data.cur_path = new_path.clone();
265		}
266	}
267}
268
269fn find_first_mismatch<T: PartialEq>(vec1: &[T], vec2: &[T]) -> Option<usize> {
270	//compare 2 elements at a time (one nav
271	//level = 2 elements)
272	for (i, (level1, level2)) in vec1.chunks(2).zip(vec2.chunks(2)).enumerate() {
273		if level1[0] != level2[0] || level1[1] != level2[1] {
274			return Some(i * 2);
275		}
276	}
277
278	if vec1.len() != vec2.len() {
279		return Some(vec1.len().min(vec2.len()));
280	}
281
282	None
283}