mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Various minor optimisations
This commit is contained in:
parent
8800bdbef6
commit
3c54e63592
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6729,6 +6729,7 @@ dependencies = [
|
||||
"ordered-float 2.10.0",
|
||||
"petgraph 0.6.2",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rayon",
|
||||
"ron 0.7.1",
|
||||
"roots",
|
||||
|
@ -82,6 +82,7 @@ specs = { version = "0.18", features = ["serde", "storage-event-control", "night
|
||||
[dev-dependencies]
|
||||
#bench
|
||||
criterion = "0.3"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
#test
|
||||
tracing-subscriber = { version = "0.3.7", default-features = false, features = ["fmt", "time", "ansi", "smallvec", "env-filter"] }
|
||||
|
@ -124,6 +124,27 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("for_each_in", |b| {
|
||||
use rand::prelude::*;
|
||||
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(thread_rng().gen());
|
||||
b.iter(|| {
|
||||
let pos = Vec3::new(
|
||||
rng.gen_range(0..TerrainChunk::RECT_SIZE.x as i32 - 3),
|
||||
rng.gen_range(0..TerrainChunk::RECT_SIZE.x as i32 - 3),
|
||||
rng.gen_range(MIN_Z..MAX_Z - 6),
|
||||
);
|
||||
chunk.for_each_in(
|
||||
Aabb {
|
||||
min: pos,
|
||||
max: pos + Vec3::new(3, 3, 6),
|
||||
},
|
||||
|pos, vox| {
|
||||
black_box((pos, vox));
|
||||
},
|
||||
);
|
||||
})
|
||||
});
|
||||
black_box(chunk);
|
||||
}
|
||||
|
||||
|
@ -181,6 +181,49 @@ impl<V, S: RectVolSize, M: Clone> ReadVol for Chonk<V, S, M> {
|
||||
.map_err(Self::Error::SubChunkError)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_unchecked(&self, pos: Vec3<i32>) -> &V {
|
||||
if pos.z < self.get_min_z() {
|
||||
// Below the terrain
|
||||
&self.below
|
||||
} else if pos.z >= self.get_max_z() {
|
||||
// Above the terrain
|
||||
&self.above
|
||||
} else {
|
||||
// Within the terrain
|
||||
let sub_chunk_idx = self.sub_chunk_idx(pos.z);
|
||||
let rpos = pos
|
||||
- Vec3::unit_z()
|
||||
* (self.z_offset + sub_chunk_idx * SubChunkSize::<S>::SIZE.z as i32);
|
||||
self.sub_chunks[sub_chunk_idx as usize]
|
||||
.get_unchecked(rpos)
|
||||
}
|
||||
}
|
||||
|
||||
fn for_each_in(&self, aabb: Aabb<i32>, mut f: impl FnMut(Vec3<i32>, Self::Vox))
|
||||
where
|
||||
Self::Vox: Copy,
|
||||
{
|
||||
let idx = self.sub_chunk_idx(aabb.min.z);
|
||||
// Special-case for the AABB being entirely within a single sub-chunk as this is very common.
|
||||
if idx == self.sub_chunk_idx(aabb.max.z) && idx >= 0 && idx < self.sub_chunks.len() as i32 {
|
||||
let sub_chunk = &self.sub_chunks[idx as usize];
|
||||
let z_off = self.z_offset + idx * SubChunkSize::<S>::SIZE.z as i32;
|
||||
sub_chunk.for_each_in(
|
||||
Aabb { min: aabb.min.with_z(aabb.min.z - z_off), max: aabb.max.with_z(aabb.max.z - z_off) },
|
||||
|pos, vox| f(pos.with_z(pos.z + z_off), vox),
|
||||
);
|
||||
} else {
|
||||
for z in aabb.min.z..aabb.max.z + 1 {
|
||||
for y in aabb.min.y..aabb.max.y + 1 {
|
||||
for x in aabb.min.x..aabb.max.x + 1 {
|
||||
f(Vec3::new(x, y, z), *self.get_unchecked(Vec3::new(x, y, z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Clone + PartialEq, S: RectVolSize, M: Clone> WriteVol for Chonk<V, S, M> {
|
||||
|
20
common/src/util/grid_hasher.rs
Normal file
20
common/src/util/grid_hasher.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::hash::{Hasher, BuildHasher};
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct GridHasher(u64);
|
||||
|
||||
// It's extremely unlikely that the spatial grid can be used to viably DOS the server given that clients only have
|
||||
// control over their player and a handful of entities in their near vicinity. For this reason, we just use an xor
|
||||
// hash, which should keep collisions relatively low since the spatial coherence of the grid is distributed fairly
|
||||
// evenly with the output of the hash function.
|
||||
impl Hasher for GridHasher {
|
||||
fn finish(&self) -> u64 { self.0 }
|
||||
fn write(&mut self, _: &[u8]) { panic!("Hashing arbitrary bytes is unimplemented"); }
|
||||
fn write_i32(&mut self, x: i32) { self.0 = self.0.wrapping_mul(113989) ^ self.0 ^ x as u64; }
|
||||
}
|
||||
|
||||
impl BuildHasher for GridHasher {
|
||||
type Hasher = Self;
|
||||
|
||||
fn build_hasher(&self) -> Self::Hasher { *self }
|
||||
}
|
@ -7,6 +7,7 @@ pub mod projection;
|
||||
/// Contains [`SpatialGrid`] which is useful for accelerating queries of nearby
|
||||
/// entities
|
||||
mod spatial_grid;
|
||||
mod grid_hasher;
|
||||
|
||||
pub const GIT_VERSION_BUILD: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
|
||||
pub const GIT_TAG_BUILD: &str = include_str!(concat!(env!("OUT_DIR"), "/gittag"));
|
||||
@ -39,3 +40,4 @@ pub use option::either_with;
|
||||
pub use plane::Plane;
|
||||
pub use projection::Projection;
|
||||
pub use spatial_grid::SpatialGrid;
|
||||
pub use grid_hasher::GridHasher;
|
||||
|
@ -1,11 +1,12 @@
|
||||
use vek::*;
|
||||
use super::GridHasher;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpatialGrid {
|
||||
// Uses two scales of grids so that we can have a hard limit on how far to search in the
|
||||
// smaller grid
|
||||
grid: hashbrown::HashMap<Vec2<i32>, Vec<specs::Entity>>,
|
||||
large_grid: hashbrown::HashMap<Vec2<i32>, Vec<specs::Entity>>,
|
||||
grid: hashbrown::HashMap<Vec2<i32>, Vec<specs::Entity>, GridHasher>,
|
||||
large_grid: hashbrown::HashMap<Vec2<i32>, Vec<specs::Entity>, GridHasher>,
|
||||
// Log base 2 of the cell size of the spatial grid
|
||||
lg2_cell_size: usize,
|
||||
// Log base 2 of the cell size of the large spatial grid
|
||||
@ -53,7 +54,7 @@ impl SpatialGrid {
|
||||
/// NOTE: for best optimization of the iterator use
|
||||
/// `for_each` rather than a for loop.
|
||||
pub fn in_aabr<'a>(&'a self, aabr: Aabr<i32>) -> impl Iterator<Item = specs::Entity> + 'a {
|
||||
let iter = |max_entity_radius, grid: &'a hashbrown::HashMap<_, _>, lg2_cell_size| {
|
||||
let iter = |max_entity_radius, grid: &'a hashbrown::HashMap<_, _, _>, lg2_cell_size| {
|
||||
// Add buffer for other entity radius
|
||||
let min = aabr.min - max_entity_radius as i32;
|
||||
let max = aabr.max + max_entity_radius as i32;
|
||||
|
@ -98,6 +98,10 @@ pub trait ReadVol: BaseVol {
|
||||
/// Get a reference to the voxel at the provided position in the volume.
|
||||
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Error>;
|
||||
|
||||
/// Get a reference to the voxel at the provided position in the volume. Many volumes provide a fast path,
|
||||
/// provided the position is always in-bounds. Note that this function is still safe.
|
||||
fn get_unchecked(&self, pos: Vec3<i32>) -> &Self::Vox { self.get(pos).unwrap() }
|
||||
|
||||
/// NOTE: By default, this ray will simply run from `from` to `to` without
|
||||
/// stopping. To make something interesting happen, call `until` or
|
||||
/// `for_each`.
|
||||
|
@ -307,6 +307,28 @@ impl<V, S: VolSize, M> ReadVol for Chunk<V, S, M> {
|
||||
Ok(self.get_unchecked(pos))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_unchecked(&self, pos: Vec3<i32>) -> &Self::Vox {
|
||||
self.get_unchecked(pos)
|
||||
}
|
||||
|
||||
fn for_each_in(&self, mut aabb: Aabb<i32>, mut f: impl FnMut(Vec3<i32>, Self::Vox))
|
||||
where
|
||||
Self::Vox: Copy,
|
||||
{
|
||||
aabb.intersect(Aabb {
|
||||
min: Vec3::zero(),
|
||||
max: S::SIZE.map(|e| e as i32) - 1,
|
||||
});
|
||||
for z in aabb.min.z..aabb.max.z + 1 {
|
||||
for y in aabb.min.y..aabb.max.y + 1 {
|
||||
for x in aabb.min.x..aabb.max.x + 1 {
|
||||
f(Vec3::new(x, y, z), *self.get_unchecked(Vec3::new(x, y, z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Clone + PartialEq, S: VolSize, M> WriteVol for Chunk<V, S, M> {
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
terrain::MapSizeLg,
|
||||
vol::{BaseVol, ReadVol, RectRasterableVol, SampleVol, WriteVol},
|
||||
volumes::dyna::DynaError,
|
||||
util::GridHasher,
|
||||
};
|
||||
use hashbrown::{hash_map, HashMap};
|
||||
use std::{fmt::Debug, ops::Deref, sync::Arc};
|
||||
@ -24,7 +25,7 @@ pub struct VolGrid2d<V: RectRasterableVol> {
|
||||
map_size_lg: MapSizeLg,
|
||||
/// Default voxel for use outside of max map bounds.
|
||||
default: Arc<V>,
|
||||
chunks: HashMap<Vec2<i32>, Arc<V>>,
|
||||
chunks: HashMap<Vec2<i32>, Arc<V>, GridHasher>,
|
||||
}
|
||||
|
||||
impl<V: RectRasterableVol> VolGrid2d<V> {
|
||||
@ -64,9 +65,10 @@ impl<V: RectRasterableVol + ReadVol + Debug> ReadVol for VolGrid2d<V> {
|
||||
let ck = Self::chunk_key(pos);
|
||||
self.get_key(ck)
|
||||
.ok_or(VolGrid2dError::NoSuchChunk)
|
||||
.and_then(|chunk| {
|
||||
.map(|chunk| {
|
||||
let co = Self::chunk_offs(pos);
|
||||
chunk.get(co).map_err(VolGrid2dError::ChunkError)
|
||||
// Always within bounds of the chunk, so we can use the get_unchecked form
|
||||
chunk.get_unchecked(co)
|
||||
})
|
||||
}
|
||||
|
||||
@ -271,7 +273,7 @@ impl<'a, V: RectRasterableVol + ReadVol> CachedVolGrid2d<'a, V> {
|
||||
chunk
|
||||
};
|
||||
let co = VolGrid2d::<V>::chunk_offs(pos);
|
||||
chunk.get(co).map_err(VolGrid2dError::ChunkError)
|
||||
Ok(chunk.get_unchecked(co))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1460,11 +1460,9 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
// Calculate the world space near aabb
|
||||
let near_aabb = move_aabb(near_aabb, pos.0);
|
||||
let player_overlap = |block_aabb: Aabb<f32>| {
|
||||
ordered_float::OrderedFloat(
|
||||
(block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5)
|
||||
.map(f32::abs)
|
||||
.sum(),
|
||||
)
|
||||
(block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5)
|
||||
.map(f32::abs)
|
||||
.sum()
|
||||
};
|
||||
|
||||
terrain.for_each_in(near_aabb, |block_pos, block| {
|
||||
@ -1479,19 +1477,16 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
|
||||
// Determine whether the block's AABB collides with the player's AABB
|
||||
if block_aabb.collides_with_aabb(player_aabb) {
|
||||
most_colliding = match most_colliding {
|
||||
match &most_colliding {
|
||||
// Select the minimum of the value from `player_overlap`
|
||||
other @ Some((_, other_block_aabb, _))
|
||||
Some((_, other_block_aabb, _))
|
||||
if {
|
||||
// TODO: comment below is outdated (as of ~1 year ago)
|
||||
// Find the maximum of the minimum collision axes (this bit
|
||||
// is weird, trust me that it works)
|
||||
player_overlap(block_aabb) >= player_overlap(other_block_aabb)
|
||||
} =>
|
||||
{
|
||||
other
|
||||
},
|
||||
_ => Some((block_pos, block_aabb, block)),
|
||||
player_overlap(block_aabb) >= player_overlap(*other_block_aabb)
|
||||
} => {},
|
||||
_ => most_colliding = Some((block_pos, block_aabb, block)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user