Skip to main content

dusk_wasmtime/runtime/component/
resource_table.rs

1use super::Resource;
2use std::any::Any;
3use std::collections::{BTreeSet, HashMap};
4
5#[derive(Debug)]
6/// Errors returned by operations on `ResourceTable`
7pub enum ResourceTableError {
8    /// ResourceTable has no free keys
9    Full,
10    /// Resource not present in table
11    NotPresent,
12    /// Resource present in table, but with a different type
13    WrongType,
14    /// Resource cannot be deleted because child resources exist in the table. Consult wit docs for
15    /// the particular resource to see which methods may return child resources.
16    HasChildren,
17}
18
19impl std::fmt::Display for ResourceTableError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            Self::Full => write!(f, "resource table has no free keys"),
23            Self::NotPresent => write!(f, "resource not present"),
24            Self::WrongType => write!(f, "resource is of another type"),
25            Self::HasChildren => write!(f, "resource has children"),
26        }
27    }
28}
29impl std::error::Error for ResourceTableError {}
30
31/// The `ResourceTable` type maps a `Resource<T>` to its `T`.
32#[derive(Debug)]
33pub struct ResourceTable {
34    entries: Vec<Entry>,
35    free_head: Option<usize>,
36}
37
38#[derive(Debug)]
39enum Entry {
40    Free { next: Option<usize> },
41    Occupied { entry: TableEntry },
42}
43
44impl Entry {
45    pub fn occupied(&self) -> Option<&TableEntry> {
46        match self {
47            Self::Occupied { entry } => Some(entry),
48            Self::Free { .. } => None,
49        }
50    }
51
52    pub fn occupied_mut(&mut self) -> Option<&mut TableEntry> {
53        match self {
54            Self::Occupied { entry } => Some(entry),
55            Self::Free { .. } => None,
56        }
57    }
58}
59
60/// This structure tracks parent and child relationships for a given table entry.
61///
62/// Parents and children are referred to by table index. We maintain the
63/// following invariants to prevent orphans and cycles:
64/// * parent can only be assigned on creating the entry.
65/// * parent, if some, must exist when creating the entry.
66/// * whenever a child is created, its index is added to children.
67/// * whenever a child is deleted, its index is removed from children.
68/// * an entry with children may not be deleted.
69#[derive(Debug)]
70struct TableEntry {
71    /// The entry in the table, as a boxed dynamically-typed object
72    entry: Box<dyn Any + Send>,
73    /// The index of the parent of this entry, if it has one.
74    parent: Option<u32>,
75    /// The indicies of any children of this entry.
76    children: BTreeSet<u32>,
77}
78
79impl TableEntry {
80    fn new(entry: Box<dyn Any + Send>, parent: Option<u32>) -> Self {
81        Self {
82            entry,
83            parent,
84            children: BTreeSet::new(),
85        }
86    }
87    fn add_child(&mut self, child: u32) {
88        debug_assert!(!self.children.contains(&child));
89        self.children.insert(child);
90    }
91    fn remove_child(&mut self, child: u32) {
92        let was_removed = self.children.remove(&child);
93        debug_assert!(was_removed);
94    }
95}
96
97impl ResourceTable {
98    /// Create an empty table
99    pub fn new() -> Self {
100        ResourceTable {
101            entries: Vec::new(),
102            free_head: None,
103        }
104    }
105
106    /// Create an empty table with at least the specified capacity.
107    pub fn with_capacity(capacity: usize) -> Self {
108        ResourceTable {
109            entries: Vec::with_capacity(capacity),
110            free_head: None,
111        }
112    }
113
114    /// Inserts a new value `T` into this table, returning a corresponding
115    /// `Resource<T>` which can be used to refer to it after it was inserted.
116    pub fn push<T>(&mut self, entry: T) -> Result<Resource<T>, ResourceTableError>
117    where
118        T: Send + 'static,
119    {
120        let idx = self.push_(TableEntry::new(Box::new(entry), None))?;
121        Ok(Resource::new_own(idx))
122    }
123
124    /// Pop an index off of the free list, if it's not empty.
125    fn pop_free_list(&mut self) -> Option<usize> {
126        if let Some(ix) = self.free_head {
127            // Advance free_head to the next entry if one is available.
128            match &self.entries[ix] {
129                Entry::Free { next } => self.free_head = *next,
130                Entry::Occupied { .. } => unreachable!(),
131            }
132            Some(ix)
133        } else {
134            None
135        }
136    }
137
138    /// Free an entry in the table, returning its [`TableEntry`]. Add the index to the free list.
139    fn free_entry(&mut self, ix: usize) -> TableEntry {
140        let entry = match std::mem::replace(
141            &mut self.entries[ix],
142            Entry::Free {
143                next: self.free_head,
144            },
145        ) {
146            Entry::Occupied { entry } => entry,
147            Entry::Free { .. } => unreachable!(),
148        };
149
150        self.free_head = Some(ix);
151
152        entry
153    }
154
155    /// Push a new entry into the table, returning its handle. This will prefer to use free entries
156    /// if they exist, falling back on pushing new entries onto the end of the table.
157    fn push_(&mut self, e: TableEntry) -> Result<u32, ResourceTableError> {
158        if let Some(free) = self.pop_free_list() {
159            self.entries[free] = Entry::Occupied { entry: e };
160            Ok(free as u32)
161        } else {
162            let ix = self
163                .entries
164                .len()
165                .try_into()
166                .map_err(|_| ResourceTableError::Full)?;
167            self.entries.push(Entry::Occupied { entry: e });
168            Ok(ix)
169        }
170    }
171
172    fn occupied(&self, key: u32) -> Result<&TableEntry, ResourceTableError> {
173        self.entries
174            .get(key as usize)
175            .and_then(Entry::occupied)
176            .ok_or(ResourceTableError::NotPresent)
177    }
178
179    fn occupied_mut(&mut self, key: u32) -> Result<&mut TableEntry, ResourceTableError> {
180        self.entries
181            .get_mut(key as usize)
182            .and_then(Entry::occupied_mut)
183            .ok_or(ResourceTableError::NotPresent)
184    }
185
186    /// Insert a resource at the next available index, and track that it has a
187    /// parent resource.
188    ///
189    /// The parent must exist to create a child. All children resources must
190    /// be destroyed before a parent can be destroyed - otherwise
191    /// [`ResourceTable::delete`] will fail with
192    /// [`ResourceTableError::HasChildren`].
193    ///
194    /// Parent-child relationships are tracked inside the table to ensure that
195    /// a parent resource is not deleted while it has live children. This
196    /// allows child resources to hold "references" to a parent by table
197    /// index, to avoid needing e.g. an `Arc<Mutex<parent>>` and the associated
198    /// locking overhead and design issues, such as child existence extending
199    /// lifetime of parent referent even after parent resource is destroyed,
200    /// possibility for deadlocks.
201    ///
202    /// Parent-child relationships may not be modified once created. There
203    /// is no way to observe these relationships through the [`ResourceTable`]
204    /// methods except for erroring on deletion, or the [`std::fmt::Debug`]
205    /// impl.
206    pub fn push_child<T, U>(
207        &mut self,
208        entry: T,
209        parent: &Resource<U>,
210    ) -> Result<Resource<T>, ResourceTableError>
211    where
212        T: Send + 'static,
213        U: 'static,
214    {
215        let parent = parent.rep();
216        self.occupied(parent)?;
217        let child = self.push_(TableEntry::new(Box::new(entry), Some(parent)))?;
218        self.occupied_mut(parent)?.add_child(child);
219        Ok(Resource::new_own(child))
220    }
221
222    /// Get an immutable reference to a resource of a given type at a given
223    /// index.
224    ///
225    /// Multiple shared references can be borrowed at any given time.
226    pub fn get<T: Any + Sized>(&self, key: &Resource<T>) -> Result<&T, ResourceTableError> {
227        self.get_(key.rep())?
228            .downcast_ref()
229            .ok_or(ResourceTableError::WrongType)
230    }
231
232    fn get_(&self, key: u32) -> Result<&dyn Any, ResourceTableError> {
233        let r = self.occupied(key)?;
234        Ok(&*r.entry)
235    }
236
237    /// Get an mutable reference to a resource of a given type at a given
238    /// index.
239    pub fn get_mut<T: Any + Sized>(
240        &mut self,
241        key: &Resource<T>,
242    ) -> Result<&mut T, ResourceTableError> {
243        self.get_any_mut(key.rep())?
244            .downcast_mut()
245            .ok_or(ResourceTableError::WrongType)
246    }
247
248    /// Returns the raw `Any` at the `key` index provided.
249    pub fn get_any_mut(&mut self, key: u32) -> Result<&mut dyn Any, ResourceTableError> {
250        let r = self.occupied_mut(key)?;
251        Ok(&mut *r.entry)
252    }
253
254    /// Same as `delete`, but typed
255    pub fn delete<T>(&mut self, resource: Resource<T>) -> Result<T, ResourceTableError>
256    where
257        T: Any,
258    {
259        debug_assert!(resource.owned());
260        let entry = self.delete_entry(resource.rep())?;
261        match entry.entry.downcast() {
262            Ok(t) => Ok(*t),
263            Err(_e) => Err(ResourceTableError::WrongType),
264        }
265    }
266
267    fn delete_entry(&mut self, key: u32) -> Result<TableEntry, ResourceTableError> {
268        if !self.occupied(key)?.children.is_empty() {
269            return Err(ResourceTableError::HasChildren);
270        }
271        let e = self.free_entry(key as usize);
272        if let Some(parent) = e.parent {
273            // Remove deleted resource from parent's child list.
274            // Parent must still be present because it cant be deleted while still having
275            // children:
276            self.occupied_mut(parent)
277                .expect("missing parent")
278                .remove_child(key);
279        }
280        Ok(e)
281    }
282
283    /// Zip the values of the map with mutable references to table entries corresponding to each
284    /// key. As the keys in the [HashMap] are unique, this iterator can give mutable references
285    /// with the same lifetime as the mutable reference to the [ResourceTable].
286    pub fn iter_entries<'a, T>(
287        &'a mut self,
288        map: HashMap<u32, T>,
289    ) -> impl Iterator<Item = (Result<&'a mut dyn Any, ResourceTableError>, T)> {
290        map.into_iter().map(move |(k, v)| {
291            let item = self
292                .occupied_mut(k)
293                .map(|e| Box::as_mut(&mut e.entry))
294                // Safety: extending the lifetime of the mutable reference.
295                .map(|item| unsafe { &mut *(item as *mut dyn Any) });
296            (item, v)
297        })
298    }
299
300    /// Iterate over all children belonging to the provided parent
301    pub fn iter_children<T>(
302        &self,
303        parent: &Resource<T>,
304    ) -> Result<impl Iterator<Item = &(dyn Any + Send)>, ResourceTableError>
305    where
306        T: 'static,
307    {
308        let parent_entry = self.occupied(parent.rep())?;
309        Ok(parent_entry.children.iter().map(|child_index| {
310            let child = self.occupied(*child_index).expect("missing child");
311            child.entry.as_ref()
312        }))
313    }
314}
315
316impl Default for ResourceTable {
317    fn default() -> Self {
318        ResourceTable::new()
319    }
320}
321
322#[test]
323pub fn test_free_list() {
324    let mut table = ResourceTable::new();
325
326    let x = table.push(()).unwrap();
327    assert_eq!(x.rep(), 0);
328
329    let y = table.push(()).unwrap();
330    assert_eq!(y.rep(), 1);
331
332    // Deleting x should put it on the free list, so the next entry should have the same rep.
333    table.delete(x).unwrap();
334    let x = table.push(()).unwrap();
335    assert_eq!(x.rep(), 0);
336
337    // Deleting x and then y should yield indices 1 and then 0 for new entries.
338    table.delete(x).unwrap();
339    table.delete(y).unwrap();
340
341    let y = table.push(()).unwrap();
342    assert_eq!(y.rep(), 1);
343
344    let x = table.push(()).unwrap();
345    assert_eq!(x.rep(), 0);
346
347    // As the free list is empty, this entry will have a new id.
348    let x = table.push(()).unwrap();
349    assert_eq!(x.rep(), 2);
350}