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