Merge branch 'xMAC94x/get_rid_of_useless_threadpool' into 'master'

rayon::join creates a global threadpool, which is only used in /world

See merge request veloren/veloren!2251
This commit is contained in:
Marcel 2021-05-05 13:43:18 +00:00
commit 3ddbb479f2
18 changed files with 241 additions and 172 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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;

View File

@ -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"] }

View File

@ -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::<common::outcome::Outcome>::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<ThreadPool> { &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.

View File

@ -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"

View File

@ -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);

View File

@ -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(),

View File

@ -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

View File

@ -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)))

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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(

View File

@ -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 = <SimdType as Zero>::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<F: Float + Send + Sync>(
pub(crate) fn fill_sinks<F: Float + Send + Sync>(
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<F: fmt::Debug + Float + Sync + Into<Compute>>(
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<F: fmt::Debug + Float + Sync + Into<Compute>>(
.unzip_into_vecs(&mut mrec, &mut don_vis);
let czero = <Compute as Zero>::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)

View File

@ -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,

View File

@ -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<T> + 'a, T> NoiseFn<T> for ScaleBias<'a, F> {
#[cfg(target_os = "emscripten")]
fn get(&self, point: T) -> f64 { (self.source.get(point) * self.scale) + self.bias }
}
*/

View File

@ -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 {