steel_core/world/tick_scheduler/
mod.rs1use std::{
37 cmp::Ordering,
38 collections::{BTreeSet, BinaryHeap},
39 ptr,
40 sync::{
41 Arc, OnceLock,
42 atomic::{AtomicI64, AtomicUsize, Ordering as AtomicOrdering},
43 },
44};
45
46use rustc_hash::{FxHashMap, FxHashSet};
47use steel_registry::blocks::BlockRef;
48use steel_registry::fluid::FluidRef;
49use steel_utils::{
50 BlockPos, ChunkPos, PackedChunkPos,
51 locks::{SyncMutex, SyncRwLock},
52};
53
54use crate::chunk::full_chunk::FullChunkRef;
55
56mod world;
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[repr(i8)]
64pub enum TickPriority {
65 ExtremelyHigh = -3,
67 VeryHigh = -2,
69 High = -1,
71 Normal = 0,
73 Low = 1,
75 VeryLow = 2,
77 ExtremelyLow = 3,
79}
80
81impl TickPriority {
82 #[must_use]
84 pub const fn by_value(value: i32) -> Self {
85 match value {
86 -3 => Self::ExtremelyHigh,
87 -2 => Self::VeryHigh,
88 -1 => Self::High,
89 0 => Self::Normal,
90 1 => Self::Low,
91 2 => Self::VeryLow,
92 3 => Self::ExtremelyLow,
93 value if value < Self::ExtremelyHigh as i32 => Self::ExtremelyHigh,
94 _ => Self::ExtremelyLow,
95 }
96 }
97}
98
99pub trait TickKey: Copy {
103 fn key(self) -> usize;
105}
106
107impl TickKey for BlockRef {
108 #[inline]
109 fn key(self) -> usize {
110 ptr::from_ref(self) as usize
111 }
112}
113
114impl TickKey for FluidRef {
115 #[inline]
116 fn key(self) -> usize {
117 ptr::from_ref(self) as usize
118 }
119}
120
121#[derive(Debug, Clone, Copy)]
123pub struct ScheduledTick<T: TickKey> {
124 pub tick_type: T,
126 pub pos: BlockPos,
128 pub trigger_tick: i64,
130 pub priority: TickPriority,
132 pub sub_tick_order: i64,
135}
136
137#[derive(Debug, Clone, Copy)]
142pub(crate) struct SavedTick<T: TickKey> {
143 pub(crate) tick_type: T,
145 pub(crate) pos: BlockPos,
147 pub(crate) delay: i32,
149 pub(crate) priority: TickPriority,
151}
152
153pub type BlockTick = ScheduledTick<BlockRef>;
155pub type FluidTick = ScheduledTick<FluidRef>;
157pub type ScheduledTickKey = (BlockPos, usize);
159pub type BlockTickList = TickList<BlockRef>;
161pub type FluidTickList = TickList<FluidRef>;
163
164#[derive(Debug, Default)]
166pub(crate) struct ChunkTickLists {
167 block: BlockTickList,
168 fluid: FluidTickList,
169}
170
171impl ChunkTickLists {
172 #[must_use]
173 pub(crate) const fn new(block: BlockTickList, fluid: FluidTickList) -> Self {
174 Self { block, fluid }
175 }
176
177 pub(crate) const fn block(&self) -> &BlockTickList {
178 &self.block
179 }
180
181 pub(crate) const fn block_mut(&mut self) -> &mut BlockTickList {
182 &mut self.block
183 }
184
185 pub(crate) const fn fluid(&self) -> &FluidTickList {
186 &self.fluid
187 }
188
189 pub(crate) const fn fluid_mut(&mut self) -> &mut FluidTickList {
190 &mut self.fluid
191 }
192
193 fn packing_snapshot(&self) -> ChunkTickPackingSnapshot {
194 ChunkTickPackingSnapshot {
195 block: self.block.packing_snapshot(),
196 fluid: self.fluid.packing_snapshot(),
197 }
198 }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202enum ChunkTickContainerLifecycle {
203 Proto,
204 PrePublication,
205 Registered,
206 Finalized,
207}
208
209#[derive(Debug)]
210struct ChunkTickContainerState {
211 lifecycle: ChunkTickContainerLifecycle,
212 lists: ChunkTickLists,
213}
214
215#[derive(Debug)]
221pub(crate) struct ChunkTickContainer {
222 state: SyncMutex<ChunkTickContainerState>,
223}
224
225impl ChunkTickContainer {
226 #[must_use]
227 pub(crate) const fn new(lists: ChunkTickLists) -> Self {
228 Self {
229 state: SyncMutex::new(ChunkTickContainerState {
230 lifecycle: ChunkTickContainerLifecycle::PrePublication,
231 lists,
232 }),
233 }
234 }
235
236 #[must_use]
237 pub(crate) const fn new_proto(lists: ChunkTickLists) -> Self {
238 Self {
239 state: SyncMutex::new(ChunkTickContainerState {
240 lifecycle: ChunkTickContainerLifecycle::Proto,
241 lists,
242 }),
243 }
244 }
245
246 pub(crate) fn promote_to_full(&self) -> bool {
248 let mut state = self.state.lock();
249 if state.lifecycle != ChunkTickContainerLifecycle::Proto {
250 return false;
251 }
252 state.lifecycle = ChunkTickContainerLifecycle::PrePublication;
253 true
254 }
255
256 pub(crate) fn schedule_unregistered_block(
257 &self,
258 block: BlockRef,
259 pos: BlockPos,
260 trigger_tick: i64,
261 priority: TickPriority,
262 sub_tick_order: i64,
263 ) -> Option<bool> {
264 let mut state = self.state.lock();
265 (state.lifecycle == ChunkTickContainerLifecycle::PrePublication).then(|| {
266 state
267 .lists
268 .block_mut()
269 .schedule(block, pos, trigger_tick, priority, sub_tick_order)
270 })
271 }
272
273 pub(crate) fn schedule_unregistered_fluid(
274 &self,
275 fluid: FluidRef,
276 pos: BlockPos,
277 trigger_tick: i64,
278 priority: TickPriority,
279 sub_tick_order: i64,
280 ) -> Option<bool> {
281 let mut state = self.state.lock();
282 (state.lifecycle == ChunkTickContainerLifecycle::PrePublication).then(|| {
283 state
284 .lists
285 .fluid_mut()
286 .schedule(fluid, pos, trigger_tick, priority, sub_tick_order)
287 })
288 }
289
290 pub(crate) fn schedule_pending_block(
291 &self,
292 block: BlockRef,
293 pos: BlockPos,
294 priority: TickPriority,
295 ) -> Option<bool> {
296 let mut state = self.state.lock();
297 (state.lifecycle == ChunkTickContainerLifecycle::Proto).then(|| {
298 state
299 .lists
300 .block_mut()
301 .schedule_pending(block, pos, priority)
302 })
303 }
304
305 pub(crate) fn schedule_pending_fluid(
306 &self,
307 fluid: FluidRef,
308 pos: BlockPos,
309 priority: TickPriority,
310 ) -> Option<bool> {
311 let mut state = self.state.lock();
312 (state.lifecycle == ChunkTickContainerLifecycle::Proto).then(|| {
313 state
314 .lists
315 .fluid_mut()
316 .schedule_pending(fluid, pos, priority)
317 })
318 }
319
320 pub(crate) fn pending_block_snapshot(&self) -> Option<Vec<SavedTick<BlockRef>>> {
321 let state = self.state.lock();
322 (state.lifecycle == ChunkTickContainerLifecycle::Proto)
323 .then(|| state.lists.block().pending_entries().to_vec())
324 }
325
326 #[cfg(test)]
327 pub(crate) fn pending_fluid_snapshot(&self) -> Option<Vec<SavedTick<FluidRef>>> {
328 let state = self.state.lock();
329 (state.lifecycle == ChunkTickContainerLifecycle::Proto)
330 .then(|| state.lists.fluid().pending_entries().to_vec())
331 }
332
333 pub(crate) fn remove_pending_blocks_matching(
334 &self,
335 predicate: impl FnMut(&SavedTick<BlockRef>) -> bool,
336 ) -> Option<usize> {
337 let mut state = self.state.lock();
338 (state.lifecycle == ChunkTickContainerLifecycle::Proto)
339 .then(|| state.lists.block_mut().remove_pending_matching(predicate))
340 }
341
342 pub(crate) fn has_block(&self, pos: BlockPos, block: BlockRef) -> Option<bool> {
343 let state = self.state.lock();
344 (state.lifecycle != ChunkTickContainerLifecycle::Finalized)
345 .then(|| state.lists.block().has_tick(pos, block))
346 }
347
348 pub(crate) fn has_fluid(&self, pos: BlockPos, fluid: FluidRef) -> Option<bool> {
349 let state = self.state.lock();
350 (state.lifecycle != ChunkTickContainerLifecycle::Finalized)
351 .then(|| state.lists.fluid().has_tick(pos, fluid))
352 }
353
354 pub(crate) fn snapshot(&self, current_tick: i64) -> Option<ScheduledTickSnapshot> {
355 let packing = {
356 let state = self.state.lock();
357 (state.lifecycle != ChunkTickContainerLifecycle::Finalized)
358 .then(|| state.lists.packing_snapshot())
359 }?;
360 Some(packing.pack(current_tick))
361 }
362}
363
364struct ChunkTickPackingSnapshot {
365 block: TickListPackingSnapshot<BlockRef>,
366 fluid: TickListPackingSnapshot<FluidRef>,
367}
368
369impl ChunkTickPackingSnapshot {
370 fn pack(self, current_tick: i64) -> ScheduledTickSnapshot {
371 ScheduledTickSnapshot {
372 block: self.block.pack(current_tick),
373 fluid: self.fluid.pack(current_tick),
374 }
375 }
376}
377
378pub(crate) struct ScheduledTickSnapshot {
380 pub(crate) block: Vec<SavedTick<BlockRef>>,
381 pub(crate) fluid: Vec<SavedTick<FluidRef>>,
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub(crate) enum TickSchedulerError {
387 AlreadyRegistered(ChunkPos),
389 MissingContainer(ChunkPos),
391 ContainerMismatch(ChunkPos),
393}
394
395pub(crate) struct ScheduledTickBatch<T: TickKey> {
397 pub(crate) ticks: Vec<ScheduledTick<T>>,
398 pub(crate) changed_containers: Vec<usize>,
399}
400
401mod list;
407mod run_batch;
408mod scheduler;
409
410use list::{TickList, TickListPackingSnapshot, intra_tick_drain_order};
411pub(crate) use run_batch::*;
412pub(crate) use scheduler::*;
413
414#[cfg(test)]
415mod tests;