Skip to content

Commit bea84f0

Browse files
committed
Do population stage 0 in parallel
Make HashMap extension fn like get_many_mut but for variable sizes See also rust-lang/hashbrown#332
1 parent c214c01 commit bea84f0

File tree

8 files changed

+86
-45
lines changed

8 files changed

+86
-45
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::{
2+
collections::HashMap,
3+
hash::{BuildHasher, Hash},
4+
};
5+
6+
use itertools::Itertools;
7+
8+
/// Workaround for <https://github.com/rust-lang/hashbrown/issues/332>
9+
#[allow(clippy::missing_safety_doc)]
10+
pub trait HashMapExt {
11+
type K;
12+
type V;
13+
14+
unsafe fn get_many_var_unchecked_mut(&mut self, keys: &[Self::K]) -> Option<Vec<&mut Self::V>>;
15+
fn get_many_var_mut(&mut self, keys: &[Self::K]) -> Option<Vec<&mut Self::V>>;
16+
}
17+
18+
impl<K: Eq + Hash, V, S: BuildHasher> HashMapExt for HashMap<K, V, S> {
19+
type K = K;
20+
type V = V;
21+
22+
unsafe fn get_many_var_unchecked_mut(&mut self, keys: &[K]) -> Option<Vec<&mut V>> {
23+
let out = keys
24+
.iter()
25+
.map(|k| self.get_mut(k).map(|v| &mut *(v as &mut _ as *mut _)))
26+
.collect();
27+
out
28+
}
29+
30+
fn get_many_var_mut(&mut self, keys: &[K]) -> Option<Vec<&mut V>> {
31+
let unique = keys.iter().duplicates().next().is_none();
32+
if unique {
33+
unsafe { self.get_many_var_unchecked_mut(keys) }
34+
} else {
35+
None
36+
}
37+
}
38+
}

fs_common/src/game/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod networking;
22
pub mod world;
33

44
pub mod cli;
5+
pub mod hashmap_ext;
56
mod settings;
67
use std::ops::Range;
78

fs_common/src/game/common/world/chunk.rs

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::game::common::hashmap_ext::HashMapExt;
12
use crate::game::common::world::gen::populator::ChunkContext;
23
use crate::game::common::world::particle::ParticleSystem;
34
use crate::game::common::world::simulator::Simulator;
@@ -128,7 +129,7 @@ struct ChunkSaveFormat {
128129
colors: Vec<u8>,
129130
}
130131

