Skip to main content

moire_runtime/
handles.rs

1use moire_types::{
2    CustomEventKind, EdgeKind, Entity, EntityBody, EntityBodySlot, EntityId, EventKind,
3    EventTarget, Json, Scope, ScopeBody, ScopeId,
4};
5use std::marker::PhantomData;
6use std::sync::{Arc, Weak};
7
8use super::db::runtime_db;
9
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct EntityRef {
12    id: EntityId,
13}
14
15impl EntityRef {
16    pub fn id(&self) -> &EntityId {
17        &self.id
18    }
19}
20
21pub fn entity_ref_from_wire(id: impl Into<String>) -> EntityRef {
22    EntityRef {
23        id: EntityId::new(id.into()),
24    }
25}
26
27pub fn current_causal_target() -> Option<EntityRef> {
28    current_causal_target_from_stack()
29}
30
31pub fn current_causal_target_from_stack() -> Option<EntityRef> {
32    super::FUTURE_CAUSAL_STACK
33        .try_with(|stack| {
34            stack.borrow().last().map(|id| EntityRef {
35                id: EntityId::new(id.as_str()),
36            })
37        })
38        .ok()
39        .flatten()
40}
41
42pub fn current_causal_target_with_task_fallback() -> Option<EntityRef> {
43    current_causal_target_from_stack()
44        .or_else(|| super::aether_entity_for_current_task().map(|id| EntityRef { id }))
45}
46
47#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub struct ScopeRef {
49    id: ScopeId,
50}
51
52impl ScopeRef {
53    pub fn id(&self) -> &ScopeId {
54        &self.id
55    }
56}
57
58struct ScopeHandleInner {
59    id: ScopeId,
60}
61
62impl Drop for ScopeHandleInner {
63    fn drop(&mut self) {
64        if let Ok(mut db) = runtime_db().lock() {
65            db.remove_scope(&self.id);
66        }
67    }
68}
69
70#[derive(Clone)]
71pub struct ScopeHandle {
72    inner: Arc<ScopeHandleInner>,
73}
74
75impl ScopeHandle {
76    pub fn new(name: impl Into<String>, body: ScopeBody) -> Self {
77        let scope = Scope::new(super::capture_backtrace_id(), name, body);
78        let id = ScopeId::new(scope.id.as_str());
79
80        if let Ok(mut db) = runtime_db().lock() {
81            db.upsert_scope(scope);
82        }
83
84        Self {
85            inner: Arc::new(ScopeHandleInner { id }),
86        }
87    }
88
89    pub fn id(&self) -> &ScopeId {
90        &self.inner.id
91    }
92
93    pub fn scope_ref(&self) -> ScopeRef {
94        ScopeRef {
95            id: ScopeId::new(self.inner.id.as_str()),
96        }
97    }
98}
99
100struct HandleInner {
101    id: EntityId,
102    kind_name: &'static str,
103}
104
105impl Drop for HandleInner {
106    fn drop(&mut self) {
107        if let Ok(mut db) = runtime_db().lock() {
108            db.remove_entity(&self.id);
109        }
110    }
111}
112
113pub struct EntityHandle<S> {
114    inner: Arc<HandleInner>,
115    _slot: PhantomData<S>,
116}
117
118impl<S> Clone for EntityHandle<S> {
119    fn clone(&self) -> Self {
120        Self {
121            inner: Arc::clone(&self.inner),
122            _slot: PhantomData,
123        }
124    }
125}
126
127impl<S> EntityHandle<S> {
128    fn from_entity(entity: Entity) -> Self {
129        let kind_name = entity.body.kind_name();
130        let id = EntityId::new(entity.id.as_str());
131
132        if let Ok(mut db) = runtime_db().lock() {
133            db.upsert_entity(entity);
134        }
135
136        Self {
137            inner: Arc::new(HandleInner { id, kind_name }),
138            _slot: PhantomData,
139        }
140    }
141}
142
143impl<S> EntityHandle<S>
144where
145    S: EntityBodySlot<Value = S> + Into<EntityBody>,
146{
147    pub fn new(name: impl Into<String>, body: S) -> Self {
148        let entity = Entity::new(super::capture_backtrace_id(), name, body.into());
149        Self::from_entity(entity)
150    }
151}
152
153impl<S> EntityHandle<S> {
154    pub fn id(&self) -> &EntityId {
155        &self.inner.id
156    }
157
158    pub fn rename(&self, name: impl Into<String>) -> bool {
159        let mut db = runtime_db()
160            .lock()
161            .expect("runtime db lock poisoned during entity rename");
162        db.rename_entity_and_maybe_upsert(self.id(), name)
163    }
164
165    pub fn kind_name(&self) -> &'static str {
166        self.inner.kind_name
167    }
168
169    pub fn entity_ref(&self) -> EntityRef {
170        EntityRef {
171            id: EntityId::new(self.inner.id.as_str()),
172        }
173    }
174
175    pub fn link_to(&self, target: &EntityRef, kind: EdgeKind) {
176        if let Ok(mut db) = runtime_db().lock() {
177            db.upsert_edge(self.id(), target.id(), kind, super::capture_backtrace_id());
178        }
179    }
180
181    pub fn link_to_handle<T>(&self, target: &EntityHandle<T>, kind: EdgeKind) {
182        self.link_to(&target.entity_ref(), kind);
183    }
184}
185
186impl<S> EntityHandle<S>
187where
188    S: EntityBodySlot,
189{
190    pub fn mutate(&self, f: impl FnOnce(&mut S::Value)) -> bool {
191        if self.kind_name() != S::KIND_NAME {
192            panic!(
193                "entity kind mismatch for mutate: handle kind={} slot kind={} entity_id={}",
194                self.kind_name(),
195                S::KIND_NAME,
196                self.id().as_str(),
197            );
198        }
199
200        let mut db = runtime_db()
201            .lock()
202            .expect("runtime db lock poisoned during entity mutate");
203        db.mutate_entity_body_and_maybe_upsert(self.id(), |body| {
204            let slot = S::project_mut(body).unwrap_or_else(|| {
205                panic!(
206                    "entity body projection failed after kind check: kind={} entity_id={}",
207                    S::KIND_NAME,
208                    self.id().as_str(),
209                )
210            });
211            f(slot);
212        })
213    }
214}
215
216impl<S> EntityHandle<S> {
217    /// Emit a custom event on this entity.
218    pub fn emit_event(
219        &self,
220        kind: impl Into<String>,
221        display_name: impl Into<String>,
222        payload: Json,
223    ) {
224        let event = super::new_event(
225            EventTarget::Entity(EntityId::new(self.id().as_str())),
226            EventKind::Custom(CustomEventKind {
227                kind: kind.into(),
228                display_name: display_name.into(),
229                payload,
230            }),
231        );
232        super::record_event(event);
233    }
234}
235
236impl<S> EntityHandle<S> {
237    pub fn downgrade(&self) -> WeakEntityHandle<S> {
238        WeakEntityHandle {
239            inner: Arc::downgrade(&self.inner),
240            _slot: PhantomData,
241        }
242    }
243}
244
245/// A non-owning reference to an entity. Does not keep the entity alive.
246/// When the last `EntityHandle` for the entity drops, the entity is removed
247/// from the graph and subsequent `mutate` calls on any `WeakEntityHandle`
248/// pointing to it become no-ops.
249pub struct WeakEntityHandle<S> {
250    inner: Weak<HandleInner>,
251    _slot: PhantomData<S>,
252}
253
254impl<S> Clone for WeakEntityHandle<S> {
255    fn clone(&self) -> Self {
256        Self {
257            inner: self.inner.clone(),
258            _slot: PhantomData,
259        }
260    }
261}
262
263impl<S> WeakEntityHandle<S>
264where
265    S: EntityBodySlot,
266{
267    pub fn rename(&self, name: impl Into<String>) -> bool {
268        let Some(inner) = self.inner.upgrade() else {
269            return false;
270        };
271        let mut db = runtime_db()
272            .lock()
273            .expect("runtime db lock poisoned during weak entity rename");
274        db.rename_entity_and_maybe_upsert(&inner.id, name)
275    }
276
277    pub fn mutate(&self, f: impl FnOnce(&mut S::Value)) -> bool {
278        let Some(inner) = self.inner.upgrade() else {
279            return false;
280        };
281        let mut db = runtime_db()
282            .lock()
283            .expect("runtime db lock poisoned during weak entity mutate");
284        db.mutate_entity_body_and_maybe_upsert(&inner.id, |body| {
285            let slot = S::project_mut(body).unwrap_or_else(|| {
286                panic!(
287                    "entity body projection failed: kind={} entity_id={}",
288                    S::KIND_NAME,
289                    inner.id.as_str(),
290                )
291            });
292            f(slot);
293        })
294    }
295}
296
297/// An owned edge that removes itself from the graph when dropped.
298///
299/// Does not keep either endpoint entity alive — it only stores `EntityId` values.
300/// If an endpoint entity is removed before this handle drops, the edge is already
301/// gone and the Drop impl becomes a no-op.
302pub struct EdgeHandle {
303    src: EntityId,
304    dst: EntityId,
305    kind: EdgeKind,
306}
307
308impl Drop for EdgeHandle {
309    fn drop(&mut self) {
310        if let Ok(mut db) = runtime_db().lock() {
311            db.remove_edge(&self.src, &self.dst, self.kind);
312        }
313    }
314}
315
316impl<S> EntityHandle<S> {
317    pub fn link_to_owned(&self, target: &impl AsEntityRef, kind: EdgeKind) -> EdgeHandle {
318        self.as_entity_ref().link_to_owned(target, kind)
319    }
320}
321
322impl EntityRef {
323    pub fn link_to_owned(&self, target: &impl AsEntityRef, kind: EdgeKind) -> EdgeHandle {
324        let src = self.id().clone();
325        let dst = target.as_entity_ref().id().clone();
326        if let Ok(mut db) = runtime_db().lock() {
327            db.upsert_edge(&src, &dst, kind, super::capture_backtrace_id());
328        }
329        EdgeHandle { src, dst, kind }
330    }
331}
332
333/// A type that can be used as the `on =` argument of the `moire!()` macro.
334pub trait AsEntityRef {
335    fn as_entity_ref(&self) -> EntityRef;
336}
337
338impl<S> AsEntityRef for EntityHandle<S> {
339    fn as_entity_ref(&self) -> EntityRef {
340        self.entity_ref()
341    }
342}
343
344impl AsEntityRef for EntityRef {
345    fn as_entity_ref(&self) -> EntityRef {
346        self.clone()
347    }
348}