Skip to main content

steel_core/world/tick_scheduler/
world.rs

1use super::super::*;
2
3impl World {
4    /// Schedules a block tick at the given position.
5    ///
6    /// The tick will fire after `delay` world game ticks with the given priority.
7    /// Its deadline continues to age while the loaded chunk is outside simulation distance.
8    /// Only one tick per `(pos, block)` pair can be active at a time — duplicates
9    /// are silently ignored.
10    pub fn schedule_block_tick(
11        &self,
12        pos: BlockPos,
13        block: BlockRef,
14        delay: i32,
15        priority: super::TickPriority,
16    ) {
17        let trigger_tick = self.game_time().wrapping_add(i64::from(delay));
18        let order = self.scheduled_ticks.next_sub_tick_order();
19        let chunk_pos = Self::chunk_pos_for_block(pos);
20        self.chunk_map.with_full_chunk(chunk_pos, |chunk| {
21            chunk.schedule_block_tick(pos, block, trigger_tick, priority, order);
22        });
23    }
24
25    /// Schedules a block tick with `Normal` priority.
26    pub fn schedule_block_tick_default(&self, pos: BlockPos, block: BlockRef, delay: i32) {
27        self.schedule_block_tick(pos, block, delay, super::TickPriority::Normal);
28    }
29
30    /// Schedules a fluid tick at the given position.
31    ///
32    /// The tick will fire after `delay` world game ticks with the given priority.
33    /// Its deadline continues to age while the loaded chunk is outside simulation distance.
34    /// Only one tick per `(pos, fluid)` pair can be active at a time.
35    pub fn schedule_fluid_tick(
36        &self,
37        pos: BlockPos,
38        fluid: FluidRef,
39        delay: i32,
40        priority: super::TickPriority,
41    ) {
42        let trigger_tick = self.game_time().wrapping_add(i64::from(delay));
43        let order = self.scheduled_ticks.next_sub_tick_order();
44        let chunk_pos = Self::chunk_pos_for_block(pos);
45        self.chunk_map.with_full_chunk(chunk_pos, |chunk| {
46            chunk.schedule_fluid_tick(pos, fluid, trigger_tick, priority, order);
47        });
48    }
49
50    /// Schedules a fluid tick with `Normal` priority.
51    pub fn schedule_fluid_tick_default(&self, pos: BlockPos, fluid: FluidRef, delay: i32) {
52        self.schedule_fluid_tick(pos, fluid, delay, super::TickPriority::Normal);
53    }
54
55    /// Returns `true` if a block tick is already scheduled for the given `(pos, block)`.
56    ///
57    /// # Panics
58    ///
59    /// Panics if a published Full chunk's scheduled-tick container was finalized,
60    /// which violates the chunk publication invariant.
61    pub fn has_scheduled_block_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
62        let chunk_pos = Self::chunk_pos_for_block(pos);
63        self.chunk_map
64            .with_full_chunk(chunk_pos, |chunk| {
65                chunk
66                    .has_scheduled_block_tick(pos, block)
67                    .expect("Full chunk scheduled-tick ownership invariant failed")
68            })
69            .unwrap_or(false)
70    }
71
72    /// Returns `true` if a fluid tick is already scheduled for the given `(pos, fluid)`.
73    ///
74    /// # Panics
75    ///
76    /// Panics if a published Full chunk's scheduled-tick container was finalized,
77    /// which violates the chunk publication invariant.
78    pub fn has_scheduled_fluid_tick(&self, pos: BlockPos, fluid: FluidRef) -> bool {
79        let chunk_pos = Self::chunk_pos_for_block(pos);
80        self.chunk_map
81            .with_full_chunk(chunk_pos, |chunk| {
82                chunk
83                    .has_scheduled_fluid_tick(pos, fluid)
84                    .expect("Full chunk scheduled-tick ownership invariant failed")
85            })
86            .unwrap_or(false)
87    }
88
89    pub(crate) fn register_full_chunk_ticks(
90        &self,
91        chunk: FullChunkRef<'_>,
92    ) -> Result<(), super::TickSchedulerError> {
93        self.scheduled_ticks.register_chunk(chunk)
94    }
95
96    pub(crate) fn unregister_full_chunk_ticks(&self, pos: ChunkPos) {
97        self.scheduled_ticks.unregister_chunk(pos);
98    }
99
100    #[cfg(test)]
101    pub(crate) fn has_registered_full_chunk_ticks(&self, pos: ChunkPos) -> bool {
102        self.scheduled_ticks.has_registered_chunk(pos)
103    }
104
105    #[cfg(test)]
106    pub(crate) fn has_indexed_scheduled_tick_head(&self, pos: ChunkPos) -> bool {
107        self.scheduled_ticks.has_indexed_head(pos)
108    }
109
110    pub(crate) fn schedule_block_tick_for_chunk(
111        &self,
112        chunk: FullChunkRef<'_>,
113        pos: BlockPos,
114        block: BlockRef,
115        trigger_tick: i64,
116        priority: super::TickPriority,
117        sub_tick_order: i64,
118    ) -> Result<bool, super::TickSchedulerError> {
119        self.scheduled_ticks.schedule_block(
120            chunk,
121            block,
122            pos,
123            trigger_tick,
124            priority,
125            sub_tick_order,
126        )
127    }
128
129    pub(crate) fn schedule_fluid_tick_for_chunk(
130        &self,
131        chunk: FullChunkRef<'_>,
132        pos: BlockPos,
133        fluid: FluidRef,
134        trigger_tick: i64,
135        priority: super::TickPriority,
136        sub_tick_order: i64,
137    ) -> Result<bool, super::TickSchedulerError> {
138        self.scheduled_ticks.schedule_fluid(
139            chunk,
140            fluid,
141            pos,
142            trigger_tick,
143            priority,
144            sub_tick_order,
145        )
146    }
147
148    pub(crate) fn unpack_scheduled_ticks(
149        &self,
150        pos: ChunkPos,
151    ) -> Result<(), super::TickSchedulerError> {
152        self.scheduled_ticks.unpack_chunk(pos, self.game_time())
153    }
154
155    pub(crate) fn reconcile_active_scheduled_tick_chunks<I>(
156        &self,
157        active_chunks: I,
158    ) -> Result<(), super::TickSchedulerError>
159    where
160        I: Iterator<Item = ChunkPos> + Clone,
161    {
162        self.scheduled_ticks.reconcile_active_chunks(active_chunks)
163    }
164
165    pub(crate) fn begin_scheduled_tick_phase(
166        &self,
167        current_tick: i64,
168        max_ticks: usize,
169    ) -> super::ScheduledTickBatch<BlockRef> {
170        self.scheduled_ticks.begin_tick(current_tick, max_ticks)
171    }
172
173    pub(crate) fn collect_scheduled_fluid_tick_batch(
174        &self,
175        current_tick: i64,
176        max_ticks: usize,
177    ) -> super::ScheduledTickBatch<FluidRef> {
178        self.scheduled_ticks
179            .collect_fluid_ticks(current_tick, max_ticks)
180    }
181
182    /// Returns whether a selected block tick at `(pos, block)` has not started yet.
183    ///
184    /// This mirrors `LevelTickAccess.willTickThisTick` and is distinct from
185    /// [`Self::has_scheduled_block_tick`], because selected ticks have already
186    /// been removed from their owning chunk queue.
187    pub fn will_tick_block_this_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
188        let batch = self
189            .scheduled_block_ticks_this_tick
190            .lock()
191            .as_ref()
192            .cloned();
193        batch.is_some_and(|batch| batch.contains(pos, block))
194    }
195
196    /// Returns whether a selected fluid tick at `(pos, fluid)` has not started yet.
197    pub fn will_tick_fluid_this_tick(&self, pos: BlockPos, fluid: FluidRef) -> bool {
198        let batch = self
199            .scheduled_fluid_ticks_this_tick
200            .lock()
201            .as_ref()
202            .cloned();
203        batch.is_some_and(|batch| batch.contains(pos, fluid))
204    }
205
206    pub(crate) fn begin_scheduled_block_tick_batch(
207        &self,
208        ticks: Vec<super::BlockTick>,
209    ) -> Arc<super::ScheduledTickRunBatch<BlockRef>> {
210        let batch = Arc::new(super::ScheduledTickRunBatch::new(ticks));
211        let mut current = self.scheduled_block_ticks_this_tick.lock();
212        assert!(
213            current.is_none(),
214            "scheduled block-tick batch was already active"
215        );
216        *current = Some(Arc::clone(&batch));
217        batch
218    }
219
220    pub(crate) fn end_scheduled_block_tick_batch(
221        &self,
222        batch: &Arc<super::ScheduledTickRunBatch<BlockRef>>,
223    ) {
224        let removed = {
225            let mut current = self.scheduled_block_ticks_this_tick.lock();
226            assert!(
227                current
228                    .as_ref()
229                    .is_some_and(|current| Arc::ptr_eq(current, batch)),
230                "scheduled block-tick batch identity changed during execution"
231            );
232            current.take()
233        };
234        drop(removed);
235    }
236
237    pub(crate) fn begin_scheduled_fluid_tick_batch(
238        &self,
239        ticks: Vec<super::FluidTick>,
240    ) -> Arc<super::ScheduledTickRunBatch<FluidRef>> {
241        let batch = Arc::new(super::ScheduledTickRunBatch::new(ticks));
242        let mut current = self.scheduled_fluid_ticks_this_tick.lock();
243        assert!(
244            current.is_none(),
245            "scheduled fluid-tick batch was already active"
246        );
247        *current = Some(Arc::clone(&batch));
248        batch
249    }
250
251    pub(crate) fn end_scheduled_fluid_tick_batch(
252        &self,
253        batch: &Arc<super::ScheduledTickRunBatch<FluidRef>>,
254    ) {
255        let removed = {
256            let mut current = self.scheduled_fluid_ticks_this_tick.lock();
257            assert!(
258                current
259                    .as_ref()
260                    .is_some_and(|current| Arc::ptr_eq(current, batch)),
261                "scheduled fluid-tick batch identity changed during execution"
262            );
263            current.take()
264        };
265        drop(removed);
266    }
267}