steel_core/world/block_updates/
neighbor_updater.rs1use 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] = [
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}