diff --git a/Cargo.lock b/Cargo.lock index 458eb85209..385f69d304 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5607,6 +5607,7 @@ version = "0.9.0" dependencies = [ "bincode", "hashbrown", + "num_cpus", "rayon", "scopeguard", "serde", @@ -5766,6 +5767,7 @@ dependencies = [ "clap", "crossterm 0.19.0", "lazy_static", + "num_cpus", "ron", "serde", "signal-hook 0.3.8", @@ -5821,6 +5823,7 @@ dependencies = [ "old_school_gfx_glutin_ext", "ordered-float 2.1.1", "rand 0.8.3", + "rayon", "rodio", "ron", "serde", diff --git a/common/src/consts.rs b/common/src/consts.rs index 2e4897696b..f030d9742a 100644 --- a/common/src/consts.rs +++ b/common/src/consts.rs @@ -16,3 +16,7 @@ pub const WATER_DENSITY: f32 = 999.1026; pub const IRON_DENSITY: f32 = 7870.0; // pub const HUMAN_DENSITY: f32 = 1010.0; // real value pub const HUMAN_DENSITY: f32 = 990.0; // value we use to make humanoids gently float +// 1 thread might be used for long-running cpu intensive tasks, like chunk +// generation. having at least 2 helps not blocking in the main tick here +pub const MIN_RECOMMENDED_RAYON_THREADS: usize = 2; +pub const MIN_RECOMMENDED_TOKIO_THREADS: usize = 2; diff --git a/common/state/Cargo.toml b/common/state/Cargo.toml index 26c2cec587..74b42220ce 100644 --- a/common/state/Cargo.toml +++ b/common/state/Cargo.toml @@ -18,6 +18,7 @@ common-ecs = { package = "veloren-common-ecs", path = "../ecs" } common-base = { package = "veloren-common-base", path = "../base" } rayon = "1.5" +num_cpus = "1.0" tracing = { version = "0.1", default-features = false } vek = { version = "=0.14.1", features = ["serde"] } diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 9031c57b55..236e5996cb 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -103,6 +103,7 @@ impl State { let thread_pool = Arc::new( ThreadPoolBuilder::new() + .num_threads(num_cpus::get().max(common::consts::MIN_RECOMMENDED_RAYON_THREADS)) .thread_name(move |i| format!("rayon-{}-{}", thread_name_infix, i)) .build() .unwrap(), @@ -208,8 +209,8 @@ impl State { ecs.insert(Vec::::new()); ecs.insert(common::CachedSpatialGrid::default()); - let slow_limit = thread_pool.current_num_threads().max(2) as u64; - let slow_limit = slow_limit / 2 + slow_limit / 4; + let num_cpu = num_cpus::get() as u64; + let slow_limit = (num_cpu / 2 + num_cpu / 4).max(1); tracing::trace!(?slow_limit, "Slow Thread limit"); ecs.insert(SlowJobPool::new(slow_limit, Arc::clone(&thread_pool))); @@ -321,6 +322,8 @@ impl State { /// Get a mutable reference to the internal ECS world. pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs } + pub fn thread_pool(&self) -> &Arc { &self.thread_pool } + /// Get a reference to the `TerrainChanges` structure of the state. This /// contains information about terrain state that has changed since the /// last game tick. diff --git a/server-cli/Cargo.toml b/server-cli/Cargo.toml index 6041be5e66..59217755b3 100644 --- a/server-cli/Cargo.toml +++ b/server-cli/Cargo.toml @@ -28,6 +28,7 @@ common-net = { package = "veloren-common-net", path = "../common/net" } common-frontend = { package = "veloren-common-frontend", path = "../common/frontend" } tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] } +num_cpus = "1.0" ansi-parser = "0.7" clap = "2.33" crossterm = "0.19" diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs index 39dbe97a9f..c8d1a6ac39 100644 --- a/server-cli/src/main.rs +++ b/server-cli/src/main.rs @@ -14,7 +14,7 @@ use crate::{ cmd::Message, shutdown_coordinator::ShutdownCoordinator, tui_runner::Tui, tuilog::TuiLog, }; use clap::{App, Arg, SubCommand}; -use common::clock::Clock; +use common::{clock::Clock, consts::MIN_RECOMMENDED_TOKIO_THREADS}; use common_base::span; use core::sync::atomic::{AtomicUsize, Ordering}; use server::{ @@ -117,9 +117,13 @@ fn main() -> io::Result<()> { path }; + // We don't need that many threads in the async pool, at least 2 but generally + // 25% of all available will do + // TODO: evaluate std::thread::available_concurrency as a num_cpus replacement let runtime = Arc::new( tokio::runtime::Builder::new_multi_thread() .enable_all() + .worker_threads((num_cpus::get() / 4).max(MIN_RECOMMENDED_TOKIO_THREADS)) .thread_name_fn(|| { static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); diff --git a/server/src/lib.rs b/server/src/lib.rs index 31f44535b1..6d41d226ad 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -282,21 +282,26 @@ impl Server { state.ecs_mut().insert(AliasValidator::new(banned_words)); #[cfg(feature = "worldgen")] - let (world, index) = World::generate(settings.world_seed, WorldOpts { - seed_elements: true, - world_file: if let Some(ref opts) = settings.map_file { - opts.clone() - } else { - // Load default map from assets. - FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()) + let (world, index) = World::generate( + settings.world_seed, + WorldOpts { + seed_elements: true, + world_file: if let Some(ref opts) = settings.map_file { + opts.clone() + } else { + // Load default map from assets. + FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()) + }, + ..WorldOpts::default() }, - ..WorldOpts::default() - }); + &state.thread_pool(), + ); + #[cfg(feature = "worldgen")] - let map = world.get_map_data(index.as_index_ref()); + let map = world.get_map_data(index.as_index_ref(), &state.thread_pool()); #[cfg(not(feature = "worldgen"))] - let (world, index) = World::generate(settings.world_seed); + let (world, index) = World::generate(settings.world_seed, &state.thread_pool()); #[cfg(not(feature = "worldgen"))] let map = WorldMapMsg { dimensions_lg: Vec2::zero(), diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 374431b9eb..80787cdea2 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -123,6 +123,7 @@ winres = "0.1" criterion = "0.3" git2 = "0.13" world = {package = "veloren-world", path = "../world"} +rayon = "1.5.0" [[bench]] harness = false diff --git a/voxygen/benches/meshing_benchmark.rs b/voxygen/benches/meshing_benchmark.rs index a203913312..0d1f0d135a 100644 --- a/voxygen/benches/meshing_benchmark.rs +++ b/voxygen/benches/meshing_benchmark.rs @@ -13,16 +13,21 @@ const GEN_SIZE: i32 = 4; #[allow(clippy::needless_update)] // TODO: Pending review in #587 pub fn criterion_benchmark(c: &mut Criterion) { + let pool = rayon::ThreadPoolBuilder::new().build().unwrap(); // Generate chunks here to test let mut terrain = TerrainGrid::new().unwrap(); - let (world, index) = World::generate(42, sim::WorldOpts { - // NOTE: If this gets too expensive, we can turn it off. - // TODO: Consider an option to turn off all erosion as well, or even provide altitude - // directly with a closure. - seed_elements: true, - world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), - ..Default::default() - }); + let (world, index) = World::generate( + 42, + sim::WorldOpts { + // NOTE: If this gets too expensive, we can turn it off. + // TODO: Consider an option to turn off all erosion as well, or even provide altitude + // directly with a closure. + seed_elements: true, + world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()), + ..Default::default() + }, + &pool, + ); let index = index.as_index_ref(); (0..GEN_SIZE) .flat_map(|x| (0..GEN_SIZE).map(move |y| Vec2::new(x, y))) diff --git a/voxygen/src/menu/main/client_init.rs b/voxygen/src/menu/main/client_init.rs index 6cbe4ee2bc..de69abc5a5 100644 --- a/voxygen/src/menu/main/client_init.rs +++ b/voxygen/src/menu/main/client_init.rs @@ -3,6 +3,7 @@ use client::{ error::{Error as ClientError, NetworkConnectError, NetworkError}, Client, ServerInfo, }; +use common::consts::MIN_RECOMMENDED_TOKIO_THREADS; use crossbeam::channel::{unbounded, Receiver, Sender, TryRecvError}; use std::{ sync::{ @@ -62,11 +63,12 @@ impl ClientInit { let cancel2 = Arc::clone(&cancel); let runtime = runtime.unwrap_or_else(|| { + // TODO: evaluate std::thread::available_concurrency as a num_cpus replacement let cores = num_cpus::get(); Arc::new( runtime::Builder::new_multi_thread() .enable_all() - .worker_threads(if cores > 4 { cores - 1 } else { cores }) + .worker_threads((cores / 4).max(MIN_RECOMMENDED_TOKIO_THREADS)) .thread_name_fn(|| { static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs index 8cd06499e0..25c5575482 100644 --- a/voxygen/src/singleplayer.rs +++ b/voxygen/src/singleplayer.rs @@ -1,4 +1,4 @@ -use common::clock::Clock; +use common::{clock::Clock, consts::MIN_RECOMMENDED_TOKIO_THREADS}; use crossbeam::channel::{bounded, unbounded, Receiver, Sender, TryRecvError}; use server::{ persistence::{DatabaseSettings, SqlLogMode}, @@ -82,12 +82,13 @@ impl Singleplayer { let settings = server::Settings::singleplayer(&server_data_dir); let editable_settings = server::EditableSettings::singleplayer(&server_data_dir); + // TODO: evaluate std::thread::available_concurrency as a num_cpus replacement let cores = num_cpus::get(); debug!("Creating a new runtime for server"); let runtime = Arc::new( tokio::runtime::Builder::new_multi_thread() .enable_all() - .worker_threads(if cores > 4 { cores - 1 } else { cores }) + .worker_threads((cores / 4).max(MIN_RECOMMENDED_TOKIO_THREADS)) .thread_name_fn(|| { static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); diff --git a/world/examples/view.rs b/world/examples/view.rs index 38d0feeb30..56f0b105ac 100644 --- a/world/examples/view.rs +++ b/world/examples/view.rs @@ -6,10 +6,15 @@ const W: usize = 640; const H: usize = 480; fn main() { - let (world, index) = World::generate(0, WorldOpts { - seed_elements: true, - ..WorldOpts::default() - }); + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); + let (world, index) = World::generate( + 0, + WorldOpts { + seed_elements: true, + ..WorldOpts::default() + }, + &threadpool, + ); let index = index.as_index_ref(); diff --git a/world/examples/water.rs b/world/examples/water.rs index 8c722dddb8..42dd3fad26 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -29,6 +29,7 @@ fn main() { .with_max_level(Level::ERROR) .with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into())) .init(); + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); // To load a map file of your choice, replace map_file with the name of your map // (stored locally in the map directory of your Veloren root), and swap the @@ -42,13 +43,17 @@ fn main() { let mut _map_file = PathBuf::from("./maps"); _map_file.push(map_file); - let (world, index) = World::generate(5284, WorldOpts { - seed_elements: false, - world_file: sim::FileOpts::LoadAsset(veloren_world::sim::DEFAULT_WORLD_MAP.into()), - // world_file: sim::FileOpts::Load(_map_file), - // world_file: sim::FileOpts::Save, - ..WorldOpts::default() - }); + let (world, index) = World::generate( + 5284, + WorldOpts { + seed_elements: false, + world_file: sim::FileOpts::LoadAsset(veloren_world::sim::DEFAULT_WORLD_MAP.into()), + // world_file: sim::FileOpts::Load(_map_file), + // world_file: sim::FileOpts::Save, + ..WorldOpts::default() + }, + &threadpool, + ); let index = index.as_index_ref(); tracing::info!("Sampling data..."); let sampler = world.sim(); diff --git a/world/src/lib.rs b/world/src/lib.rs index 17ad70b013..1036abb16b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -87,16 +87,24 @@ impl assets::Asset for Colors { } impl World { - pub fn generate(seed: u32, opts: sim::WorldOpts) -> (Self, IndexOwned) { + pub fn generate( + seed: u32, + opts: sim::WorldOpts, + threadpool: &rayon::ThreadPool, + ) -> (Self, IndexOwned) { // NOTE: Generating index first in order to quickly fail if the color manifest // is broken. - let mut index = Index::new(seed); - let mut sim = sim::WorldSim::generate(seed, opts); - let civs = civ::Civs::generate(seed, &mut sim, &mut index); + threadpool.install(|| { + let mut index = Index::new(seed); - sim2::simulate(&mut index, &mut sim); + let mut sim = sim::WorldSim::generate(seed, opts, threadpool); - (Self { sim, civs }, IndexOwned::new(index)) + let civs = civ::Civs::generate(seed, &mut sim, &mut index); + + sim2::simulate(&mut index, &mut sim); + + (Self { sim, civs }, IndexOwned::new(index)) + }) } pub fn sim(&self) -> &sim::WorldSim { &self.sim } @@ -107,66 +115,68 @@ impl World { // TODO } - pub fn get_map_data(&self, index: IndexRef) -> WorldMapMsg { - // we need these numbers to create unique ids for cave ends - let num_sites = self.civs().sites().count() as u64; - let num_caves = self.civs().caves.values().count() as u64; - WorldMapMsg { - pois: self.civs().pois.iter().map(|(_, poi)| { - world_msg::PoiInfo { - name: poi.name.clone(), - kind: match &poi.kind { - civ::PoiKind::Peak(alt) => world_msg::PoiKind::Peak(*alt), - civ::PoiKind::Lake(size) => world_msg::PoiKind::Lake(*size), - }, - wpos: poi.loc * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), - } - }).collect(), - sites: self - .civs() - .sites - .iter() - .map(|(_, site)| { - world_msg::SiteInfo { - id: site.site_tmp.map(|i| i.id()).unwrap_or_default(), - name: site.site_tmp.map(|id| index.sites[id].name().to_string()), - // TODO: Probably unify these, at some point - kind: match &site.kind { - civ::SiteKind::Settlement => world_msg::SiteKind::Town, - civ::SiteKind::Dungeon => world_msg::SiteKind::Dungeon { - difficulty: match site.site_tmp.map(|id| &index.sites[id].kind) { - Some(site::SiteKind::Dungeon(d)) => d.difficulty(), - _ => 0, - }, - }, - civ::SiteKind::Castle => world_msg::SiteKind::Castle, - civ::SiteKind::Refactor => world_msg::SiteKind::Town, - civ::SiteKind::Tree => world_msg::SiteKind::Tree, + pub fn get_map_data(&self, index: IndexRef, threadpool: &rayon::ThreadPool) -> WorldMapMsg { + threadpool.install(|| { + // we need these numbers to create unique ids for cave ends + let num_sites = self.civs().sites().count() as u64; + let num_caves = self.civs().caves.values().count() as u64; + WorldMapMsg { + pois: self.civs().pois.iter().map(|(_, poi)| { + world_msg::PoiInfo { + name: poi.name.clone(), + kind: match &poi.kind { + civ::PoiKind::Peak(alt) => world_msg::PoiKind::Peak(*alt), + civ::PoiKind::Lake(size) => world_msg::PoiKind::Lake(*size), }, - wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + wpos: poi.loc * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), } - }) - .chain( - self.civs() - .caves - .iter() - .map(|(id, info)| { - // separate the two locations, combine with name - std::iter::once((id.id()+num_sites, info.name.clone(), info.location.0)) - // unfortunately we have to introduce a fake id (as it gets stored in a map in the client) - .chain(std::iter::once((id.id()+num_sites+num_caves, info.name.clone(), info.location.1))) - }) - .flatten() // unwrap inner iteration - .map(|(id, name, pos)| world_msg::SiteInfo { - id, - name: Some(name), - kind: world_msg::SiteKind::Cave, - wpos: pos, - }), - ) - .collect(), - ..self.sim.get_map(index) - } + }).collect(), + sites: self + .civs() + .sites + .iter() + .map(|(_, site)| { + world_msg::SiteInfo { + id: site.site_tmp.map(|i| i.id()).unwrap_or_default(), + name: site.site_tmp.map(|id| index.sites[id].name().to_string()), + // TODO: Probably unify these, at some point + kind: match &site.kind { + civ::SiteKind::Settlement => world_msg::SiteKind::Town, + civ::SiteKind::Dungeon => world_msg::SiteKind::Dungeon { + difficulty: match site.site_tmp.map(|id| &index.sites[id].kind) { + Some(site::SiteKind::Dungeon(d)) => d.difficulty(), + _ => 0, + }, + }, + civ::SiteKind::Castle => world_msg::SiteKind::Castle, + civ::SiteKind::Refactor => world_msg::SiteKind::Town, + civ::SiteKind::Tree => world_msg::SiteKind::Tree, + }, + wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + } + }) + .chain( + self.civs() + .caves + .iter() + .map(|(id, info)| { + // separate the two locations, combine with name + std::iter::once((id.id() + num_sites, info.name.clone(), info.location.0)) + // unfortunately we have to introduce a fake id (as it gets stored in a map in the client) + .chain(std::iter::once((id.id() + num_sites + num_caves, info.name.clone(), info.location.1))) + }) + .flatten() // unwrap inner iteration + .map(|(id, name, pos)| world_msg::SiteInfo { + id, + name: Some(name), + kind: world_msg::SiteKind::Cave, + wpos: pos, + }), + ) + .collect(), + ..self.sim.get_map(index) + } + }) } pub fn sample_columns( diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index 0ceefb3141..679f9c9749 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -28,6 +28,7 @@ pub type Alt = f64; pub type Compute = f64; pub type Computex8 = [Compute; 8]; +/* code used by sharp in future /// Compute the water flux at all chunks, given a list of chunk indices sorted /// by increasing height. pub fn get_drainage( @@ -56,6 +57,7 @@ pub fn get_drainage( }); flux } +*/ /// Compute the water flux at all chunks for multiple receivers, given a list of /// chunk indices sorted by increasing height and weights for each receiver. @@ -710,6 +712,7 @@ fn erode( // scaling factors height_scale: impl Fn(f32) -> Alt + Sync, k_da_scale: impl Fn(f64) -> f64, + threadpool: &rayon::ThreadPool, ) { let compute_stats = true; debug!("Done draining..."); @@ -795,7 +798,7 @@ fn erode( let k_fs_mult_sed = 4.0; // Dimensionless multiplier for G when land becomes sediment. let g_fs_mult_sed = 1.0; - let ((dh, newh, maxh, mrec, mstack, mwrec, area), (mut max_slopes, h_t)) = rayon::join( + let ((dh, newh, maxh, mrec, mstack, mwrec, area), (mut max_slopes, h_t)) = threadpool.join( || { let mut dh = downhill( map_size_lg, @@ -817,6 +820,7 @@ fn erode( dx as Compute, dy as Compute, maxh, + threadpool, ); debug!("Got multiple receivers..."); // TODO: Figure out how to switch between single-receiver and multi-receiver @@ -827,7 +831,7 @@ fn erode( (dh, newh, maxh, mrec, mstack, mwrec, area) }, || { - rayon::join( + threadpool.join( || { let max_slope = get_max_slope(map_size_lg, h, rock_strength_nz, |posi| { height_scale(n_f(posi)) @@ -851,7 +855,7 @@ fn erode( // Precompute factors for Stream Power Law. let czero = ::zero(); - let (k_fs_fact, k_df_fact) = rayon::join( + let (k_fs_fact, k_df_fact) = threadpool.join( || { dh.par_iter() .enumerate() @@ -1081,7 +1085,7 @@ fn erode( // Keep track of how many iterations we've gone to test for convergence. n_gs_stream_power_law += 1; - rayon::join( + threadpool.join( || { // guess/update the elevation at t+Δt (k) (&mut *h_p, &*h_stack) @@ -1721,7 +1725,7 @@ fn erode( /// http://horizon.documentation.ird.fr/exl-doc/pleins_textes/pleins_textes_7/sous_copyright/010031925.pdf /// /// See https://github.com/mewo2/terrain/blob/master/terrain.js -pub fn fill_sinks( +pub(crate) fn fill_sinks( map_size_lg: MapSizeLg, h: impl Fn(usize) -> F + Sync, is_ocean: impl Fn(usize) -> bool + Sync, @@ -2339,6 +2343,7 @@ pub fn get_multi_rec>( dx: Compute, dy: Compute, _maxh: F, + threadpool: &rayon::ThreadPool, ) -> (Box<[u8]>, Box<[u32]>, Box<[Computex8]>) { let nn = nx * ny; let dxdy = Vec2::new(dx, dy); @@ -2437,7 +2442,7 @@ pub fn get_multi_rec>( .unzip_into_vecs(&mut mrec, &mut don_vis); let czero = ::zero(); - let (wrec, stack) = rayon::join( + let (wrec, stack) = threadpool.join( || { (0..nn) .into_par_iter() @@ -2541,6 +2546,7 @@ pub fn do_erosion( height_scale: impl Fn(f32) -> Alt + Sync, k_d_scale: f64, k_da_scale: impl Fn(f64) -> f64, + threadpool: &rayon::ThreadPool, ) -> (Box<[Alt]>, Box<[Alt]> /* , Box<[Alt]> */) { debug!("Initializing erosion arrays..."); let oldh_ = (0..map_size_lg.chunks_len()) @@ -2663,6 +2669,7 @@ pub fn do_erosion( |posi| is_ocean(posi), height_scale, k_da_scale, + threadpool, ); }); (h, b) diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index cf6c16b6ff..026eea738f 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -9,18 +9,21 @@ mod way; use self::erosion::Compute; pub use self::{ diffusion::diffusion, - erosion::{ - do_erosion, fill_sinks, get_drainage, get_lakes, get_multi_drainage, get_multi_rec, - get_rivers, mrec_downhill, Alt, RiverData, RiverKind, - }, location::Location, map::{sample_pos, sample_wpos}, - util::{ - cdf_irwin_hall, downhill, get_horizon_map, get_oceans, local_cells, map_edge_factor, - uniform_noise, uphill, InverseCdf, ScaleBias, - }, + util::get_horizon_map, way::{Cave, Path, Way}, }; +pub(crate) use self::{ + erosion::{ + do_erosion, fill_sinks, get_lakes, get_multi_drainage, get_multi_rec, get_rivers, Alt, + RiverData, RiverKind, + }, + util::{ + cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, uniform_noise, uphill, + InverseCdf, + }, +}; use crate::{ all::{Environment, ForestKind, TreeAttr}, @@ -368,7 +371,7 @@ pub struct WorldSim { impl WorldSim { #[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587 - pub fn generate(seed: u32, opts: WorldOpts) -> Self { + pub fn generate(seed: u32, opts: WorldOpts, threadpool: &rayon::ThreadPool) -> Self { // Parse out the contents of various map formats into the values we need. let parsed_world_file = (|| { let map = match opts.world_file { @@ -619,7 +622,7 @@ impl WorldSim { // No NaNs in these uniform vectors, since the original noise value always // returns Some. - let ((alt_base, _), (chaos, _)) = rayon::join( + let ((alt_base, _), (chaos, _)) = threadpool.join( || { uniform_noise(map_size_lg, |_, wposf| { // "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied @@ -1067,6 +1070,7 @@ impl WorldSim { height_scale, k_d_scale(n_approx), k_da_scale, + threadpool, ); // Quick "small scale" erosion cycle in order to lower extreme angles. @@ -1090,6 +1094,7 @@ impl WorldSim { height_scale, k_d_scale(n_approx), k_da_scale, + threadpool, ) }; @@ -1168,6 +1173,7 @@ impl WorldSim { height_scale, k_d_scale(n_approx), k_da_scale, + threadpool, ) }; @@ -1190,6 +1196,7 @@ impl WorldSim { TerrainChunkSize::RECT_SIZE.x as Compute, TerrainChunkSize::RECT_SIZE.y as Compute, maxh, + threadpool, ) }; let flux_old = get_multi_drainage(map_size_lg, &mstack, &mrec, &*mwrec, boundary_len); @@ -1318,61 +1325,63 @@ impl WorldSim { }; // NaNs in these uniform vectors wherever pure_water() returns true. - let (((alt_no_water, _), (pure_flux, _)), ((temp_base, _), (humid_base, _))) = rayon::join( - || { - rayon::join( - || { - uniform_noise(map_size_lg, |posi, _| { - if pure_water(posi) { - None - } else { - // A version of alt that is uniform over *non-water* (or - // land-adjacent water) chunks. - Some(alt[posi] as f32) - } - }) - }, - || { - uniform_noise(map_size_lg, |posi, _| { - if pure_water(posi) { - None - } else { - Some(flux_old[posi]) - } - }) - }, - ) - }, - || { - rayon::join( - || { - uniform_noise(map_size_lg, |posi, wposf| { - if pure_water(posi) { - None - } else { - // -1 to 1. - Some(gen_ctx.temp_nz.get((wposf).into_array()) as f32) - } - }) - }, - || { - uniform_noise(map_size_lg, |posi, wposf| { - // Check whether any tiles around this tile are water. - if pure_water(posi) { - None - } else { - // 0 to 1, hopefully. - Some( - (gen_ctx.humid_nz.get(wposf.div(1024.0).into_array()) as f32) - .add(1.0) - .mul(0.5), - ) - } - }) - }, - ) - }, - ); + let (((alt_no_water, _), (pure_flux, _)), ((temp_base, _), (humid_base, _))) = threadpool + .join( + || { + threadpool.join( + || { + uniform_noise(map_size_lg, |posi, _| { + if pure_water(posi) { + None + } else { + // A version of alt that is uniform over *non-water* (or + // land-adjacent water) chunks. + Some(alt[posi] as f32) + } + }) + }, + || { + uniform_noise(map_size_lg, |posi, _| { + if pure_water(posi) { + None + } else { + Some(flux_old[posi]) + } + }) + }, + ) + }, + || { + threadpool.join( + || { + uniform_noise(map_size_lg, |posi, wposf| { + if pure_water(posi) { + None + } else { + // -1 to 1. + Some(gen_ctx.temp_nz.get((wposf).into_array()) as f32) + } + }) + }, + || { + uniform_noise(map_size_lg, |posi, wposf| { + // Check whether any tiles around this tile are water. + if pure_water(posi) { + None + } else { + // 0 to 1, hopefully. + Some( + (gen_ctx.humid_nz.get(wposf.div(1024.0).into_array()) + as f32) + .add(1.0) + .mul(0.5), + ) + } + }) + }, + ) + }, + ); let gen_cdf = GenCdf { humid_base, diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index c54c75a501..621ea6bfe5 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -771,6 +771,7 @@ impl NoiseFn<[f64; 4]> for HybridMulti { } } +/* code used by sharp in future /// Noise function that applies a scaling factor and a bias to the output value /// from the source function. /// @@ -810,3 +811,4 @@ impl<'a, F: NoiseFn + 'a, T> NoiseFn for ScaleBias<'a, F> { #[cfg(target_os = "emscripten")] fn get(&self, point: T) -> f64 { (self.source.get(point) * self.scale) + self.bias } } + */ diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index bba5e5613c..92b64705b6 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -1071,6 +1071,7 @@ mod tests { #[test] fn test_economy() { init(); + let threadpool = rayon::ThreadPoolBuilder::new().build().unwrap(); info!("init"); let seed = 59686; let opts = sim::WorldOpts { @@ -1080,7 +1081,7 @@ mod tests { }; let mut index = crate::index::Index::new(seed); info!("Index created"); - let mut sim = sim::WorldSim::generate(seed, opts); + let mut sim = sim::WorldSim::generate(seed, opts, &threadpool); info!("World loaded"); let regenerate_input = false; if regenerate_input {