Skip to main content

steel_core/entity/
reference.rs

1use std::sync::{Arc, Weak};
2
3use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
4use simdnbt::owned::{NbtCompound, NbtTag};
5use steel_utils::{UuidExt as _, locks::SyncMutex};
6use uuid::Uuid;
7
8use crate::world::World;
9
10use super::{SharedEntity, WeakEntity};
11
12/// A UUID reference that re-resolves removed entities within the same domain.
13///
14/// Its cache is weak to avoid entity ownership cycles.
15#[derive(Clone)]
16pub struct EntityReference {
17    uuid: Uuid,
18    // Clones share resolution while callers release their own state locks before lookup.
19    cached: Arc<SyncMutex<Option<WeakEntity>>>,
20}
21
22impl EntityReference {
23    /// Creates an unresolved reference from a persisted UUID.
24    #[must_use]
25    pub fn from_uuid(uuid: Uuid) -> Self {
26        Self {
27            uuid,
28            cached: Arc::new(SyncMutex::new(None)),
29        }
30    }
31
32    /// Creates a reference with a weak cache of a live entity.
33    #[must_use]
34    pub fn from_entity(entity: &SharedEntity) -> Self {
35        Self {
36            uuid: entity.uuid(),
37            cached: Arc::new(SyncMutex::new(Some(Arc::downgrade(entity)))),
38        }
39    }
40
41    /// Reads a UUID reference; absent or malformed values have no reference.
42    #[must_use]
43    pub fn read(nbt: &BorrowedNbtCompoundView<'_, '_>, key: &str) -> Option<Self> {
44        Uuid::from_int_array(&nbt.int_array(key)?).map(Self::from_uuid)
45    }
46
47    /// Stores the UUID as an NBT int array.
48    pub fn store(&self, nbt: &mut NbtCompound, key: &str) {
49        nbt.insert(key, NbtTag::IntArray(self.uuid.to_int_array().to_vec()));
50    }
51
52    /// Returns the persistent referenced UUID.
53    #[must_use]
54    pub const fn uuid(&self) -> Uuid {
55        self.uuid
56    }
57
58    /// Caches an entity only when its UUID matches this reference.
59    pub fn cache_entity(&self, entity: &SharedEntity) {
60        if entity.uuid() == self.uuid {
61            *self.cached.lock() = Some(Arc::downgrade(entity));
62        }
63    }
64
65    /// Resolves a live cache, then the UUID in any loaded world in this domain.
66    #[must_use]
67    pub fn get_entity(&self, world: &World) -> Option<SharedEntity> {
68        {
69            let mut cached = self.cached.lock();
70            if let Some(entity) = cached.as_ref().and_then(Weak::upgrade)
71                && !entity.is_removed()
72                && entity.uuid() == self.uuid
73                && entity
74                    .level()
75                    .is_some_and(|level| level.domain() == world.domain())
76            {
77                return Some(entity);
78            }
79            *cached = None;
80        }
81
82        let entity = world.get_entity_in_domain_by_uuid(&self.uuid)?;
83        if entity.is_removed() || entity.uuid() != self.uuid {
84            return None;
85        }
86        self.cache_entity(&entity);
87        Some(entity)
88    }
89}