#[cfg(feature = "3d")]
use std::collections::HashSet;
use std::sync::mpsc;
#[cfg(feature = "3d")]
use glam::{Vec2, Vec3};
#[derive(Debug)]
pub struct Job<T> {
result: mpsc::Receiver<T>,
done: Option<Box<T>>,
taken: bool,
}
impl<T: Send + 'static> Job<T> {
pub fn spawn(work: impl FnOnce() -> T + Send + 'static) -> Self {
let (send, result) = mpsc::sync_channel(1);
let run = move || {
if let Ok(result) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(work)) {
let _ = send.send(result);
}
};
#[cfg(not(target_arch = "wasm32"))]
rayon::spawn(run);
#[cfg(target_arch = "wasm32")]
run();
Self {
result,
done: None,
taken: false,
}
}
}
impl<T> Job<T> {
pub fn is_done(&mut self) -> bool {
self.poll();
self.taken || self.done.is_some()
}
pub fn take(&mut self) -> Option<T> {
self.poll();
let result = self.done.take();
if result.is_some() {
self.taken = true;
}
result.map(|r| *r)
}
pub fn wait(mut self) -> T {
if let Some(result) = self.done.take() {
return *result;
}
assert!(!self.taken, "ponko: Job::wait: the result was already taken");
self.result
.recv()
.unwrap_or_else(|_| panic!("ponko: a job's work panicked"))
}
fn poll(&mut self) {
if self.done.is_some() || self.taken {
return;
}
match self.result.try_recv() {
Ok(result) => self.done = Some(Box::new(result)),
Err(mpsc::TryRecvError::Empty) => {}
Err(mpsc::TryRecvError::Disconnected) => panic!("ponko: a job's work panicked"),
}
}
}
#[cfg(feature = "3d")]
#[derive(Debug, Clone)]
pub struct CellStreamer {
cell_size: f32,
load_radius: f32,
keep_radius: f32,
loaded: HashSet<(i32, i32)>,
to_load: Vec<(i32, i32)>,
to_drop: Vec<(i32, i32)>,
wanted: Vec<(f32, (i32, i32))>,
}
#[cfg(feature = "3d")]
impl CellStreamer {
#[must_use]
pub fn new(cell_size: f32, load_radius: f32, keep_radius: f32) -> Self {
assert!(
cell_size > 0.0 && cell_size.is_finite(),
"ponko: CellStreamer: cell_size must be above 0 (got {cell_size})"
);
assert!(
load_radius >= 0.0 && keep_radius >= 0.0,
"ponko: CellStreamer: the radii can't be negative"
);
Self {
cell_size,
load_radius,
keep_radius: keep_radius.max(load_radius),
loaded: HashSet::new(),
to_load: Vec::new(),
to_drop: Vec::new(),
wanted: Vec::new(),
}
}
pub fn update(&mut self, at: Vec3, max_new: usize) {
let at = Vec2::new(at.x, at.z);
let size = self.cell_size;
let away = |(x, z): (i32, i32)| {
let min = Vec2::new(x as f32, z as f32) * size;
(at.clamp(min, min + size) - at).length()
};
self.to_drop.clear();
let keep = self.keep_radius;
let dropped = &mut self.to_drop;
self.loaded.retain(|&cell| {
let stays = away(cell) <= keep;
if !stays {
dropped.push(cell);
}
stays
});
self.to_drop.sort_unstable();
self.wanted.clear();
let reach = (self.load_radius / size).ceil() as i32 + 1;
let (cx, cz) = ((at.x / size).floor() as i32, (at.y / size).floor() as i32);
for z in cz - reach..=cz + reach {
for x in cx - reach..=cx + reach {
let d = away((x, z));
if d <= self.load_radius && !self.loaded.contains(&(x, z)) {
self.wanted.push((d, (x, z)));
}
}
}
self.wanted
.sort_unstable_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
self.to_load.clear();
for &(_, cell) in self.wanted.iter().take(max_new) {
self.loaded.insert(cell);
self.to_load.push(cell);
}
}
#[must_use]
pub fn to_load(&self) -> &[(i32, i32)] {
&self.to_load
}
#[must_use]
pub fn to_drop(&self) -> &[(i32, i32)] {
&self.to_drop
}
pub fn loaded(&self) -> impl Iterator<Item = (i32, i32)> + '_ {
self.loaded.iter().copied()
}
#[must_use]
pub fn is_loaded(&self, cell: (i32, i32)) -> bool {
self.loaded.contains(&cell)
}
#[must_use]
pub fn loaded_count(&self) -> usize {
self.loaded.len()
}
#[must_use]
pub fn corner(&self, (x, z): (i32, i32)) -> Vec3 {
Vec3::new(x as f32 * self.cell_size, 0.0, z as f32 * self.cell_size)
}
pub fn clear(&mut self) {
self.loaded.clear();
self.to_load.clear();
self.to_drop.clear();
}
}