Skip to main content

cranpose_core/
applier_host.rs

1use std::{
2    cell::{BorrowMutError, Cell, RefCell, RefMut},
3    ops::{Deref, DerefMut},
4    rc::Rc,
5};
6
7use crate::{
8    Applier, NodeError, NodeId, SlotsHost,
9    slot::{DetachedSubtree, SlotLifecycleCoordinator, dispose_detached_node_now},
10};
11
12/// Detached node identities and the resources that must outlive their unmount.
13#[derive(Default)]
14pub struct NodeDisposal {
15    nodes: Vec<(NodeId, u32)>,
16    lifecycle: SlotLifecycleCoordinator,
17    slot_hosts: Vec<Rc<SlotsHost>>,
18}
19
20impl NodeDisposal {
21    /// Creates a disposal batch from node IDs and their current generations.
22    ///
23    /// ```
24    /// use cranpose_core::{Applier, MemoryApplier, Node, NodeDisposal};
25    /// struct Leaf;
26    /// impl Node for Leaf {}
27    /// let mut applier = MemoryApplier::new();
28    /// let id = applier.create(Box::new(Leaf));
29    /// let disposal = NodeDisposal::new(vec![(id, applier.node_generation(id))]);
30    /// ```
31    pub fn new(nodes: Vec<(NodeId, u32)>) -> Self {
32        Self {
33            nodes,
34            lifecycle: SlotLifecycleCoordinator::default(),
35            slot_hosts: Vec::new(),
36        }
37    }
38
39    pub(crate) fn from_subcomposed(
40        disposal: crate::subcompose::SubcomposeDisposal,
41        applier: &dyn Applier,
42    ) -> Self {
43        Self {
44            nodes: disposal
45                .nodes
46                .into_iter()
47                .map(|id| (id, applier.node_generation(id)))
48                .collect(),
49            lifecycle: SlotLifecycleCoordinator::default(),
50            slot_hosts: disposal.slot_hosts,
51        }
52    }
53
54    pub(crate) fn retain_subtree(&mut self, subtree: DetachedSubtree) {
55        subtree.append_disposal_roots(&mut self.nodes);
56        self.lifecycle.queue_subtree_disposal(subtree);
57    }
58}
59
60#[derive(Default)]
61struct NodeDisposalQueue {
62    batches: RefCell<Vec<NodeDisposal>>,
63    pending: Cell<bool>,
64}
65
66impl NodeDisposalQueue {
67    fn enqueue(&self, disposal: NodeDisposal) {
68        if !disposal.nodes.is_empty()
69            || !disposal.slot_hosts.is_empty()
70            || disposal.lifecycle.pending_drops_len() != 0
71        {
72            self.batches.borrow_mut().push(disposal);
73            self.pending.set(true);
74        }
75    }
76
77    fn flush<A: Applier + ?Sized>(
78        &self,
79        applier: &mut A,
80        dispose: fn(&mut A, NodeId) -> Result<(), NodeError>,
81    ) -> Result<(), NodeError> {
82        while self.pending.get() {
83            let next = self.batches.borrow_mut().pop();
84            let Some(mut batch) = next else {
85                self.pending.set(false);
86                break;
87            };
88            if let Some((node, generation)) = batch.nodes.pop() {
89                let resume_at = self.batches.borrow().len();
90                self.batches.borrow_mut().push(batch);
91                if applier.node_generation(node) == generation
92                    && let Err(error) = dispose(applier, node)
93                {
94                    self.batches.borrow_mut()[resume_at]
95                        .nodes
96                        .push((node, generation));
97                    return Err(error);
98                }
99            } else if let Some(host) = batch.slot_hosts.pop() {
100                self.batches.borrow_mut().push(batch);
101                drop(host);
102            } else if let Some(payload) = batch.lifecycle.pop_pending_drop() {
103                self.batches.borrow_mut().push(batch);
104                drop(payload);
105            }
106        }
107        Ok(())
108    }
109}
110
111/// Owns an applier and completes node disposal when an outstanding borrow ends.
112pub trait ApplierHost {
113    /// Borrows the applier mutably as a trait object. Dropping the guard finishes any node disposal
114    /// queued while it was held.
115    fn borrow_dyn(&self) -> ApplierGuard<'_, dyn Applier>;
116
117    /// Disposes detached roots, deferring recursive cleanup to the current borrow's guard.
118    ///
119    /// ```
120    /// use cranpose_core::{
121    ///     Applier, ApplierHost, ConcreteApplierHost, MemoryApplier, Node, NodeDisposal,
122    /// };
123    /// # fn example() -> Result<(), cranpose_core::NodeError> {
124    /// struct Leaf;
125    /// impl Node for Leaf {}
126    /// let host = ConcreteApplierHost::new(MemoryApplier::new());
127    /// let mut applier = host.borrow_typed();
128    /// let root = applier.create(Box::new(Leaf));
129    /// host.dispose_nodes(NodeDisposal::new(vec![(
130    ///     root,
131    ///     applier.node_generation(root),
132    /// )]))?;
133    /// drop(applier);
134    /// assert!(host.borrow_typed().get_mut(root).is_err());
135    /// # Ok(())
136    /// # }
137    /// ```
138    fn dispose_nodes(&self, disposal: NodeDisposal) -> Result<(), NodeError>;
139
140    /// Compacts internal storage after commands have been applied.
141    fn compact(&self) {}
142}
143
144/// An [`ApplierHost`] that stores one applier of the concrete type `A`.
145pub struct ConcreteApplierHost<A: Applier + 'static> {
146    inner: RefCell<A>,
147    disposals: NodeDisposalQueue,
148}
149
150impl<A: Applier + 'static> ConcreteApplierHost<A> {
151    /// Creates a host that owns `applier`.
152    pub fn new(applier: A) -> Self {
153        Self {
154            inner: RefCell::new(applier),
155            disposals: NodeDisposalQueue::default(),
156        }
157    }
158
159    /// Borrows the applier mutably with its concrete type. Panics if it is already borrowed.
160    pub fn borrow_typed(&self) -> ApplierGuard<'_, A> {
161        self.guard(self.inner.borrow_mut())
162    }
163
164    /// Borrows the applier mutably with its concrete type. Returns an error if it is already
165    /// borrowed.
166    pub fn try_borrow_typed(&self) -> Result<ApplierGuard<'_, A>, BorrowMutError> {
167        self.inner.try_borrow_mut().map(|inner| self.guard(inner))
168    }
169
170    fn guard<'a>(&'a self, inner: RefMut<'a, A>) -> ApplierGuard<'a, A> {
171        ApplierGuard {
172            inner,
173            disposals: &self.disposals,
174            dispose: |applier, node| dispose_detached_node_now(applier, node),
175        }
176    }
177
178    /// Returns the applier after all queued nodes have been disposed.
179    ///
180    /// On failure, returns this host with its pending resources and the error,
181    /// allowing the caller to repair the cause and retry extraction.
182    ///
183    /// ```
184    /// use cranpose_core::{ConcreteApplierHost, MemoryApplier};
185    /// let host = ConcreteApplierHost::new(MemoryApplier::new());
186    /// assert!(host.try_into_inner().is_ok());
187    /// ```
188    pub fn try_into_inner(mut self) -> Result<A, (Self, NodeError)> {
189        if let Err(error) = self.disposals.flush(self.inner.get_mut(), |applier, node| {
190            dispose_detached_node_now(applier, node)
191        }) {
192            return Err((self, error));
193        }
194        Ok(self.inner.into_inner())
195    }
196}
197
198impl<A: Applier + 'static> ApplierHost for ConcreteApplierHost<A> {
199    fn borrow_dyn(&self) -> ApplierGuard<'_, dyn Applier> {
200        ApplierGuard {
201            inner: RefMut::map(self.inner.borrow_mut(), |applier| {
202                applier as &mut dyn Applier
203            }),
204            disposals: &self.disposals,
205            dispose: dispose_detached_node_now,
206        }
207    }
208
209    fn dispose_nodes(&self, disposal: NodeDisposal) -> Result<(), NodeError> {
210        self.disposals.enqueue(disposal);
211        if let Ok(mut applier) = self.inner.try_borrow_mut() {
212            return self.disposals.flush(&mut *applier, |applier, node| {
213                dispose_detached_node_now(applier, node)
214            });
215        }
216        Ok(())
217    }
218
219    fn compact(&self) {
220        self.borrow_typed().compact();
221    }
222}
223
224/// A mutable applier borrow that drains recursive node disposal before returning.
225pub struct ApplierGuard<'a, A: Applier + ?Sized + 'static> {
226    inner: RefMut<'a, A>,
227    disposals: &'a NodeDisposalQueue,
228    dispose: fn(&mut A, NodeId) -> Result<(), NodeError>,
229}
230
231impl<A: Applier + ?Sized + 'static> ApplierGuard<'_, A> {
232    fn flush_disposals(&mut self) -> Result<(), NodeError> {
233        self.disposals.flush(&mut *self.inner, self.dispose)
234    }
235}
236
237impl<A: Applier + ?Sized + 'static> Deref for ApplierGuard<'_, A> {
238    type Target = A;
239
240    fn deref(&self) -> &Self::Target {
241        &self.inner
242    }
243}
244
245impl<A: Applier + ?Sized + 'static> DerefMut for ApplierGuard<'_, A> {
246    fn deref_mut(&mut self) -> &mut Self::Target {
247        &mut self.inner
248    }
249}
250
251impl<A: Applier + ?Sized + 'static> Drop for ApplierGuard<'_, A> {
252    fn drop(&mut self) {
253        if self.disposals.pending.get()
254            && let Err(error) = self.flush_disposals()
255        {
256            log::error!("deferred node disposal failed: {error}");
257        }
258    }
259}