1use parking_lot::ArcMutexGuard;
8use parking_lot::RawMutex;
9use rustc_hash::FxHashMap;
10use std::borrow::Borrow;
11use std::mem;
12use std::ops::{Deref, DerefMut};
13use std::sync::Arc;
14use steel_utils::locks::Shared;
15use steel_utils::{Downcast as _, DowncastType, locks::SyncMutex};
16
17use crate::{
18 block_entity::{BlockEntityBase, SharedBlockEntity},
19 inventory::container::Container,
20 player::{Player, player_inventory::PlayerInventory},
21};
22use steel_registry::item_stack::ItemStack;
23
24pub type SharedContainer = Shared<dyn Container>;
26
27struct LockedContainer(ArcMutexGuard<RawMutex, dyn Container>);
28
29impl Deref for LockedContainer {
30 type Target = dyn Container;
31
32 fn deref(&self) -> &Self::Target {
33 &*self.0
34 }
35}
36
37impl DerefMut for LockedContainer {
38 fn deref_mut(&mut self) -> &mut Self::Target {
39 &mut *self.0
40 }
41}
42
43#[derive(Clone)]
48pub struct ContainerRef {
49 id: ContainerId,
50 source: SharedContainer,
51 owner: Option<Arc<BlockEntityBase>>,
52}
53
54impl<T> From<Shared<T>> for ContainerRef
55where
56 T: Container + 'static,
57{
58 fn from(container: Shared<T>) -> Self {
59 let id = ContainerId::from_arc(&container);
60 let container: SharedContainer = container;
61 Self {
62 id,
63 source: container,
64 owner: None,
65 }
66 }
67}
68
69impl<T> From<&Shared<T>> for ContainerRef
70where
71 T: Container + 'static,
72{
73 fn from(container: &Shared<T>) -> Self {
74 container.clone().into()
75 }
76}
77
78impl From<&ContainerRef> for ContainerRef {
79 fn from(r: &ContainerRef) -> Self {
80 r.clone()
81 }
82}
83
84impl From<&SharedContainer> for ContainerRef {
85 fn from(container: &SharedContainer) -> Self {
86 container.clone().into()
87 }
88}
89
90impl From<SharedContainer> for ContainerRef {
91 fn from(container: SharedContainer) -> Self {
92 Self {
93 id: ContainerId::from_arc(&container),
94 source: container,
95 owner: None,
96 }
97 }
98}
99
100impl ContainerRef {
101 #[must_use]
105 pub fn from_block_entity(block_entity: SharedBlockEntity) -> Option<Self> {
106 block_entity.container_ref()
107 }
108
109 #[must_use]
116 pub fn owned_by_block_entity(container: SharedContainer, owner: Arc<BlockEntityBase>) -> Self {
117 Self {
118 id: ContainerId::from_arc(&container),
119 source: container,
120 owner: Some(owner),
121 }
122 }
123
124 #[must_use]
126 pub const fn container_id(&self) -> ContainerId {
127 self.id
128 }
129
130 #[must_use]
132 pub fn still_valid(&self, player: &Player) -> bool {
133 self.owner
134 .as_ref()
135 .is_none_or(|owner| owner.is_valid_container_for(player))
136 }
137
138 fn lock(&self) -> LockedContainer {
140 LockedContainer(SyncMutex::lock_arc(&self.source))
141 }
142}
143
144pub struct ContainerLockGuard {
164 sources: Vec<(ContainerId, ContainerRef)>,
166 guards: Vec<(ContainerId, LockedContainer)>,
168 id_to_index: FxHashMap<ContainerId, usize>,
170}
171
172impl ContainerLockGuard {
173 #[must_use]
179 pub fn lock_all<C>(containers: &[C]) -> Self
180 where
181 C: Borrow<ContainerRef>,
182 {
183 let mut sources: Vec<_> = containers
184 .iter()
185 .map(|c| (c.borrow().container_id(), c.borrow().clone()))
186 .collect();
187
188 sources.sort_by_key(|(id, _)| *id);
190
191 sources.dedup_by_key(|(id, _)| *id);
193
194 let mut guards = Vec::with_capacity(sources.len());
196 for (id, container) in &sources {
197 let guard = container.lock();
198 guards.push((*id, guard));
199 }
200
201 let id_to_index = guards
203 .iter()
204 .enumerate()
205 .map(|(idx, (id, _))| (*id, idx))
206 .collect();
207
208 Self {
209 sources,
210 guards,
211 id_to_index,
212 }
213 }
214
215 pub fn get_disjoint_mut<const N: usize>(
219 &mut self,
220 ids: [ContainerId; N],
221 ) -> Option<[&mut dyn Container; N]> {
222 let mut indices = [0usize; N];
223 for (i, id) in ids.iter().enumerate() {
224 indices[i] = *self.id_to_index.get(id)?;
225 }
226 let entries = self.guards.get_disjoint_mut(indices).ok()?;
227 Some(entries.map(|(_, locked)| &mut **locked as &mut dyn Container))
228 }
229
230 #[must_use]
235 pub fn relock(self, containers: &[&ContainerRef]) -> Self {
236 drop(self);
238 Self::lock_all(containers)
240 }
241
242 #[must_use]
244 pub fn get(&self, id: impl Into<ContainerId>) -> Option<&dyn Container> {
245 self.id_to_index
246 .get(&id.into())
247 .and_then(|&idx| self.guards.get(idx))
248 .map(|(_, guard)| &**guard as &dyn Container)
249 }
250
251 pub fn get_mut(&mut self, id: impl Into<ContainerId>) -> Option<&mut dyn Container> {
256 self.id_to_index
257 .get(&id.into())
258 .copied()
259 .and_then(|idx| self.guards.get_mut(idx))
260 .map(|(_, guard)| &mut **guard as &mut dyn Container)
261 }
262
263 pub fn set_item(&mut self, id: impl Into<ContainerId>, slot: usize, stack: ItemStack) -> bool {
268 let id = id.into();
269 let Some(&index) = self.id_to_index.get(&id) else {
270 return false;
271 };
272 self.guards[index].1.set_item(slot, stack);
273 let owner = self.sources[index].1.owner.clone();
274 self.notify_owner(owner);
275 true
276 }
277
278 pub fn remove_item(
280 &mut self,
281 id: impl Into<ContainerId>,
282 slot: usize,
283 amount: i32,
284 ) -> Option<ItemStack> {
285 let id = id.into();
286 let index = *self.id_to_index.get(&id)?;
287 let removed = self.guards.get_mut(index)?.1.remove_item(slot, amount);
288 if !removed.is_empty() {
289 let owner = self.sources.get(index)?.1.owner.clone();
290 self.notify_owner(owner);
291 }
292 Some(removed)
293 }
294
295 pub fn set_changed(&mut self, id: impl Into<ContainerId>) -> bool {
298 let id = id.into();
299 let Some(&index) = self.id_to_index.get(&id) else {
300 return false;
301 };
302 self.guards[index].1.set_changed();
303 let owner = self.sources[index].1.owner.clone();
304 self.notify_owner(owner);
305 true
306 }
307
308 pub(crate) fn run_unlocked<R>(&mut self, callback: impl FnOnce() -> R) -> R {
311 drop(mem::take(&mut self.guards));
312 let result = callback();
313 self.guards = self
314 .sources
315 .iter()
316 .map(|(id, container)| (*id, container.lock()))
317 .collect();
318 result
319 }
320
321 fn notify_owner(&mut self, owner: Option<Arc<BlockEntityBase>>) {
322 let Some(owner) = owner else {
323 return;
324 };
325
326 self.run_unlocked(|| owner.set_changed());
327 }
328
329 #[must_use]
331 pub fn get_typed<T>(&self, id: impl Into<ContainerId>) -> Option<&T>
332 where
333 T: Container + DowncastType,
334 {
335 self.get(id)?.downcast_ref::<T>()
336 }
337
338 pub fn get_typed_mut<T>(&mut self, id: impl Into<ContainerId>) -> Option<&mut T>
343 where
344 T: Container + DowncastType,
345 {
346 self.get_mut(id)?.downcast_mut::<T>()
347 }
348
349 pub fn get_two_typed_mut<A, B>(
354 &mut self,
355 first: impl Into<ContainerId>,
356 second: impl Into<ContainerId>,
357 ) -> Option<(&mut A, &mut B)>
358 where
359 A: Container + DowncastType,
360 B: Container + DowncastType,
361 {
362 let first_index = *self.id_to_index.get(&first.into())?;
363 let second_index = *self.id_to_index.get(&second.into())?;
364 if first_index == second_index {
365 return None;
366 }
367
368 let (first, second): (&mut dyn Container, &mut dyn Container) =
369 if first_index < second_index {
370 let (before_second, from_second) = self.guards.split_at_mut(second_index);
371 (&mut *before_second[first_index].1, &mut *from_second[0].1)
372 } else {
373 let (before_first, from_first) = self.guards.split_at_mut(first_index);
374 (&mut *from_first[0].1, &mut *before_first[second_index].1)
375 };
376 Some((first.downcast_mut::<A>()?, second.downcast_mut::<B>()?))
377 }
378
379 #[must_use]
381 pub fn contains(&self, id: ContainerId) -> bool {
382 self.id_to_index.contains_key(&id)
383 }
384}
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
391pub struct ContainerId(usize);
392
393impl ContainerId {
394 pub fn from_arc<T: ?Sized>(arc: &Arc<T>) -> Self {
396 Self(Arc::as_ptr(arc).cast::<()>() as usize)
397 }
398}
399
400impl From<&Shared<PlayerInventory>> for ContainerId {
401 fn from(value: &Shared<PlayerInventory>) -> Self {
402 Self::from_arc(value)
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use std::sync::{Arc, Weak};
409
410 use steel_registry::blocks::block_state_ext::BlockStateExt as _;
411 use steel_registry::blocks::properties::{BlockStateProperties, Direction};
412 use steel_registry::{
413 init_vanilla_registry, item_stack::ItemStack, vanilla_block_entity_types, vanilla_blocks,
414 vanilla_items,
415 };
416 use steel_utils::types::UpdateFlags;
417 use steel_utils::{BlockPos, ChunkPos, locks::SyncMutex};
418
419 use super::{ContainerId, ContainerLockGuard, ContainerRef};
420 use crate::behavior::{BLOCK_BEHAVIORS, init_behaviors};
421 use crate::block_entity::{
422 SharedBlockEntity,
423 entities::{BarrelBlockEntity, RawBlockEntity},
424 init_block_entities,
425 };
426 use crate::inventory::container::{Container, CraftingContainer, ResultContainer};
427 use crate::inventory::slots::{NormalSlot, Slot as _};
428 use crate::test_support::{fresh_test_world, insert_ready_full_chunk};
429
430 #[test]
431 fn erased_container_ref_preserves_id_and_typed_access() {
432 let crafting = Arc::new(SyncMutex::new(CraftingContainer::new(2, 2)));
433 let id = ContainerId::from_arc(&crafting);
434 let container_ref = ContainerRef::from(Arc::clone(&crafting));
435
436 assert_eq!(container_ref.container_id(), id);
437
438 let mut guard = ContainerLockGuard::lock_all(&[&container_ref]);
439 let Some(typed) = guard.get_typed::<CraftingContainer>(id) else {
440 panic!("erased crafting container should retain its concrete type");
441 };
442 assert_eq!((typed.width(), typed.height()), (2, 2));
443 assert!(guard.get_typed::<ResultContainer>(id).is_none());
444 assert!(guard.get_typed_mut::<CraftingContainer>(id).is_some());
445 }
446
447 #[test]
448 fn block_entity_container_capability_is_independently_lockable() {
449 init_vanilla_registry();
450 let barrel = Arc::new(BarrelBlockEntity::new(
451 Weak::new(),
452 BlockPos::new(1, 2, 3),
453 vanilla_blocks::BARREL.default_state(),
454 ));
455 let block_entity: SharedBlockEntity = barrel.clone();
456 let Some(container_ref) = ContainerRef::from_block_entity(block_entity) else {
457 panic!("barrel block entity should expose Container");
458 };
459 let id = container_ref.container_id();
460
461 let guard = ContainerLockGuard::lock_all(&[&container_ref]);
462 assert_eq!(guard.get(id).map(Container::get_container_size), Some(27));
463 }
464
465 #[test]
466 fn non_container_block_entity_ref_is_rejected() {
467 init_vanilla_registry();
468 let block_entity: SharedBlockEntity = Arc::new(RawBlockEntity::new(
469 &vanilla_block_entity_types::END_PORTAL,
470 Weak::new(),
471 BlockPos::new(1, 2, 3),
472 vanilla_blocks::END_PORTAL.default_state(),
473 ));
474
475 assert!(ContainerRef::from_block_entity(block_entity).is_none());
476 }
477
478 #[test]
479 fn unlocked_callback_releases_and_reacquires_every_container() {
480 let crafting = Arc::new(SyncMutex::new(CraftingContainer::new(2, 2)));
481 let result = Arc::new(SyncMutex::new(ResultContainer::new()));
482 let crafting_ref = ContainerRef::from(Arc::clone(&crafting));
483 let result_ref = ContainerRef::from(Arc::clone(&result));
484 let mut guard = ContainerLockGuard::lock_all(&[&crafting_ref, &result_ref]);
485
486 let both_unlocked = guard.run_unlocked(|| {
487 let crafting_guard = crafting.try_lock();
488 let result_guard = result.try_lock();
489 crafting_guard.is_some() && result_guard.is_some()
490 });
491
492 assert!(both_unlocked);
493 assert!(guard.get(crafting_ref.container_id()).is_some());
494 assert!(guard.get(result_ref.container_id()).is_some());
495 }
496
497 #[test]
498 fn barrel_change_reenters_analog_read_without_holding_container_lock() {
499 init_vanilla_registry();
500 init_behaviors();
501 init_block_entities();
502 let world = fresh_test_world("barrel_comparator_reentry");
503 let barrel_pos = BlockPos::new(8, 64, 8);
504 let comparator_pos = barrel_pos.west();
505 insert_ready_full_chunk(&world, ChunkPos::from_block_pos(barrel_pos));
506 assert!(world.set_block(
507 comparator_pos.below(),
508 vanilla_blocks::STONE.default_state(),
509 UpdateFlags::UPDATE_NONE,
510 ));
511 assert!(world.set_block(
512 barrel_pos,
513 vanilla_blocks::BARREL.default_state(),
514 UpdateFlags::UPDATE_NONE,
515 ));
516 let comparator_state = vanilla_blocks::COMPARATOR
517 .default_state()
518 .set_value(&BlockStateProperties::HORIZONTAL_FACING, Direction::East);
519 assert!(world.set_block(comparator_pos, comparator_state, UpdateFlags::UPDATE_NONE,));
520 assert!(!world.has_scheduled_block_tick(comparator_pos, &vanilla_blocks::COMPARATOR));
521
522 let container_ref = ContainerRef::from_block_entity(
523 world
524 .get_block_entity(barrel_pos)
525 .expect("barrel should create its block entity"),
526 )
527 .expect("barrel should expose a container capability");
528 let slot = NormalSlot::new(container_ref.clone(), 0);
529 let mut guard = ContainerLockGuard::lock_all(&[&container_ref]);
530 slot.set_item(&mut guard, ItemStack::new(&vanilla_items::STONE));
531 drop(guard);
532
533 let analog = BLOCK_BEHAVIORS
534 .get_behavior(&vanilla_blocks::BARREL)
535 .get_analog_output_signal(
536 world.get_block_state(barrel_pos),
537 world.as_ref(),
538 barrel_pos,
539 Direction::West,
540 );
541 assert_eq!(analog, 1);
542 assert!(
543 world.has_scheduled_block_tick(comparator_pos, &vanilla_blocks::COMPARATOR),
544 "barrel callback did not schedule the comparator despite analog output {analog}"
545 );
546 }
547}