Small fix to executor scheduling.

Also some preliminary separation for new lighting and removal of some
unneeded checks.
This commit is contained in:
Joshua Yanovski 2023-04-06 08:33:43 -07:00
parent 8d9680c68b
commit 0c99438993
5 changed files with 171 additions and 61 deletions

View File

@ -13,7 +13,7 @@ use executors::{
parker::{LargeThreadData, StaticParker},
Executor,
};
pub use executors::{builder::ThreadPoolBuilder, parker::large};
pub use executors::{builder::ThreadPoolBuilder, parker::large, FuturesExecutor};
use hashbrown::{hash_map::Entry, HashMap};
use pin_project_lite::pin_project;
// use rayon::ThreadPool;
@ -515,7 +515,7 @@ impl SlowJobPool {
// NOTE: It's important not to use internal until we're in the spawned thread, since the
// lock is probably currently taken!
self.threadpool.execute(move || {
self.threadpool./*execute*/spawn(async move {
// Repeatedly run until exit; we do things this way to avoid recursion, which might blow
// our call stack.
loop {
@ -549,6 +549,19 @@ impl SlowJobPool {
execution_start,
execution_end,
};
// Hint to the scheduler to run any tasks that were explicitly woken. This should
// run explicitly-woken tasks first (if enough time has elapsed, including tasks
// woken by other threads). Waiting to acquire the next job until we've yielded
// also gives other threads the opportunity to take any queued-up slowjobs.
tokio::task::yield_now().await;
// Hint to scheduler to run any other threads that are currently waiting, since
// slowjobs are, well, slow! i.e. the cost of yielding should be very small
// compared to the cost of actually running the job for *almost* every job on the
// pool (and for jobs for which this isn't true, we should probably batch them up).
//
// NOTE: Ideally this would be done automatically as part of the yield, but
// async_executor currently isn't a very good citizen in this regard.
std::thread::yield_now();
// directly maintain the next task afterwards
let next_task = {
// We take the lock in this scope to make sure it's dropped before we
@ -569,13 +582,8 @@ impl SlowJobPool {
// times or something in case we have more tasks to execute).
return;
};
// Hint to scheduler to run any other threads that are currently waiting, since
// slowjobs are, well, slow :) i.e. the cost of yielding should be very small
// compared to the cost of actually running the job for *almost* every job on the
// pool (and for jobs for which this isn't true, we should probably batch them up).
std::thread::yield_now();
}
});
}).detach();
}
/// spawn a new slow job on a certain NAME IF it can run immediately

View File

@ -71,8 +71,57 @@ fn flat_get<'a>(flat: &'a Vec<Block>, w: i32, h: i32, d: i32) -> impl Fn(Vec3<i3
} */
}
const UNKNOWN: u8 = 255;
struct LightMap {
default_light: u8,
bounds: Aabb<i32>,
light_map: Vec<u8>,
}
impl LightMap {
fn into_fn(self) -> impl Fn(Vec3<i32>) -> f32 + 'static + Send + Sync {
let outer = Aabb {
min: self.bounds.min/* - Vec3::new(SUNLIGHT as i32, SUNLIGHT as i32, 1) */ - Vec3::new(0, 0, 1),
max: self.bounds.max/* + Vec3::new(SUNLIGHT as i32, SUNLIGHT as i32, 1) */ + Vec3::new(0, 0, 1),
};
/* let mut vol_cached = vol.cached(); */
let mut light_map_ = vec![UNKNOWN; outer.size().product() as usize];
let (w_, h_, d_) = outer.clone().size().into_tuple();
let wh_ = w_ * h_;
let lm_idx = {
#[inline(always)] move |x, y, z| {
(wh_ * z + w_ * y + x) as usize
}
};
let min_bounds = Aabb {
min: self.bounds.min - Vec3::unit_z(),
max: self.bounds.max + Vec3::unit_z(),
};
#[inline(always)] move |wpos| {
/* if is_sunlight { return 1.0 } else { 0.0 } */
let pos = wpos - min_bounds.min;
let l = self.light_map
.get(/*lm_idx2*/lm_idx(pos.x, pos.y, pos.z))
.copied()
.unwrap_or(if pos.z < 0 { 0 } else { self.default_light });
if /* l != OPAQUE && */l != UNKNOWN {
l as f32 * SUNLIGHT_INV
} else {
0.0
}
}
}
}
fn calc_light<'a,
V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static,
/* V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static, */
I: Iterator<Item=(Vec3<i32>, u8)>,
/* F: /*for<'x> */for<'a> fn(&'a Vec<Block>) -> G, */
/* G: /*[&'x &'a (); 0], */Fn(Vec3<i32>) -> Block, */
@ -82,13 +131,12 @@ fn calc_light<'a,
default_light: u8,
bounds: Aabb<i32>,
range: Aabb<i32>,
vol: &'a VolGrid2d<V>,
/* vol: &'a VolGrid2d<V>, */
lit_blocks: I,
flat: &'a Vec<Block>,
(w, h, d): (i32, i32, i32)
) -> impl Fn(Vec3<i32>) -> f32 + 'static + Send + Sync/*CalcLightFn*//*<V, I>*/ {
) -> /*impl Fn(Vec3<i32>) -> f32 + 'static + Send + Sync/*CalcLightFn*//*<V, I>*/*/LightMap {
span!(_guard, "calc_light");
const UNKNOWN: u8 = 255;
const OPAQUE: u8 = 254;
let outer = Aabb {
@ -295,7 +343,7 @@ fn calc_light<'a,
});
});
let min_bounds = Aabb {
/* let min_bounds = Aabb {
min: bounds.min - Vec3::unit_z(),
max: bounds.max + Vec3::unit_z(),
};
@ -332,31 +380,47 @@ fn calc_light<'a,
} else {
0.0
}
} */
LightMap {
default_light,
bounds,
light_map: light_map_,
}
}
type V = TerrainChunk;
#[tracing::instrument(skip_all, name = "<&VolGrid2d as Meshable<_, _>>::generate_mesh")]
pub struct PreparedLights {
flat: Vec<Block>,
row_kinds: Vec<u8>,
z_start: i32,
z_end: i32,
light: LightMap,
glow: LightMap,
}
/// Representative block for air.
const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty);
/// Representative block for liquid.
///
/// FIXME: Can you really skip meshing for general liquids? Probably not...
const LIQUID: Block = Block::water(common::terrain::sprite::SpriteKind::Empty);
/// Representtive block for solids.
///
/// FIXME: Really hacky!
const OPAQUE: Block = Block::lava(common::terrain::sprite::SpriteKind::Empty);
const ALL_OPAQUE: u8 = 0b1;
const ALL_LIQUID: u8 = 0b10;
const ALL_AIR: u8 = 0b100;
#[tracing::instrument(skip_all, name = "<&VolGrid2d as Meshable<_, _>>::prepare_lights")]
#[allow(clippy::type_complexity)]
#[inline(always)]
pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
vol: &'a VolGrid2d<V>,
create_texture: impl FnOnce(usize) -> /*Option<Model<[u8; 4]>>*/F + Send,
(range, max_texture_size, boi): (Aabb<i32>, Vec2<u16>, &'a BlocksOfInterest),
) -> MeshGen<
TerrainVertex,
FluidVertex,
TerrainVertex,
(
Aabb<f32>,
/*ColLightInfo*/(Option<Model<[u8; 4]>>, Vec2<u16>),
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
AltIndices,
(f32, f32),
),
> {
pub fn prepare_lights(
vol: &VolGrid2d<V>,
(range, boi): (Aabb<i32>, &BlocksOfInterest),
) -> PreparedLights {
/* span!(
_guard,
"generate_mesh",
@ -374,20 +438,6 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
// z can range from -1..range.size().d + 1
let d = d + 2;
/// Representative block for air.
const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty);
/// Representative block for liquid.
///
/// FIXME: Can you really skip meshing for general liquids? Probably not...
const LIQUID: Block = Block::water(common::terrain::sprite::SpriteKind::Empty);
/// Representtive block for solids.
///
/// FIXME: Really hacky!
const OPAQUE: Block = Block::lava(common::terrain::sprite::SpriteKind::Empty);
const ALL_OPAQUE: u8 = 0b1;
const ALL_LIQUID: u8 = 0b10;
const ALL_AIR: u8 = 0b100;
// For each horizontal slice of the chunk, we keep track of what kinds of blocks are in it.
// This allows us to compute limits after the fact, much more precisely than keeping track of a
// single intersection would; it also lets us skip homogeoneous slices entirely.
@ -652,6 +702,7 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
let z_max = chonk.get_max_z() - chonk.get_min_z();
let below = *chonk.below();
// FIXME: This copies way more groups than necessary for side and corner chunks.
let flat_chunk = chonk.make_flat(&arena);
let min_z_ = z_diff - intersection.min.z;
@ -665,6 +716,8 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
ALL_AIR
};
// chonk.min.z - flat_range.min.z = chonk.min.z - (range.min.z - 1)
// NOTE: max(0) should not be required when this is fixed to work properly.
let skip_count = min_z_.max(0);
let take_count = (max_z_.min(d) - skip_count).max(0);
let skip_count = skip_count as usize;
@ -793,7 +846,7 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
light_end = Some(z);
}
},
// We could probably handle mixed opaque/liquid a well, since it's pretty much guaranteed
// We could probably handle mixed opaque/liquid as well, since it's pretty much guaranteed
// to attenuate, but we choose not to since we expect this to be much more
// common than fully opaque chunks.
_ => {},
@ -896,8 +949,54 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
}
}
} */
let light = calc_light(true, SUNLIGHT, light_range, range, vol, core::iter::empty(), &flat, (w, h, d));
let glow = calc_light(false, 0, glow_range, range, vol, glow_blocks.into_iter(), &flat, (w, h, d));
let light = calc_light(true, SUNLIGHT, light_range, range/*, vol*/, core::iter::empty(), &flat, (w, h, d));
let glow = calc_light(false, 0, glow_range, range/*, vol*/, glow_blocks.into_iter(), &flat, (w, h, d));
PreparedLights {
flat,
row_kinds,
z_start,
z_end,
light,
glow,
}
}
#[tracing::instrument(skip_all, name = "<&VolGrid2d as Meshable<_, _>>::generate_mesh")]
#[allow(clippy::type_complexity)]
#[inline(always)]
pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
vol: &'a VolGrid2d<V>,
create_texture: impl FnOnce(usize) -> /*Option<Model<[u8; 4]>>*/F + Send,
(range, max_texture_size, boi): (Aabb<i32>, Vec2<u16>, &'a BlocksOfInterest),
) -> MeshGen<
TerrainVertex,
FluidVertex,
TerrainVertex,
(
Aabb<f32>,
/*ColLightInfo*/(Option<Model<[u8; 4]>>, Vec2<u16>),
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
AltIndices,
(f32, f32),
),
> {
/* span!(
_guard,
"generate_mesh",
"<&VolGrid2d as Meshable<_, _>>::generate_mesh"
); */
let (w, h, d) = range.size().into_tuple();
// z can range from -1..range.size().d + 1
let d = d + 2;
let PreparedLights { flat, row_kinds, z_start, z_end, light, glow } =
prepare_lights(vol, (range, boi));
let light = light.into_fn();
let glow = glow.into_fn();
let max_size = max_texture_size;
assert!(z_end >= z_start);
@ -924,7 +1023,15 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
let mut greedy =
GreedyMesh::<guillotiere::SimpleAtlasAllocator>::new(max_size, greedy::terrain_config());
let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1);
// NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5
// + 14). FIXME: Make this function fallible, since the terrain
// information might be dynamically generated which would make this hard
// to enforce.
assert!(greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 16384);
// NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16,
// which always fits into a f32.
let max_bounds: Vec3<f32> = greedy_size.as_::<f32>();
// NOTE: Conversion to f32 is fine since this i32 is actually in bounds for u16.
let mesh_delta = Vec3::new(0.0, 0.0, (z_start + range.min.z) as f32);
let mut opaque_deep = Vec::new();
let mut opaque_shallow = Vec::new();
@ -938,13 +1045,6 @@ pub async fn generate_mesh<'a, F: Future<Output=Option<Model<[u8; 4]>>> + 'a>(
let mut do_draw_greedy = #[inline(always)] |z_start: i32, z_end: i32| {
// dbg!(range.min, z_start, z_end);
let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1);
// NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5
// + 14). FIXME: Make this function fallible, since the terrain
// information might be dynamically generated which would make this hard
// to enforce.
assert!(greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 16384);
// NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16,
// which always fits into a f32.
// NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16,
// which always fits into a usize.
let greedy_size = greedy_size.as_::<usize>();

View File

@ -1128,6 +1128,8 @@ impl/*<V: RectRasterableVol>*/ Terrain<V> {
for j in -1..2 {
let pos = pos + Vec2::new(i, j);
let is_sender = (pos.x & 1) ^ (pos.y & 1);
let entry = self.mesh_todo.entry(pos);
let done_meshing = self.chunks.contains_key(&pos);
let in_progress = done_meshing || matches!(entry, hash_map::Entry::Occupied(_));

View File

@ -752,7 +752,7 @@ impl Settlement {
BlockKind::Grass,
BlockKind::Earth,
BlockKind::Sand,
BlockKind::Snow,
/* BlockKind::Snow, */
BlockKind::Rock,
]
.contains(&block.kind())

View File

@ -1113,10 +1113,10 @@ impl Site {
let mut underground = true;
for z in -8..6 {
canvas.map(Vec3::new(wpos2d.x, wpos2d.y, alt + z), |b| {
if b.kind() == BlockKind::Snow {
/*if b.kind() == BlockKind::Snow {
underground = false;
b.into_vacant()
} else if b.is_filled() {
} else */if b.is_filled() {
if b.is_terrain() {
Block::new(
BlockKind::Earth,
@ -1196,10 +1196,10 @@ impl Site {
} else {
SpriteKind::Empty
};
if b.kind() == BlockKind::Snow {
/* if b.kind() == BlockKind::Snow {
underground = false;
b.into_vacant().with_sprite(sprite)
} else if b.is_filled() {
} else */if b.is_filled() {
if b.is_terrain() {
Block::new(
BlockKind::Earth,
@ -1227,7 +1227,7 @@ impl Site {
BlockKind::Grass,
BlockKind::Earth,
BlockKind::Sand,
BlockKind::Snow,
/* BlockKind::Snow, */
BlockKind::Rock,
]
.contains(&b.kind()) {