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] = [
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}