131-
impl<C: Chunk> ChunkHandlerGeneric for ChunkHandler<C> {
132+
impl<C: Chunk + Send> ChunkHandlerGeneric for ChunkHandler<C> {
132133
#[profiling::function]
133134
fn update_chunk_graphics(&mut self) {
134135
let keys = self.loaded_chunks.keys().copied().collect::<Vec<u32>>();
@@ -498,22 +499,30 @@ impl<C: Chunk> ChunkHandlerGeneric for ChunkHandler<C> {
498499
})
499500
.collect()
500501
};
501-
for ch in generated {
502-
profiling::scope!("finish chunk");
503-
504-
// TODO: this is very slow
505-
506-
let chunk = self.loaded_chunks.get_mut(ch.0).unwrap();
507-
chunk.set_pixels(*ch.1);
508-
chunk.set_pixel_colors(*ch.2);
509-
510-
// TODO: populating stage 0 should be able to be multithreaded
511-
self.generator.populators().populate(
512-
0,
513-
&mut [chunk as &mut dyn Chunk],
514-
seed,
515-
registries,
516-
);
502+
503+
let keys: Vec<_> = generated
504+
.into_iter()
505+
.map(|ch| {
506+
profiling::scope!("finish chunk");
507+
508+
let chunk = self.loaded_chunks.get_mut(ch.0).unwrap();
509+
chunk.set_pixels(*ch.1);
510+
chunk.set_pixel_colors(*ch.2);
511+
512+
*ch.0
513+
})
514+
.collect();
515+
516+
let pops = self.generator.populators();
517+
{
518+
profiling::scope!("populate stage 0");
519+
self.loaded_chunks
520+
.get_many_var_mut(&keys)
521+
.unwrap()
522+
.into_par_iter()
523+
.for_each(|chunk| {
524+
pops.populate(0, &mut [chunk as &mut dyn Chunk], seed, registries);
525+
});
517526
}
518527
}
519528
}
@@ -596,36 +605,31 @@ impl<C: Chunk> ChunkHandlerGeneric for ChunkHandler<C> {
596605
_ => false,
597606
}
598607
}) {
599-
let mut chunks = Vec::new();
608+
let mut keys = Vec::new();
600609

601610
let range = i32::from(cur_stage + 1);
602611

603612
// try to gather the nearby chunks needed to populate this one
604-
let mut failed = false;
605-
'outer: for y in -range..=range {
613+
for y in -range..=range {
606614
for x in -range..=range {
607-
let c = self
608-
.loaded_chunks
609-
.get_mut(&chunk_index(chunk_x + x, chunk_y + y));
610-
if let Some(c) = c {
611-
let c = c as &mut dyn Chunk as *mut _;
612-
// this is just blatantly bypassing the borrow checker to get mutable refs to multiple unique hashmap entries
613-
// TODO: can probably use get_many_mut once stabilized: https://github.com/rust-lang/rust/issues/97601
614-
chunks.push(unsafe { &mut *c });
615-
} else {
616-
failed = true;
617-
break 'outer;
618-
}
615+
keys.push(chunk_index(chunk_x + x, chunk_y + y));
619616
}
620617
}
621618

619+
let chunks = self.loaded_chunks.get_many_var_mut(&keys);
620+
622621
// if we failed to get all nearby chunks, don't populate and don't go to the next stage
623-
if !failed {
622+
if let Some(chunks) = chunks {
623+
let mut chunks_dyn: Vec<_> = chunks
624+
.into_iter()
625+
.map(|c| c as &mut dyn Chunk)
626+
.collect();
627+
624628
// TODO: make not hardcoded
625629

626630
if cur_stage + 1 == 1 {
627631
let mut ctx =
628-
ChunkContext::<1>::new(&mut chunks).unwrap();
632+
ChunkContext::<1>::new(&mut chunks_dyn).unwrap();
629633
let mut rng = StdRng::seed_from_u64(
630634
seed as u64
631635
+ u64::from(chunk_index(
@@ -640,7 +644,7 @@ impl<C: Chunk> ChunkHandlerGeneric for ChunkHandler<C> {
640644

641645
self.generator.populators().populate(
642646
cur_stage + 1,
643-
&mut chunks,
647+
&mut chunks_dyn,
644648
seed,
645649
registries,
646650
);
@@ -742,7 +746,6 @@ impl<C: Chunk> ChunkHandlerGeneric for ChunkHandler<C> {
742746
.as_mut()
743747
.unwrap();
744748
// blatantly bypassing the borrow checker, see safety comment above
745-
// TODO: can probably use get_many_mut once stabilized: https://github.com/rust-lang/rust/issues/97601
746749
unsafe { &mut *raw }
747750
});
748751

@@ -766,7 +769,6 @@ impl<C: Chunk> ChunkHandlerGeneric for ChunkHandler<C> {
766769
.unwrap()
767770
.get_colors_mut();
768771
// blatantly bypassing the borrow checker, see safety comment above
769-
// TODO: can probably use get_many_mut once stabilized: https://github.com/rust-lang/rust/issues/97601
770772
unsafe { &mut *raw }
771773
});
772774

fs_common/src/game/common/world/copy_paste.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ impl MaterialBuf {
2222
}
2323
}
2424

25-
pub fn copy<C: Chunk>(
25+
pub fn copy<C: Chunk + Send>(
2626
chunk_handler: &ChunkHandler<C>,
2727
x: impl Into<i64>,
2828
y: impl Into<i64>,
@@ -47,7 +47,7 @@ impl MaterialBuf {
4747
Ok(Self { width, height, materials: buf })
4848
}
4949

50-
pub fn cut<C: Chunk>(
50+
pub fn cut<C: Chunk + Send>(
5151
chunk_handler: &mut ChunkHandler<C>,
5252
x: impl Into<i64>,
5353
y: impl Into<i64>,
@@ -73,7 +73,7 @@ impl MaterialBuf {
7373
Ok(Self { width, height, materials: buf })
7474
}
7575

76-
pub fn paste<C: Chunk>(
76+
pub fn paste<C: Chunk + Send>(
7777
&self,
7878
chunk_handler: &mut ChunkHandler<C>,
7979
x: impl Into<i64>,

fs_common/src/game/common/world/simulator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ struct SimulationHelperRigidBody<'a, C: Chunk> {
233233
physics: &'a mut Physics,
234234
}
235235

236-
impl<C: Chunk> SimulationHelper for SimulationHelperRigidBody<'_, C> {
236+
impl<C: Chunk + Send> SimulationHelper for SimulationHelperRigidBody<'_, C> {
237237
fn get_pixel_local(&self, x: i32, y: i32) -> MaterialInstance {
238238
let world_mat = self.chunk_handler.get(i64::from(x), i64::from(y)); // TODO: consider changing the args to i64
239239
if let Ok(m) = world_mat {
@@ -402,7 +402,7 @@ impl Simulator {
402402
#[allow(clippy::unnecessary_unwrap)]
403403
#[allow(clippy::needless_range_loop)]
404404
#[profiling::function]
405-
pub fn simulate_rigidbodies<C: Chunk>(
405+
pub fn simulate_rigidbodies<C: Chunk + Send>(
406406
chunk_handler: &mut ChunkHandler<C>,
407407
rigidbodies: &mut Vec<FSRigidBody>,
408408
physics: &mut Physics,

fs_common/src/game/common/world/world.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub struct World<C: Chunk> {
5555
pub seed: i32,
5656
}
5757

58-
impl<C: Chunk> World<C> {
58+
impl<C: Chunk + Send> World<C> {
5959
#[profiling::function]
6060
pub fn create(path: Option<PathBuf>, seed: Option<i32>) -> Self {
6161
let mut ecs = specs::World::new();

fs_common/src/game/common/world/world_loading.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub struct WorldMeta {
2020
pub last_played_time: toml::value::Datetime,
2121
}
2222

23-
impl<C: Chunk> World<C> {
23+
impl<C: Chunk + Send> World<C> {
2424
pub fn find_files(root: PathBuf) -> Result<WorldTreeNode<PathBuf, PathBuf>, std::io::Error> {
2525
let mut res = Vec::new();
2626
for entry in fs::read_dir(&root)? {

fs_common/src/game/game.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub struct FPSCounter {
6262
pub tick_physics_times: [f32; 200],
6363
}
6464

65-
impl<C: Chunk> GameData<C> {
65+
impl<C: Chunk + Send> GameData<C> {
6666
#[profiling::function]
6767
pub fn new(file_helper: FileHelper, build_data: BuildData) -> Self {
6868
GameData {

0 commit comments

Comments
 (0)