Skip to main content

borger/handwritten/plugin_exports/
slotmap.rs

1use borger_plugin_sdk::diff_ser::DiffSerializer;
2use borger_plugin_sdk::multiplayer_tradeoff::{AnyTradeOff, DiffSerializerToImpl};
3use borger_plugin_sdk::primitive::{DeserializeOopsy, PrimitiveSerDes, usize32};
4use borger_plugin_sdk::traits::{
5	ConstructPlugin, CustomStruct, DiffDeserializeCustomStruct, DiffDeserializePlugin, SnapshotState,
6	UntrackedState,
7};
8use borger_plugin_sdk::{ClientKind, diff_operation_enum};
9use std::collections::HashMap;
10use std::iter::FusedIterator;
11use std::mem;
12use std::ops::Deref;
13use std::rc::Rc;
14
15#[cfg(feature = "server")]
16use borger_plugin_sdk::NetVisibility;
17
18#[cfg(feature = "client")]
19use {
20	crate::simulation::Client,
21	borger_plugin_sdk::TickID,
22	borger_plugin_sdk::multiplayer_tradeoff::Impl,
23	borger_plugin_sdk::traits::{PresentationCollect, PresentationOutput},
24	std::any::TypeId,
25	std::mem::MaybeUninit,
26	std::ptr,
27	std::vec,
28};
29
30diff_operation_enum!("SlotMap");
31
32//like a hashmap, except the key is an internally generated, unique, incremental id.
33//currently does not do any sort of generational tracking
34//guarantees:
35//- values are stored in contiguous memory for fast iteration
36//- slot id's are only reused upon wrapping around back to 0 due to crazy amount of add+remove cycles
37//- get+remove (random accesses) are O(1)
38//- add is amortized O(1), but O(n) if internal data structures need to resize
39//- iteration order is deterministic but not in order of insertion*
40//- 	*it will only be in order of insertion so long as elements are not removed from the "middle"
41//- 	*you can grab the most recently inserted slot with .iter().next_back(), if no slots have been removed since inserting it
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct RawSlotMap<V> {
45	slots: Vec<(usize32, V)>,
46	random_access: HashMap<usize32, usize32>, //<slot id, physical index>
47	next_id: usize32,
48}
49
50impl<V> Default for RawSlotMap<V> {
51	fn default() -> Self {
52		Self::new()
53	}
54}
55
56impl<V> RawSlotMap<V> {
57	pub fn new() -> Self {
58		Self {
59			slots: Vec::new(),
60			random_access: HashMap::new(),
61			next_id: 0,
62		}
63	}
64
65	pub fn add(&mut self, value: V) -> (usize32, &mut V) {
66		self.add_with_id(|_| value)
67	}
68
69	pub fn add_with_id(&mut self, value: impl FnOnce(usize32) -> V) -> (usize32, &mut V) {
70		let start_id = self.next_id;
71		let id = loop {
72			let new_id = self.next_id;
73
74			self.next_id = self.next_id.wrapping_add(1);
75			if self.next_id == start_id {
76				panic!("SlotMap's belly is too full to eat no more");
77			}
78
79			if !self.random_access.contains_key(&new_id) {
80				break new_id;
81			}
82		};
83
84		let physical_index = self.len();
85
86		self.slots.push((id, value(id)));
87		self.random_access.insert(id, physical_index);
88
89		let slot = self.slots.last_mut().unwrap();
90		(slot.0, &mut slot.1)
91	}
92
93	pub fn remove(&mut self, id: usize32) -> Option<V> {
94		let physical_index_u32 = self.random_access.remove(&id)?;
95		let physical_index_usize = physical_index_u32 as usize;
96		let (_, removed_slot) = self.slots.swap_remove(physical_index_usize);
97
98		if physical_index_u32 < self.len() {
99			//swap_remove moved the last slot to the emptied slot.
100			//need to update its random_access
101			*self
102				.random_access
103				.get_mut(&self.slots[physical_index_usize].0)
104				.unwrap() = physical_index_u32;
105		}
106
107		Some(removed_slot)
108	}
109
110	pub fn clear(&mut self) -> usize32 {
111		let len = self.len();
112
113		self.slots.clear();
114		self.random_access.clear();
115		self.next_id = 0;
116
117		len
118	}
119
120	pub fn has(&self, id: usize32) -> bool {
121		self.random_access.contains_key(&id)
122	}
123
124	pub fn get(&self, id: usize32) -> Option<&V> {
125		self.random_access
126			.get(&id)
127			.map(|&physical_index| &self.slots.get(physical_index as usize).unwrap().1)
128	}
129
130	pub fn get_mut(&mut self, id: usize32) -> Option<&mut V> {
131		self.random_access
132			.get(&id)
133			.map(|&physical_index| &mut self.slots.get_mut(physical_index as usize).unwrap().1)
134	}
135
136	pub fn len(&self) -> usize32 {
137		self.slots.len() as usize32
138	}
139
140	pub fn is_empty(&self) -> bool {
141		self.slots.is_empty()
142	}
143
144	pub fn iter(
145		&self,
146	) -> impl ExactSizeIterator<Item = (usize32, &V)> + DoubleEndedIterator + FusedIterator + Clone {
147		self.slots.iter().map(|slot| (slot.0, &slot.1))
148	}
149
150	pub fn iter_mut(
151		&mut self,
152	) -> impl ExactSizeIterator<Item = (usize32, &mut V)> + DoubleEndedIterator + FusedIterator {
153		self.slots.iter_mut().map(|slot| (slot.0, &mut slot.1))
154	}
155
156	pub fn ids(
157		&self,
158	) -> impl ExactSizeIterator<Item = usize32> + DoubleEndedIterator + FusedIterator + Clone {
159		self.slots.iter().map(|slot| slot.0)
160	}
161
162	pub fn values(&self) -> impl ExactSizeIterator<Item = &V> + DoubleEndedIterator + FusedIterator + Clone {
163		self.slots.iter().map(|slot| &slot.1)
164	}
165
166	pub fn values_mut(
167		&mut self,
168	) -> impl ExactSizeIterator<Item = &mut V> + DoubleEndedIterator + FusedIterator {
169		self.slots.iter_mut().map(|slot| &mut slot.1)
170	}
171}
172
173//---simulation---//
174
175#[derive(Debug)]
176pub struct SlotMap<V: CustomStruct> {
177	diff_path: Rc<[usize32]>,
178	field_id: usize32,
179
180	#[cfg(feature = "server")]
181	visibility: NetVisibility,
182
183	data: RawSlotMap<V>,
184}
185
186//---constructors---//
187
188impl<V: CustomStruct> ConstructPlugin for SlotMap<V> {
189	fn construct(
190		path: &Rc<[usize32]>,
191		field_id: usize32,
192
193		#[cfg(feature = "server")] visibility: NetVisibility,
194	) -> Self {
195		Self {
196			diff_path: path.clone(),
197			field_id,
198
199			#[cfg(feature = "server")]
200			visibility,
201
202			data: RawSlotMap::new(),
203		}
204	}
205}
206
207//---diff_ser---//
208
209impl<V: CustomStruct> Deref for SlotMap<V> {
210	type Target = RawSlotMap<V>;
211
212	fn deref(&self) -> &Self::Target {
213		&self.data
214	}
215}
216
217impl<V: CustomStruct> SlotMap<V> {
218	pub fn add(&mut self, diff: &mut DiffSerializer<impl AnyTradeOff>) -> (usize32, &mut V) {
219		self.add_with_client_owned(ClientKind::NA, diff)
220	}
221
222	pub(crate) fn add_with_client_owned(
223		&mut self,
224		client_kind: ClientKind,
225		diff: &mut DiffSerializer<impl AnyTradeOff>,
226	) -> (usize32, &mut V) {
227		let op = DiffOperation::SlotMapAdd;
228		let diff = diff.to_impl();
229
230		if let Some(buffer) = diff.ser_rollback_begin(&self.diff_path) {
231			self.data.next_id.ser_rollback(buffer);
232			self.field_id.ser_rollback(buffer);
233			op.ser_rollback(buffer);
234		}
235
236		#[cfg(feature = "server")]
237		for buffer in diff.ser_tx_begin(&self.diff_path, self.visibility) {
238			op.ser_tx(buffer);
239			self.field_id.ser_tx(buffer);
240		}
241
242		self.data
243			.add_with_id(|id| V::construct(&build_slot_path(id, &self.diff_path, self.field_id), client_kind))
244	}
245
246	//can't return the removed value! it would provide
247	//a loophole for constructing new state objects.
248	//the state must own all instances. unwrap to
249	//assert success
250	#[must_use]
251	pub fn remove(&mut self, id: usize32, diff: &mut DiffSerializer<impl AnyTradeOff>) -> Option<()> {
252		let physical_index = *self.data.random_access.get(&id)?;
253		let removed_slot = self.data.remove(id).unwrap();
254
255		let op = DiffOperation::SlotMapRemove;
256		let diff = diff.to_impl();
257
258		if let Some(buffer) = diff.ser_rollback_begin(&self.diff_path) {
259			removed_slot.ser_rollback_predict_remove(buffer);
260			physical_index.ser_rollback(buffer);
261			id.ser_rollback(buffer);
262			self.field_id.ser_rollback(buffer);
263			op.ser_rollback(buffer);
264		}
265
266		#[cfg(feature = "server")]
267		for buffer in diff.ser_tx_begin(&self.diff_path, self.visibility) {
268			op.ser_tx(buffer);
269			self.field_id.ser_tx(buffer);
270			id.ser_tx(buffer);
271		}
272
273		Some(())
274	}
275
276	//significantly more efficient in terms of bandwidth usage
277	//compared to iter+remove
278	pub fn clear(&mut self, diff: &mut DiffSerializer<impl AnyTradeOff>) -> usize32 {
279		let len = self.data.len();
280
281		//can't early exit if next_id != 0. must still reset the id
282		if len == 0 && self.data.next_id == 0 {
283			return 0;
284		}
285
286		let op = DiffOperation::SlotMapClear;
287		let diff = diff.to_impl();
288
289		if let Some(buffer) = diff.ser_rollback_begin(&self.diff_path) {
290			self.ser_rollback_predict_remove(buffer);
291			self.field_id.ser_rollback(buffer);
292			op.ser_rollback(buffer);
293		}
294
295		#[cfg(feature = "server")]
296		for buffer in diff.ser_tx_begin(&self.diff_path, self.visibility) {
297			op.ser_tx(buffer);
298			self.field_id.ser_tx(buffer);
299		}
300
301		self.data.clear();
302		len
303	}
304
305	pub fn get_mut(&mut self, id: usize32) -> Option<&mut V> {
306		self.data.get_mut(id)
307	}
308
309	pub fn iter_mut(
310		&mut self,
311	) -> impl ExactSizeIterator<Item = (usize32, &mut V)> + DoubleEndedIterator + FusedIterator {
312		self.data.iter_mut()
313	}
314
315	pub fn values_mut(
316		&mut self,
317	) -> impl ExactSizeIterator<Item = &mut V> + DoubleEndedIterator + FusedIterator {
318		self.data.values_mut()
319	}
320}
321
322//---diff_des---//
323
324impl<V: CustomStruct> DiffDeserializePlugin for SlotMap<V> {
325	fn navigate_down(&mut self, element_id: usize32) -> Option<&mut dyn DiffDeserializeCustomStruct> {
326		self.get_mut(element_id)
327			.map(|v| v as &mut dyn DiffDeserializeCustomStruct)
328	}
329
330	fn des_rollback(&mut self, diff_op: u8, buffer: &mut Vec<u8>) -> Result<(), DeserializeOopsy> {
331		match DiffOperation::try_from(diff_op).map_err(|_| DeserializeOopsy)? {
332			DiffOperation::SlotMapAdd => {
333				self.data.next_id = usize32::des_rollback(buffer)?;
334				let id = self.data.slots.pop().unwrap().0;
335				self.data.random_access.remove(&id).unwrap();
336			}
337			DiffOperation::SlotMapRemove => {
338				let id = usize32::des_rollback(buffer)?;
339				let physical_index = usize32::des_rollback(buffer)?;
340
341				//since client deletion is unpredictable and will
342				//never roll back, client_kind can be anything
343				//here. all other constructors ignore the arg
344				let mut value = V::construct(
345					&build_slot_path(id, &self.diff_path, self.field_id),
346					ClientKind::NA,
347				);
348				value.des_rollback_predict_remove(buffer)?;
349
350				let reincarnated_slot = (id, value);
351				if physical_index == self.data.len() {
352					//insert as last slot
353					self.data.slots.push(reincarnated_slot);
354				} else {
355					//insert in the middle + move the current resident
356					//of this slot back to the end
357					let end_slot =
358						mem::replace(&mut self.data.slots[physical_index as usize], reincarnated_slot);
359					*self.data.random_access.get_mut(&end_slot.0).unwrap() = self.data.len();
360					self.data.slots.push(end_slot);
361				}
362
363				self.data.random_access.insert(id, physical_index);
364			}
365			DiffOperation::SlotMapClear => {
366				self.des_rollback_predict_remove(buffer)?;
367			}
368		};
369
370		Ok(())
371	}
372
373	#[cfg(feature = "client")]
374	fn des_rx(
375		&mut self,
376		diff_op: u8,
377		buffer: &mut vec::IntoIter<u8>,
378		diff: &mut DiffSerializer<Impl>,
379	) -> Result<(), DeserializeOopsy> {
380		match DiffOperation::try_from(diff_op).map_err(|_| DeserializeOopsy)? {
381			DiffOperation::SlotMapAdd => {
382				self.add(diff);
383			}
384			DiffOperation::SlotMapRemove => {
385				let id = usize32::des_rx(buffer)?;
386				self.remove(id, diff).ok_or(DeserializeOopsy)?;
387			}
388			DiffOperation::SlotMapClear => {
389				self.clear(diff);
390			}
391		};
392
393		Ok(())
394	}
395}
396
397//---snapshot---//
398
399impl<V: CustomStruct> SnapshotState for SlotMap<V> {
400	#[cfg(feature = "server")]
401	fn ser_tx_new_client(&self, client_id: usize32, buffer: &mut Vec<u8>) {
402		self.data.next_id.ser_tx(buffer);
403		self.len().ser_tx(buffer);
404
405		for slot in self.data.slots.iter() {
406			slot.0.ser_tx(buffer);
407			slot.1.ser_tx_new_client(client_id, buffer);
408		}
409	}
410
411	#[cfg(feature = "client")]
412	fn des_rx_new_client(
413		&mut self,
414		client_id: usize32,
415		buffer: &mut impl Iterator<Item = u8>,
416	) -> Result<(), DeserializeOopsy> {
417		self.data.next_id = usize32::des_rx(buffer)?;
418		let len = usize32::des_rx(buffer)?;
419
420		for physical_index in 0..len {
421			let id = usize32::des_rx(buffer)?;
422
423			//nasty specialization hack: this is temporary,
424			//with the thinking that the custom net visibility
425			//scopes system will supersede owned/remote system
426			let client_kind = if id == client_id && TypeId::of::<V>() == TypeId::of::<Client>() {
427				ClientKind::Owned
428			} else {
429				ClientKind::Remote
430			};
431
432			let mut slot = V::construct(&build_slot_path(id, &self.diff_path, self.field_id), client_kind);
433			slot.des_rx_new_client(client_id, buffer)?;
434
435			self.data.slots.push((id, slot));
436			self.data.random_access.insert(id, physical_index);
437		}
438
439		Ok(())
440	}
441
442	//this is expensive because it serializes every single
443	//slot. if called, you've either manually called .clear(),
444	//or a parent/outer collection called remove() and was
445	//forced to serialize all inner collections. there is
446	//potentially a large amount of data being rapidly rolled
447	//back
448	fn ser_rollback_predict_remove(&self, buffer: &mut Vec<u8>) {
449		for slot in self.data.slots.iter().rev() {
450			slot.1.ser_rollback_predict_remove(buffer);
451			slot.0.ser_rollback(buffer);
452		}
453
454		self.data.len().ser_rollback(buffer);
455		self.data.next_id.ser_rollback(buffer);
456	}
457
458	fn des_rollback_predict_remove(&mut self, buffer: &mut Vec<u8>) -> Result<(), DeserializeOopsy> {
459		self.data.next_id = usize32::des_rollback(buffer)?;
460		let slots_len = usize32::des_rollback(buffer)?;
461
462		for physical_index in 0..slots_len {
463			let id = usize32::des_rollback(buffer)?;
464			let mut slot = V::construct(
465				&build_slot_path(id, &self.diff_path, self.field_id),
466				ClientKind::NA,
467			);
468			slot.des_rollback_predict_remove(buffer)?;
469
470			self.data.slots.push((id, slot));
471			self.data.random_access.insert(id, physical_index);
472		}
473
474		Ok(())
475	}
476}
477
478//---untracked---//
479impl<V: CustomStruct> UntrackedState for SlotMap<V> {
480	fn reset_untracked(&mut self) {
481		for slot in self.values_mut() {
482			slot.reset_untracked();
483		}
484	}
485}
486
487//---presentation_collect---//
488
489#[cfg(feature = "client")]
490impl<V> PresentationCollect for SlotMap<V>
491where
492	V: CustomStruct + PresentationCollect,
493{
494	type PresentationCollect = RawSlotMap<V::PresentationCollect>;
495	fn presentation_collect(&self, tick: TickID) -> Self::PresentationCollect {
496		RawSlotMap {
497			slots: self
498				.data
499				.slots
500				.iter()
501				.map(|slot| (slot.0, slot.1.presentation_collect(tick)))
502				.collect(),
503
504			random_access: self.data.random_access.clone(),
505			next_id: self.data.next_id,
506		}
507	}
508}
509
510//---presentation_output---//
511
512#[cfg(feature = "client")]
513pub struct PresentationOutputSlotMap<V> {
514	pub data: RawSlotMap<V>,
515	pub slots_ptr: *const (usize32, V),
516	pub slots_len: usize32,
517	pub removed: Vec<usize32>,
518	pub removed_ptr: *const usize32,
519	pub removed_len: usize32,
520	pub added: Vec<usize32>,
521	pub added_ptr: *const usize32,
522	pub added_len: usize32,
523}
524
525#[cfg(feature = "client")]
526impl<V: PresentationOutput<Prv>, Prv> PresentationOutput<RawSlotMap<Prv>> for RawSlotMap<V> {
527	type PresentationOutput = PresentationOutputSlotMap<V::PresentationOutput>;
528	fn presentation_output(
529		prv: Option<&RawSlotMap<Prv>>,
530		cur: &Self,
531		interpolation_alpha: f32,
532		received_new_tick: bool,
533	) -> Self::PresentationOutput {
534		let Some(prv) = prv else {
535			let slots: Vec<(u32, V::PresentationOutput)> = cur
536				.slots
537				.iter()
538				.map(|slot| {
539					(
540						slot.0,
541						V::presentation_output(None, &slot.1, interpolation_alpha, received_new_tick),
542					)
543				})
544				.collect();
545
546			let slots_ptr = slots.as_ptr();
547			let added: Vec<_> = if received_new_tick {
548				cur.ids().collect()
549			} else {
550				Vec::new()
551			};
552
553			let added_ptr = added.as_ptr();
554			let added_len = added.len() as usize32;
555
556			return PresentationOutputSlotMap {
557				data: RawSlotMap {
558					slots,
559					random_access: cur.random_access.clone(),
560					next_id: cur.next_id,
561				},
562
563				slots_ptr,
564				slots_len: cur.len(),
565				removed: Vec::default(),
566				removed_ptr: ptr::null(),
567				removed_len: 0,
568				added,
569				added_ptr,
570				added_len,
571			};
572		};
573
574		let mut slots: Vec<MaybeUninit<(u32, V::PresentationOutput)>> =
575			(0..cur.slots.len()).map(|_a| MaybeUninit::uninit()).collect();
576
577		let mut removed = Vec::new();
578		let mut added = Vec::new();
579
580		let max_len = prv.slots.len().max(cur.slots.len());
581		for slot_idx in 0..max_len {
582			//try to guess which slots in cur correspond to existing prv slots.
583			//it is logically impossible to do this with complete accuracy
584			//because the knowledge of how prv came to be cur is lost to time -
585			//mispredicts causing id changes, rollbacks repeatedly triggering
586			//add and remove events, etc. this algo simply searches for slots
587			//between prv+cur that have matching id's and interpolates them if
588			//found
589			let prv_slot = prv.slots.get(slot_idx);
590			let cur_slot = cur.slots.get(slot_idx);
591			if let (Some(prv_slot), Some(cur_slot)) = (prv_slot, cur_slot)
592				&& prv_slot.0 == cur_slot.0
593			{
594				//this branch should be what executes most often: data is still in
595				//the same slot as the previous tick so can cleanly interpolate
596				slots[slot_idx].write((
597					cur_slot.0,
598					V::presentation_output(
599						Some(&prv_slot.1),
600						&cur_slot.1,
601						interpolation_alpha,
602						received_new_tick,
603					),
604				));
605			} else {
606				if let Some(cur_slot) = cur_slot {
607					let prv_moved_slot = prv.get(cur_slot.0);
608					slots[slot_idx].write((
609						cur_slot.0,
610						V::presentation_output(
611							prv_moved_slot,
612							&cur_slot.1,
613							interpolation_alpha,
614							received_new_tick,
615						),
616					));
617
618					if received_new_tick && prv_moved_slot.is_none() {
619						added.push(cur_slot.0);
620					}
621				}
622
623				if received_new_tick && let Some(prv_slot) = prv_slot {
624					if !cur.has(prv_slot.0) {
625						removed.push(prv_slot.0);
626					}
627				}
628			}
629		}
630
631		//safety: for loop guarantees every slot was written to
632		let slots: Vec<(u32, V::PresentationOutput)> = unsafe { mem::transmute(slots) };
633		let slots_ptr = slots.as_ptr();
634		let removed_ptr = removed.as_ptr();
635		let removed_len = removed.len() as usize32;
636		let added_ptr = added.as_ptr();
637		let added_len = added.len() as usize32;
638
639		PresentationOutputSlotMap {
640			data: RawSlotMap {
641				slots,
642				random_access: cur.random_access.clone(),
643				next_id: cur.next_id,
644			},
645
646			slots_ptr,
647			slots_len: cur.len(),
648			removed,
649			removed_ptr,
650			removed_len,
651			added,
652			added_ptr,
653			added_len,
654		}
655	}
656}
657
658//---misc---//
659
660fn build_slot_path(id: usize32, diff_path: &Rc<[usize32]>, field_id: usize32) -> Rc<[usize32]> {
661	diff_path.iter().copied().chain([field_id, id]).collect()
662}