Skip to main content

steel_core/world/block_updates/
neighbor_updater.rs

1//! Vanilla collecting neighbor-update ordering.
2//!
3//! The feature-gated experimental redstone `Orientation` is intentionally absent.
4
5use std::sync::Arc;
6
7use steel_registry::blocks::BlockRef;
8use steel_utils::locks::SyncMutex;
9use steel_utils::types::UpdateFlags;
10use steel_utils::{BlockPos, BlockStateId, Direction};
11
12use crate::world::World;
13
14/// Vanilla `NeighborUpdater.UPDATE_ORDER`.
15pub(in crate::world) const UPDATE_ORDER: [Direction; 6] = [
16    Direction::West,
17    Direction::East,
18    Direction::Down,
19    Direction::Up,
20    Direction::North,
21    Direction::South,
22];
23
24pub(in crate::world) struct CollectingNeighborUpdater {
25    max_chained_neighbor_updates: i32,
26    state: SyncMutex<CollectingState<NeighborUpdate>>,
27}
28
29pub(in crate::world) struct ShapeUpdate {
30    direction: Direction,
31    neighbor_state: BlockStateId,
32    pos: BlockPos,
33    neighbor_pos: BlockPos,
34    flags: UpdateFlags,
35    update_limit: i32,
36}
37
38impl ShapeUpdate {
39    pub(in crate::world) const fn new(
40        direction: Direction,
41        neighbor_state: BlockStateId,
42        pos: BlockPos,
43        neighbor_pos: BlockPos,
44        flags: UpdateFlags,
45        update_limit: i32,
46    ) -> Self {
47        Self {
48            direction,
49            neighbor_state,
50            pos,
51            neighbor_pos,
52            flags,
53            update_limit,
54        }
55    }
56}
57
58impl CollectingNeighborUpdater {
59    pub(in crate::world) fn new(max_chained_neighbor_updates: i32) -> Self {
60        Self {
61            max_chained_neighbor_updates,
62            state: SyncMutex::new(CollectingState::default()),
63        }
64    }
65
66    pub(in crate::world) fn shape_update(&self, world: &Arc<World>, update: ShapeUpdate) {
67        self.add_and_run(world, update.pos, NeighborUpdate::Shape(update));
68    }
69
70    pub(in crate::world) fn neighbor_changed(
71        &self,
72        world: &Arc<World>,
73        pos: BlockPos,
74        source_block: BlockRef,
75    ) {
76        self.add_and_run(world, pos, NeighborUpdate::Simple { pos, source_block });
77    }
78
79    pub(in crate::world) fn neighbor_changed_with_state(
80        &self,
81        world: &Arc<World>,
82        state: BlockStateId,
83        pos: BlockPos,
84        source_block: BlockRef,
85        moved_by_piston: bool,
86    ) {
87        self.add_and_run(
88            world,
89            pos,
90            NeighborUpdate::Full {
91                state,
92                pos,
93                source_block,
94                moved_by_piston,
95            },
96        );
97    }
98
99    pub(in crate::world) fn update_neighbors_at_except_from_facing(
100        &self,
101        world: &Arc<World>,
102        pos: BlockPos,
103        source_block: BlockRef,
104        skip_direction: Option<Direction>,
105    ) {
106        self.add_and_run(
107            world,
108            pos,
109            NeighborUpdate::Multi {
110                source_pos: pos,
111                source_block,
112                skip_direction,
113                index: usize::from(skip_direction == Some(UPDATE_ORDER[0])),
114            },
115        );
116    }
117
118    fn add_and_run(&self, world: &Arc<World>, pos: BlockPos, update: NeighborUpdate) {
119        let result = self
120            .state
121            .lock()
122            .enqueue(self.max_chained_neighbor_updates, update);
123        if result.first_skipped {
124            log::error!(
125                "Too many chained neighbor updates. Skipping the rest. First skipped position: {}, {}, {}",
126                pos.x(),
127                pos.y(),
128                pos.z()
129            );
130        }
131        if result.should_run {
132            self.run_updates(world);
133        }
134    }
135
136    fn run_updates(&self, world: &Arc<World>) {
137        let mut reset_guard = ResetGuard {
138            updater: self,
139            armed: true,
140        };
141        loop {
142            let next = {
143                let mut state = self.state.lock();
144                let next = state.take_next();
145                if next.is_none() {
146                    state.reset();
147                    reset_guard.armed = false;
148                }
149                next
150            };
151            let Some(mut update) = next else {
152                return;
153            };
154
155            if update.run_next(world) {
156                self.state.lock().return_unfinished(update);
157            }
158        }
159    }
160
161    fn reset(&self) {
162        self.state.lock().reset();
163    }
164}
165
166struct ResetGuard<'a> {
167    updater: &'a CollectingNeighborUpdater,
168    armed: bool,
169}
170
171impl Drop for ResetGuard<'_> {
172    fn drop(&mut self) {
173        if self.armed {
174            self.updater.reset();
175        }
176    }
177}
178
179enum NeighborUpdate {
180    Full {
181        state: BlockStateId,
182        pos: BlockPos,
183        source_block: BlockRef,
184        moved_by_piston: bool,
185    },
186    Multi {
187        source_pos: BlockPos,
188        source_block: BlockRef,
189        skip_direction: Option<Direction>,
190        index: usize,
191    },
192    Shape(ShapeUpdate),
193    Simple {
194        pos: BlockPos,
195        source_block: BlockRef,
196    },
197}
198
199impl NeighborUpdate {
200    fn run_next(&mut self, world: &Arc<World>) -> bool {
201        match self {
202            Self::Full {
203                state,
204                pos,
205                source_block,
206                moved_by_piston,
207            } => {
208                world.execute_neighbor_update(*state, *pos, source_block, *moved_by_piston);
209                false
210            }
211            Self::Multi {
212                source_pos,
213                source_block,
214                skip_direction,
215                index,
216            } => {
217                let direction = UPDATE_ORDER[*index];
218                *index += 1;
219                let neighbor_pos = source_pos.relative(direction);
220                let state = world.get_block_state(neighbor_pos);
221                world.execute_neighbor_update(state, neighbor_pos, source_block, false);
222                if *index < UPDATE_ORDER.len() && Some(UPDATE_ORDER[*index]) == *skip_direction {
223                    *index += 1;
224                }
225                *index < UPDATE_ORDER.len()
226            }
227            Self::Shape(update) => {
228                world.execute_neighbor_shape_update(
229                    update.direction,
230                    update.pos,
231                    update.neighbor_pos,
232                    update.neighbor_state,
233                    update.flags,
234                    update.update_limit,
235                );
236                false
237            }
238            Self::Simple { pos, source_block } => {
239                let state = world.get_block_state(*pos);
240                world.execute_neighbor_update(state, *pos, source_block, false);
241                false
242            }
243        }
244    }
245}
246
247struct AddResult {
248    should_run: bool,
249    first_skipped: bool,
250}
251
252struct CollectingState<U> {
253    stack: Vec<U>,
254    added_this_layer: Vec<U>,
255    count: i32,
256}
257
258impl<U> Default for CollectingState<U> {
259    fn default() -> Self {
260        Self {
261            stack: Vec::new(),
262            added_this_layer: Vec::new(),
263            count: 0,
264        }
265    }
266}
267
268impl<U> CollectingState<U> {
269    fn enqueue(&mut self, max_chained_neighbor_updates: i32, update: U) -> AddResult {
270        let running_already = self.count > 0;
271        let too_many_updates =
272            max_chained_neighbor_updates >= 0 && self.count >= max_chained_neighbor_updates;
273        self.count = self.count.wrapping_add(1);
274
275        if !too_many_updates {
276            if running_already {
277                self.added_this_layer.push(update);
278            } else {
279                self.stack.push(update);
280            }
281        }
282
283        AddResult {
284            should_run: !running_already,
285            first_skipped: too_many_updates
286                && self.count.wrapping_sub(1) == max_chained_neighbor_updates,
287        }
288    }
289
290    fn take_next(&mut self) -> Option<U> {
291        while let Some(update) = self.added_this_layer.pop() {
292            self.stack.push(update);
293        }
294        self.stack.pop()
295    }
296
297    fn return_unfinished(&mut self, update: U) {
298        self.stack.push(update);
299    }
300
301    fn reset(&mut self) {
302        self.stack.clear();
303        self.added_this_layer.clear();
304        self.count = 0;
305    }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
313    struct TestUpdate(char);
314
315    #[test]
316    fn nested_updates_interrupt_multi_update_and_keep_sibling_fifo_order() {
317        let mut state = CollectingState::default();
318        assert!(state.enqueue(1_000_000, TestUpdate('A')).should_run);
319        let multi = state.take_next();
320        assert_eq!(multi, Some(TestUpdate('A')));
321
322        assert!(!state.enqueue(1_000_000, TestUpdate('B')).should_run);
323        assert!(!state.enqueue(1_000_000, TestUpdate('C')).should_run);
324        if let Some(multi) = multi {
325            state.return_unfinished(multi);
326        }
327
328        assert_eq!(state.take_next(), Some(TestUpdate('B')));
329        assert!(!state.enqueue(1_000_000, TestUpdate('D')).should_run);
330        assert_eq!(state.take_next(), Some(TestUpdate('D')));
331        assert_eq!(state.take_next(), Some(TestUpdate('C')));
332        assert_eq!(state.take_next(), Some(TestUpdate('A')));
333        assert_eq!(state.take_next(), None);
334    }
335
336    #[test]
337    fn chained_update_limit_counts_enqueued_tasks_and_reports_only_first_skip() {
338        let mut state = CollectingState::default();
339        let first = state.enqueue(2, TestUpdate('A'));
340        let second = state.enqueue(2, TestUpdate('B'));
341        let skipped = state.enqueue(2, TestUpdate('C'));
342        let also_skipped = state.enqueue(2, TestUpdate('D'));
343
344        assert!(first.should_run);
345        assert!(!first.first_skipped);
346        assert!(!second.first_skipped);
347        assert!(skipped.first_skipped);
348        assert!(!also_skipped.first_skipped);
349        assert_eq!(state.take_next(), Some(TestUpdate('B')));
350        assert_eq!(state.take_next(), Some(TestUpdate('A')));
351        assert_eq!(state.take_next(), None);
352    }
353
354    #[test]
355    fn zero_limit_still_starts_and_resets_a_root_run_without_executing_it() {
356        let mut state = CollectingState::default();
357        let result = state.enqueue(0, TestUpdate('A'));
358
359        assert!(result.should_run);
360        assert!(result.first_skipped);
361        assert_eq!(state.take_next(), None);
362        state.reset();
363        assert!(state.enqueue(0, TestUpdate('B')).should_run);
364    }
365}