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, (&current_region, region_data))| {
136                for (maybe_pos, _maybe_vel, maybe_presence, id) in (
137                    pos.maybe(),
138                    vel.maybe(),
139                    presence.maybe(),
140                    &region_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(&region_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}*/