veloren_common/region.rs
1use crate::comp::{Pos, Presence, Vel};
2use common_base::span;
3use hashbrown::{DefaultHashBuilder, HashSet};
4use indexmap::IndexMap;
5use specs::{BitSet, Entities, Join, LendJoin, ReadStorage, hibitset::BitSetLike};
6use vek::*;
7
8pub enum RegionEvent {
9 /// Contains the key of the region the entity moved to.
10 ///
11 /// This can be `None` if the `Pos` component is removed or
12 /// `Presence::sync_me` becomes `false`.
13 ///
14 /// If an entity is deleted this event won't be generated.
15 Left(u32, Option<Vec2<i32>>),
16 /// Contains the key of the region the entity came from.
17 Entered(u32, Option<Vec2<i32>>),
18}
19
20/// Region consisting of a bitset of entities within it.
21#[derive(Default)]
22pub struct Region {
23 // Use specs bitset for simplicity (and joinability)
24 bitset: BitSet,
25 // TODO consider SmallVec for these
26 /// Entities that left or entered this region.
27 events: Vec<RegionEvent>,
28}
29impl Region {
30 /// Checks if the region contains no entities and no events
31 fn removable(&self) -> bool { self.bitset.is_empty() && self.events.is_empty() }
32
33 fn add(&mut self, id: u32, from: Option<Vec2<i32>>) {
34 self.bitset.add(id);
35 self.events.push(RegionEvent::Entered(id, from));
36 }
37
38 fn remove(&mut self, id: u32, to: Option<Vec2<i32>>) {
39 self.bitset.remove(id);
40 self.events.push(RegionEvent::Left(id, to));
41 }
42
43 pub fn events(&self) -> &[RegionEvent] { &self.events }
44
45 pub fn entities(&self) -> &BitSet { &self.bitset }
46}
47
48/// How far can an entity roam outside its region before it is switched over to
49/// the neighboring one In units of blocks (i.e. world pos)
50/// Used to prevent rapid switching of entities between regions
51pub const TETHER_LENGTH: u32 = 16;
52// TODO: This is very coarse for clients with smaller view distances, consider
53// using a per client bitset instead of the region system, or at least opt-into
54// this for clients with sufficiently small view distances? This could save
55// bandwidth. Having a per client bitset would be useful for more complex
56// criteria of when to sync although it could take more CPU to maintain, since
57// with the region system each entity goes into a single bitset.
58//
59/// Bitshift between region and world pos, i.e. log2(REGION_SIZE)
60///
61/// 16 x 16 chunks (if chunks are 32 x 32 blocks)
62const REGION_LOG2: u8 = 9;
63/// Region size in blocks
64pub const REGION_SIZE: u32 = 1 << REGION_LOG2;
65
66#[derive(Default)]
67// TODO compare to sweep and prune approach
68/// A region system that tracks where entities are.
69///
70/// Note, this structure is primarily intended for tracking which entities need
71/// to be synchronized to which clients (and as part of that what entities are
72/// already synchronized). If an entity is marked to not be synchronized to
73/// other clients it may not appear here.
74pub struct RegionMap {
75 // Tree?
76 // Sorted Vec? (binary search lookup)
77 // Sort into multiple vecs (say 32) using lower bits of morton code, then binary search via
78 // upper bits? <-- sounds very promising to me (might not be super good though?)
79 regions: IndexMap<Vec2<i32>, Region, DefaultHashBuilder>,
80 /// If an entity isn't here it needs to be added to a region.
81 tracked_entities: BitSet,
82 /// If an entity is in `tracked_entities` this will contain the key of the
83 /// region containing that entity.
84 ///
85 /// Indexed by entity ID.
86 ///
87 /// Ideally, this would be the index of the region but then we would need to
88 /// update this whenever removing regions
89 entity_to_region: Vec<Vec2<i32>>,
90 // Re-useable vecs
91 // (src, entity, pos)
92 entities_to_move: Vec<(usize, u32, Vec3<i32>)>,
93 // (region, entity)
94 entities_to_remove: Vec<(usize, u32)>,
95 // Track the current tick, used to enable not checking everything every tick
96 // rate is dependent on the rate the caller calls region_manager.tick()
97 tick: u64,
98}
99impl RegionMap {
100 pub fn new() -> Self { Self::default() }
101
102 // TODO maintain within a system?
103 // TODO special case large entities
104 pub fn tick(
105 &mut self,
106 pos: ReadStorage<Pos>,
107 vel: ReadStorage<Vel>,
108 presence: ReadStorage<Presence>,
109 entities: Entities,
110 ) {
111 span!(_guard, "tick", "Region::tick");
112 self.tick += 1;
113 // Clear events within each region
114 self.regions.values_mut().for_each(|region| {
115 region.events.clear();
116 });
117
118 // Add any untracked entities
119 for (pos, id) in (&pos, &entities, presence.maybe(), !&self.tracked_entities)
120 .join()
121 .filter(|(_, _, presence, _)| presence.is_none_or(|p| p.kind.sync_me()))
122 .map(|(pos, e, _, _)| (pos, e.id()))
123 .collect::<Vec<_>>()
124 {
125 // Add entity
126 self.tracked_entities.add(id);
127 self.add_entity(id, pos.0.map(|e| e as i32), None);
128 }
129
130 let mut regions_to_remove = Vec::new();
131
132 self.regions
133 .iter()
134 .enumerate()
135 .for_each(|(i, (¤t_region, region_data))| {
136 for (maybe_pos, _maybe_vel, maybe_presence, id) in (
137 pos.maybe(),
138 vel.maybe(),
139 presence.maybe(),
140 ®ion_data.bitset,
141 )
142 .join()
143 {
144 // Entity should already be removed from region bitset if deleted.
145 debug_assert!(entities.is_alive(entities.entity(id)));
146
147 let should_sync = maybe_presence.is_none_or(|p| p.kind.sync_me());
148 match maybe_pos {
149 // Switch regions for entities which need switching
150 // TODO don't check every tick (use velocity) (and use id to stagger)
151 // Starting parameters at v = 0 check every 100 ticks
152 // tether_length^2 / vel^2 (with a max of every tick)
153 Some(pos) if should_sync => {
154 let pos = pos.0.map(|e| e as i32);
155 let key = Self::pos_key(pos);
156 // Consider switching
157 // Calculate distance outside border
158 if key != current_region
159 && (Vec2::<i32>::from(pos) - Self::key_pos(current_region))
160 .map(|e| e.unsigned_abs())
161 .reduce_max()
162 > TETHER_LENGTH
163 {
164 // Switch
165 self.entities_to_move.push((i, id, pos));
166 }
167 },
168 // Remove any non-existant entities (or just ones that lost their position
169 // component) TODO: distribute this between ticks
170 None | Some(_) => {
171 // TODO: shouldn't there be a way to extract the bitset of entities with
172 // positions directly from specs? Yes, with `.mask()` on the component
173 // storage.
174 self.entities_to_remove.push((i, id));
175 },
176 }
177 }
178
179 // Remove region if it is empty and has no events
180 // TODO: distribute this between ticks
181 if region_data.removable() {
182 regions_to_remove.push(current_region);
183 }
184 });
185
186 // Mutate
187 // Note entity moving is outside the whole loop so that the same entity is not
188 // checked twice (this may be fine though...)
189 while let Some((i, id, pos)) = self.entities_to_move.pop() {
190 // Remove from old region.
191 let (prev_key, region) = self.regions.get_index_mut(i).map(|(k, v)| (*k, v)).unwrap();
192 region.remove(id, Some(Self::pos_key(pos)));
193 // Add to new region.
194 self.add_entity(id, pos, Some(prev_key));
195 }
196 for (i, id) in self.entities_to_remove.drain(..) {
197 self.regions
198 .get_index_mut(i)
199 .map(|(_, v)| v)
200 .unwrap()
201 .remove(id, None);
202 self.tracked_entities.remove(id);
203 }
204 for key in regions_to_remove.into_iter() {
205 // Check that the region is still removable
206 if self.regions.get(&key).unwrap().removable() {
207 // Note we have to use key's here since the index can change when others are
208 // removed
209 self.regions.swap_remove(&key);
210 }
211 }
212 }
213
214 /// Must be called immediately after succesfully deleting an entity from the
215 /// ecs (i.e. when deleting the entity did not generate a WrongGeneration
216 /// error).
217 ///
218 /// Returns the region key if this entity was tracked in a region.
219 pub fn entity_deleted(&mut self, entity: specs::Entity) -> Option<Vec2<i32>> {
220 let id = entity.id();
221 let was_present = self.tracked_entities.remove(id);
222 if was_present {
223 // To catch bugs, replace with dummy key.
224 let region_key = core::mem::replace(
225 &mut self.entity_to_region[id as usize],
226 Vec2::from(i32::MAX),
227 );
228 self.regions
229 .get_mut(®ion_key)
230 .expect("Region must be present if entity was in `tracked_entities`")
231 .remove(id, None);
232 Some(region_key)
233 } else {
234 None
235 }
236 }
237
238 /// Returns index of the region that the entity is added to.
239 fn add_entity(&mut self, id: u32, pos: Vec3<i32>, from: Option<Vec2<i32>>) {
240 let key = Self::pos_key(pos);
241 // Add to region
242 self.regions.entry(key).or_default().add(id, from);
243
244 // Add to or update map from entity to region.
245 let id = usize::try_from(id).expect("16 bit usize not supported");
246 if self.entity_to_region.len() <= id {
247 self.entity_to_region.resize(id + 1, Vec2::from(i32::MAX));
248 }
249 self.entity_to_region[id] = key;
250 }
251
252 fn pos_key<P: Into<Vec2<i32>>>(pos: P) -> Vec2<i32> { pos.into().map(|e| e >> REGION_LOG2) }
253
254 pub fn key_pos(key: Vec2<i32>) -> Vec2<i32> { key.map(|e| e << REGION_LOG2) }
255
256 /// Checks if this entity is located in the `RegionMap`.
257 pub fn in_region_map(&self, entity: specs::Entity) -> bool {
258 self.tracked_entities.contains(entity.id())
259 }
260
261 /// Returns a region given a key.
262 pub fn get(&self, key: Vec2<i32>) -> Option<&Region> { self.regions.get(&key) }
263
264 /// Returns an iterator of (Position, Region).
265 pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &Region)> {
266 self.regions.iter().map(|(key, r)| (*key, r))
267 }
268}
269
270/// Note vd is in blocks in this case
271pub fn region_in_vd(key: Vec2<i32>, pos: Vec3<f32>, vd: f32) -> bool {
272 let vd_extended = vd + TETHER_LENGTH as f32 * 2.0f32.sqrt();
273
274 let min_region_pos = RegionMap::key_pos(key).map(|e| e as f32);
275 // Should be diff to closest point on the square (which can be in the middle of
276 // an edge)
277 let diff = (min_region_pos - Vec2::from(pos)).map(|e| {
278 if e < 0.0 {
279 (e + REGION_SIZE as f32).min(0.0)
280 } else {
281 e
282 }
283 });
284
285 diff.magnitude_squared() < vd_extended.powi(2)
286}
287
288// Note vd is in blocks in this case
289pub fn regions_in_vd(pos: Vec3<f32>, vd: f32) -> HashSet<Vec2<i32>> {
290 let mut set = HashSet::new();
291
292 let pos_xy = Vec2::<f32>::from(pos);
293 let vd_extended = vd + TETHER_LENGTH as f32 * 2.0f32.sqrt();
294
295 let max = RegionMap::pos_key(pos_xy.map(|e| (e + vd_extended) as i32));
296 let min = RegionMap::pos_key(pos_xy.map(|e| (e - vd_extended) as i32));
297
298 for x in min.x..max.x + 1 {
299 for y in min.y..max.y + 1 {
300 let key = Vec2::new(x, y);
301
302 if region_in_vd(key, pos, vd) {
303 set.insert(key);
304 }
305 }
306 }
307
308 set
309}
310// Iterator designed for use in collision systems
311// Iterates through all regions yielding them along with half of their neighbors
312// ..................
313
314/*fn interleave_i32_with_zeros(mut x: i32) -> i64 {
315 x = (x ^ (x << 16)) & 0x0000ffff0000ffff;
316 x = (x ^ (x << 8)) & 0x00ff00ff00ff00ff;
317 x = (x ^ (x << 4)) & 0x0f0f0f0f0f0f0f0f;
318 x = (x ^ (x << 2)) & 0x3333333333333333;
319 x = (x ^ (x << 1)) & 0x5555555555555555;
320 x
321}
322
323fn morton_code(pos: Vec2<i32>) -> i64 {
324 interleave_i32_with_zeros(pos.x) | (interleave_i32_with_zeros(pos.y) << 1)
325}*/