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    fn borrow_dyn(&self) -> ApplierGuard<'_, dyn Applier>;
114
115    /// Disposes detached roots, deferring recursive cleanup to the current borrow's guard.
116    ///
117    /// ```
118    /// use cranpose_core::{
119    ///     Applier, ApplierHost, ConcreteApplierHost, MemoryApplier, Node, NodeDisposal,
120    /// };
121    /// # fn example() -> Result<(), cranpose_core::NodeError> {
122    /// struct Leaf;
123    /// impl Node for Leaf {}
124    /// let host = ConcreteApplierHost::new(MemoryApplier::new());
125    /// let mut applier = host.borrow_typed();
126    /// let root = applier.create(Box::new(Leaf));
127    /// host.dispose_nodes(NodeDisposal::new(vec![(
128    ///     root,
129    ///     applier.node_generation(root),
130    /// )]))?;
131    /// drop(applier);
132    /// assert!(host.borrow_typed().get_mut(root).is_err());
133    /// # Ok(())
134    /// # }
135    /// ```
136    fn dispose_nodes(&self, disposal: NodeDisposal) -> Result<(), NodeError>;
137
138    /// Compacts internal storage after commands have been applied.
139    fn compact(&self) {}
140}
141
142pub struct ConcreteApplierHost<A: Applier + 'static> {
143    inner: RefCell<A>,
144    disposals: NodeDisposalQueue,
145}
146
147impl<A: Applier + 'static> ConcreteApplierHost<A> {
148    pub fn new(applier: A) -> Self {
149        Self {
150            inner: RefCell::new(applier),
151            disposals: NodeDisposalQueue::default(),
152        }
153    }
154
155    pub fn borrow_typed(&self) -> ApplierGuard<'_, A> {
156        self.guard(self.inner.borrow_mut())
157    }
158
159    pub fn try_borrow_typed(&self) -> Result<ApplierGuard<'_, A>, BorrowMutError> {
160        self.inner.try_borrow_mut().map(|inner| self.guard(inner))
161    }
162
163    fn guard<'a>(&'a self, inner: RefMut<'a, A>) -> ApplierGuard<'a, A> {
164        ApplierGuard {
165            inner,
166            disposals: &self.disposals,
167            dispose: |applier, node| dispose_detached_node_now(applier, node),
168        }
169    }
170
171    /// Returns the applier after all queued nodes have been disposed.
172    ///
173    /// On failure, returns this host with its pending resources and the error,
174    /// allowing the caller to repair the cause and retry extraction.
175    ///
176    /// ```
177    /// use cranpose_core::{ConcreteApplierHost, MemoryApplier};
178    /// let host = ConcreteApplierHost::new(MemoryApplier::new());
179    /// assert!(host.try_into_inner().is_ok());
180    /// ```
181    pub fn try_into_inner(mut self) -> Result<A, (Self, NodeError)> {
182        if let Err(error) = self.disposals.flush(self.inner.get_mut(), |applier, node| {
183            dispose_detached_node_now(applier, node)
184        }) {
185            return Err((self, error));
186        }
187        Ok(self.inner.into_inner())
188    }
189}
190
191impl<A: Applier + 'static> ApplierHost for ConcreteApplierHost<A> {
192    fn borrow_dyn(&self) -> ApplierGuard<'_, dyn Applier> {
193        ApplierGuard {
194            inner: RefMut::map(self.inner.borrow_mut(), |applier| {
195                applier as &mut dyn Applier
196            }),
197            disposals: &self.disposals,
198            dispose: dispose_detached_node_now,
199        }
200    }
201
202    fn dispose_nodes(&self, disposal: NodeDisposal) -> Result<(), NodeError> {
203        self.disposals.enqueue(disposal);
204        if let Ok(mut applier) = self.inner.try_borrow_mut() {
205            return self.disposals.flush(&mut *applier, |applier, node| {
206                dispose_detached_node_now(applier, node)
207            });
208        }
209        Ok(())
210    }
211
212    fn compact(&self) {
213        self.borrow_typed().compact();
214    }
215}
216
217/// A mutable applier borrow that drains recursive node disposal before returning.
218pub struct ApplierGuard<'a, A: Applier + ?Sized + 'static> {
219    inner: RefMut<'a, A>,
220    disposals: &'a NodeDisposalQueue,
221    dispose: fn(&mut A, NodeId) -> Result<(), NodeError>,
222}
223
224impl<A: Applier + ?Sized + 'static> ApplierGuard<'_, A> {
225    fn flush_disposals(&mut self) -> Result<(), NodeError> {
226        self.disposals.flush(&mut *self.inner, self.dispose)
227    }
228}
229
230impl<A: Applier + ?Sized + 'static> Deref for ApplierGuard<'_, A> {
231    type Target = A;
232
233    fn deref(&self) -> &Self::Target {
234        &self.inner
235    }
236}
237
238impl<A: Applier + ?Sized + 'static> DerefMut for ApplierGuard<'_, A> {
239    fn deref_mut(&mut self) -> &mut Self::Target {
240        &mut self.inner
241    }
242}
243
244impl<A: Applier + ?Sized + 'static> Drop for ApplierGuard<'_, A> {
245    fn drop(&mut self) {
246        if self.disposals.pending.get()
247            && let Err(error) = self.flush_disposals()
248        {
249            log::error!("deferred node disposal failed: {error}");
250        }
251    }
252}