fj_core/storage/store.rs
1//! Append-only object storage
2//!
3//! So, why a custom data structure? Well, for two reasons:
4//!
5//! 1. No limitations on performance.
6//! 2. Best possible convenience.
7//!
8//! Please note that I'm deliberately saying "no limitations" on performance. So
9//! far, performance has not been a priority, so this might not be that fast.
10//! But by having a custom data structure, we should be able to make performance
11//! as good as we need it, within the limits of the practical.
12//!
13//! The second point, best possible convenience, is already realized.
14//! [`Handle`]s can be owned, cloned, and dereference to the object they are
15//! referencing. This is made possible by the append-only nature of our object
16//! storage, and our immutable objects.
17//!
18//! There are other append-only data structures on `crates.io`. Some of them
19//! look interesting, but none of them quite fit our needs and possibilities, so
20//! a custom development seemed justified.
21//!
22//! But in any case, this was fun to write, and not that much work.
23
24use std::{marker::PhantomData, sync::Arc};
25
26use parking_lot::RwLock;
27
28use super::{
29 blocks::{Blocks, Index},
30 Handle,
31};
32
33/// Append-only object storage
34#[derive(Debug)]
35pub struct Store<T> {
36 inner: StoreInner<T>,
37}
38
39impl<T> Store<T> {
40 /// Construct a new instance of `Store`
41 ///
42 /// Equivalent to calling [`Store::with_block_size`] with a default block
43 /// size.
44 pub fn new() -> Self {
45 Self::with_block_size(16384)
46 }
47
48 /// Construct a new instance of `Store` using the provided block size
49 pub fn with_block_size(block_size: usize) -> Self {
50 let inner = Arc::new(RwLock::new(StoreInnerInner {
51 blocks: Blocks::new(block_size),
52 }));
53
54 Self { inner }
55 }
56
57 /// Reserve a slot for an object in the store
58 ///
59 /// This method returns a [`Handle`] that references the reserved slot. That
60 /// `Handle` does not refer to an object yet! Attempting to dereference the
61 /// `Handle` before it has been used to insert an object into the store will
62 /// result in a panic.
63 ///
64 /// Usually, you'd acquire a `Handle`, then immediately use it to insert an
65 /// object using [`Store::insert`]. The methods are separate to support more
66 /// advanced use cases, like inserting objects that reference each other.
67 pub fn reserve(&self) -> Handle<T> {
68 let mut inner = self.inner.write();
69
70 let (index, ptr) = inner.blocks.reserve();
71
72 Handle {
73 store: self.inner.clone(),
74 index,
75 ptr,
76 }
77 }
78
79 /// Insert an object into the store
80 ///
81 /// # Panics
82 ///
83 /// Panics, if the passed `Handle` does not refer to a reserved slot. This
84 /// can only be the case, if the handle has been used to insert an object
85 /// before.
86 pub fn insert(&mut self, handle: Handle<T>, object: T) {
87 let mut inner = self.inner.write();
88 inner.blocks.insert(handle.index, object);
89 }
90
91 /// Iterate over all objects in this store
92 pub fn iter(&self) -> Iter<T> {
93 Iter {
94 store: self.inner.clone(),
95 next_index: Index::zero(),
96 _a: PhantomData,
97 }
98 }
99}
100
101impl<T> Default for Store<T> {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl<'a, T> IntoIterator for &'a Store<T> {
108 type Item = Handle<T>;
109 type IntoIter = Iter<'a, T>;
110
111 fn into_iter(self) -> Self::IntoIter {
112 self.iter()
113 }
114}
115
116/// An iterator over objects in a [`Store`]
117pub struct Iter<'a, T> {
118 store: StoreInner<T>,
119 next_index: Index,
120 _a: PhantomData<&'a ()>,
121}
122
123impl<'a, T: 'a> Iterator for Iter<'a, T> {
124 type Item = Handle<T>;
125
126 fn next(&mut self) -> Option<Self::Item> {
127 let inner = self.store.read();
128
129 loop {
130 let index = self.next_index;
131 let ptr = inner.blocks.get_and_inc(&mut self.next_index)?;
132
133 if ptr.is_none() {
134 // This is a reserved slot.
135 continue;
136 }
137
138 return Some(Handle {
139 store: self.store.clone(),
140 index,
141 ptr,
142 });
143 }
144 }
145}
146
147pub type StoreInner<T> = Arc<RwLock<StoreInnerInner<T>>>;
148
149#[derive(Debug)]
150pub struct StoreInnerInner<T> {
151 blocks: Blocks<T>,
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::storage::Handle;
157
158 use super::Store;
159
160 #[test]
161 fn insert_and_handle() {
162 let mut store = Store::with_block_size(1);
163
164 let handle: Handle<i32> = store.reserve();
165 let object = 0;
166
167 store.insert(handle.clone(), object);
168
169 assert_eq!(*handle, object);
170 }
171
172 #[test]
173 fn insert_and_iter() {
174 let mut store = Store::with_block_size(1);
175
176 let a: Handle<i32> = store.reserve();
177 let b = store.reserve();
178 store.insert(a.clone(), 0);
179 store.insert(b.clone(), 1);
180
181 let objects = store.iter().collect::<Vec<_>>();
182 assert_eq!(objects, [a, b]);
183 }
184}