Pumpkin/pumpkin-world/src/tick/scheduler.rs
2026-04-25 21:18:10 +02:00

113 lines
3.5 KiB
Rust

use std::sync::{
Mutex,
atomic::{AtomicUsize, Ordering},
};
use pumpkin_util::math::position::BlockPos;
use rustc_hash::FxHashSet;
use crate::tick::{MAX_TICK_DELAY, OrderedTick, ScheduledTick};
pub struct ChunkTickScheduler<T> {
tick_queue: Mutex<[Vec<OrderedTick<T>>; MAX_TICK_DELAY]>,
queued_ticks: Mutex<FxHashSet<(BlockPos, T)>>,
offset: AtomicUsize,
}
impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
pub fn step_tick(&self) -> Vec<OrderedTick<&'a T>> {
// Atomic update for the offset
let current_offset = self.offset.fetch_add(1, Ordering::SeqCst) % MAX_TICK_DELAY;
let next_offset = (current_offset + 1) % MAX_TICK_DELAY;
self.offset.store(next_offset, Ordering::SeqCst);
let res = {
let mut queue = self.tick_queue.lock().unwrap_or_else(|e| e.into_inner());
std::mem::take(&mut queue[current_offset])
};
if !res.is_empty() {
let mut set = self.queued_ticks.lock().unwrap_or_else(|e| e.into_inner());
for next_tick in &res {
set.remove(&(next_tick.position, next_tick.value));
}
}
res
}
pub fn schedule_tick(&self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) {
let mut set = self.queued_ticks.lock().unwrap_or_else(|e| e.into_inner());
if set.insert((tick.position, tick.value)) {
let mut queue = self.tick_queue.lock().unwrap_or_else(|e| e.into_inner());
let offset = self.offset.load(Ordering::SeqCst);
let index = (offset + tick.delay as usize) % MAX_TICK_DELAY;
queue[index].push(OrderedTick {
priority: tick.priority,
sub_tick_order,
position: tick.position,
value: tick.value,
});
}
}
pub fn is_scheduled(&self, pos: BlockPos, value: &T) -> bool {
self.queued_ticks
.lock()
.unwrap_or_else(|e| e.into_inner())
.contains(&(pos, value))
}
#[must_use]
pub fn to_vec(&self) -> Vec<ScheduledTick<&'a T>> {
let offset = self.offset.load(Ordering::SeqCst);
let queue = self.tick_queue.lock().unwrap_or_else(|e| e.into_inner());
let mut res = Vec::new();
for i in 0..MAX_TICK_DELAY {
let index = (offset + i) % MAX_TICK_DELAY;
res.extend(queue[index].iter().map(|x| ScheduledTick {
delay: i as u8,
priority: x.priority,
position: x.position,
value: x.value,
}));
}
res
}
}
impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator<ScheduledTick<&'a T>>
for ChunkTickScheduler<&'a T>
{
fn from_iter<I: IntoIterator<Item = ScheduledTick<&'a T>>>(iter: I) -> Self {
let scheduler = Self::default();
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
if lower > 0 {
scheduler
.queued_ticks
.lock()
.unwrap_or_else(|e| e.into_inner())
.reserve(lower);
}
for tick in iter {
scheduler.schedule_tick(&tick, 0);
}
scheduler
}
}
impl<T> Default for ChunkTickScheduler<T> {
fn default() -> Self {
Self {
tick_queue: Mutex::new(std::array::from_fn(|_| Vec::new())),
queued_ticks: Mutex::new(FxHashSet::default()),
offset: AtomicUsize::new(0),
}
}
}