inc_complete/storage/macros.rs
1/// Helper macro to define an intermediate computation type.
2/// This will implement `Computation`, and `Run`.
3///
4/// This macro supports multiple `Storage` types used, separated by `|`,
5/// in case your program uses multiple databases with differing storage types.
6///
7/// Signature:
8/// `define_intermediate!(computation_id, ComputationType -> OutputType, StorageType ( | MoreStorageTypes)*, run_function)`
9///
10/// Example usage:
11/// ```
12/// # use inc_complete::{ define_intermediate, define_input, storage::SingletonStorage, impl_storage, DbHandle };
13/// # struct MyStorageType { input: SingletonStorage<MyInput>, double: SingletonStorage<Double>, more: SingletonStorage<More> }
14/// # #[derive(Debug, Clone)]
15/// # struct MyInput;
16/// # define_input!(0, MyInput -> i32, MyStorageType);
17/// # impl_storage!(MyStorageType, input:MyInput, double:Double, more:More,);
18/// ##[derive(Debug, Clone)]
19/// struct Double;
20/// ##[derive(Debug, Clone)]
21/// struct More;
22///
23/// // Define `Double` as a computation with id 1 and the given run function which returns an `i32`
24/// // to be used with a `Db<MyStorageType>` or `DbHandle<MyStorageType>`.
25/// // The type annotations on the closure are unnecessary.
26/// // We also may provide an existing function instead of a closure.
27/// define_intermediate!(1, Double -> i32, MyStorageType, |_: &Double, db: &DbHandle<MyStorageType>| {
28/// db.get(MyInput) * 2
29/// });
30///
31/// // It is also possible to signal that the value always changes with the assume_changed keyword.
32/// // Doing so let's us avoid expensive `Eq` checks on large values which are expected to change whenever their inputs do anyway:
33/// define_intermediate!(2, assume_changed More -> i32, MyStorageType, |_, db: &DbHandle<MyStorageType>| {
34/// db.get(Double) + 1
35/// });
36/// ```
37#[macro_export]
38macro_rules! define_intermediate {
39 ( $id:tt, $type_name:ident -> $output_type:ty, $( $storage_type:ty )|+, $run_function:expr) => {
40 define_intermediate!(@ $id, $type_name -> $output_type, false, $( $storage_type )|+, $run_function);
41 };
42 ( $id:tt, assume_changed $type_name:ident -> $output_type:ty, $( $storage_type:ty )|+, $run_function:expr) => {
43 define_intermediate!(@ $id, $type_name -> $output_type, true, $( $storage_type )|+, $run_function);
44 };
45 (@ $id:tt, $type_name:ident -> $output_type:ty, $assume_changed:expr, $( $storage_type:ty )|+, $run_function:expr) => {
46 impl $crate::Computation for $type_name {
47 type Output = $output_type;
48 const ASSUME_CHANGED: bool = $assume_changed;
49
50 fn computation_id() -> u32 {
51 $id
52 }
53 }
54
55 impl $type_name {
56 #[allow(unused)]
57 pub fn get(self, db: &impl $crate::DbGet<$type_name>) -> $output_type {
58 db.get(self)
59 }
60 }
61
62 $(
63 impl $crate::Run<$storage_type> for $type_name {
64 fn run(&self, db: &$crate::DbHandle<$storage_type>) -> $output_type {
65 // The type annotation here makes it so that users don't have to annotate
66 // the arguments of `run_function`.
67 let f: fn(&Self, &$crate::DbHandle<$storage_type>) -> $output_type =
68 $run_function;
69 f(self, db)
70 }
71 }
72 )+
73 };
74}
75
76/// Helper macro to define an input computation type.
77/// This will implement `Computation` and `Run`.
78/// Note that the `Run` implementation will panic by default with a message that
79/// `update_input` should have been called beforehand.
80///
81/// This macro supports multiple `Storage` types used, separated by `|`,
82/// in case your program uses multiple databases with differing storage types.
83///
84/// Signature:
85/// `define_input!(computation_id, ComputationType -> OutputType, StorageType ( | MoreStorageTypes)* )`
86///
87/// Example usage:
88/// ```
89/// # use inc_complete::{ define_intermediate, define_input, storage::SingletonStorage, impl_storage, DbHandle };
90/// # struct MyStorageType { input: SingletonStorage<MyInput>, double: SingletonStorage<Double> }
91/// # impl_storage!(MyStorageType, input:MyInput,double:Double,);
92/// # #[derive(Debug, Clone)]
93/// # struct Double;
94/// # define_intermediate!(1, Double -> i32, MyStorageType, |_: &Double, db: &DbHandle<MyStorageType>| {
95/// # db.get(MyInput) * 2
96/// # });
97/// ##[derive(Debug, Clone)]
98/// struct MyInput;
99///
100/// // Define `MyInput` as an input computation with id 0 and an `i32` value
101/// // which can be used with a `Db<MyStorageType>`.
102/// define_input!(0, MyInput -> i32, MyStorageType);
103/// ```
104#[macro_export]
105macro_rules! define_input {
106 ( $id:tt, $type_name:ident -> $output_type:ty, $( $storage_type:ty )|+ ) => {
107 define_input!(@ $id, $type_name -> $output_type, false, $( $storage_type )|+);
108 };
109 ( $id:tt, assume_changed $type_name:ident -> $output_type:ty, $( $storage_type:ty )|+ ) => {
110 define_input!(@ $id, $type_name -> $output_type, true, $( $storage_type )|+);
111 };
112 (@ $id:tt, $type_name:ident -> $output_type:ty, $assume_changed:expr, $( $storage_type:ty )|+ ) => {
113 impl $crate::Computation for $type_name {
114 type Output = $output_type;
115 const ASSUME_CHANGED: bool = $assume_changed;
116
117 fn computation_id() -> u32 {
118 $id
119 }
120 }
121
122 impl $type_name {
123 #[allow(unused)]
124 pub fn get(self, db: &impl $crate::DbGet<$type_name>) -> $output_type {
125 db.get(self)
126 }
127
128 #[allow(unused)]
129 pub fn set<S>(self, db: &mut $crate::Db<S>, value: $output_type) where S: $crate::Storage + $crate::StorageFor<$type_name> {
130 db.update_input(self, value);
131 }
132 }
133
134 $(
135 impl $crate::Run<$storage_type> for $type_name {
136 fn run(&self, _: &$crate::DbHandle<$storage_type>) -> $output_type {
137 panic!("Attempted to call `run` function on input {}, did you forget to call `update_input`?",
138 stringify!($type_name))
139 }
140 }
141 )+
142 };
143}
144
145/// Implements `Storage` for a struct type. This enables the given struct type `S` to be used
146/// as a generic on `Db<S>` to store _all_ computations cached by the program.
147///
148/// This will also create forwarding impls for `StorageFor<ComputationType>` for each field,
149/// computation type pair used.
150///
151/// Example usage:
152/// ```
153/// use inc_complete::{ impl_storage, define_input, define_intermediate };
154/// use inc_complete::storage::{ SingletonStorage, HashMapStorage };
155/// use inc_complete::accumulate::{ Accumulator, Accumulate };
156///
157/// ##[derive(Default)]
158/// struct MyStorage {
159/// foos: SingletonStorage<Foo>,
160/// bars: HashMapStorage<Bar>,
161/// logs: Accumulator<Log>,
162/// }
163///
164/// impl_storage!(MyStorage,
165/// foos: Foo,
166/// bars: Bar,
167/// @accumulators {
168/// logs: Log,
169/// }
170/// );
171///
172/// // Each input & intermediate computation should implement Debug & Clone
173/// ##[derive(Debug, Clone)]
174/// struct Foo;
175/// define_input!(0, Foo -> usize, MyStorage);
176///
177/// // HashMapStorage requires Eq and Hash
178/// ##[derive(Debug, Clone, PartialEq, Eq, Hash)]
179/// struct Bar(std::rc::Rc<String>);
180/// define_intermediate!(1, Bar -> usize, MyStorage, |bar, db| {
181/// bar.0.len() + db.get(Foo)
182/// });
183///
184/// // Accumulated values need to derive Eq, Clone, and Debug
185/// #[derive(Debug, PartialEq, Eq, Clone)]
186/// struct Log;
187/// ```
188///
189/// Note that using this macro requires each computation type to implement `Clone`.
190#[macro_export]
191macro_rules! impl_storage {
192 ($typ:ty, $( $field:ident : $computation_type:ty, )* $(@accumulators { $($acc_field:ident : $acc_type:ty, )* })? ) => {
193 impl $crate::Storage for $typ {
194 fn output_is_unset(&self, cell: $crate::Cell, computation_id: u32) -> bool {
195 use $crate::StorageFor;
196 match computation_id {
197 $(
198 x if x == <$computation_type as $crate::Computation>::computation_id() => {
199 self.$field.get_output(cell).is_none()
200 },
201 )*
202 id => panic!("Unknown computation id: {id}"),
203 }
204 }
205 $crate::run_computation!( $($field: $computation_type),* );
206
207 fn gc(&mut self, used_cells: &std::collections::HashSet<$crate::Cell>) {
208 use $crate::StorageFor;
209 $(
210 self.$field.gc(&used_cells);
211 )*
212 }
213
214 fn input_debug_string(&self, db: &$crate::Db<Self>, cell: $crate::Cell) -> String {
215 use $crate::StorageFor;
216 $(
217 if let Some(input) = self.$field.try_get_input(cell) {
218 return format!("{:?}", $crate::debug_with_db(&input, db));
219 }
220 )*
221
222 panic!("inc-complete internal error: input_debug_string: {cell:?} not found")
223 }
224
225 fn clear_accumulated_for_cell(&self, _cell: $crate::Cell) {
226 $( $(
227 self.$acc_field.clear(_cell);
228 )* )?
229 }
230 }
231
232 $(
233 impl $crate::StorageFor<$computation_type> for $typ {
234 fn get_cell_for_computation(&self, key: &$computation_type) -> Option<$crate::Cell> {
235 self.$field.get_cell_for_computation(key)
236 }
237
238 fn insert_new_cell(&self, cell: $crate::Cell, key: $computation_type) {
239 self.$field.insert_new_cell(cell, key)
240 }
241
242 fn try_get_input(&self, cell: $crate::Cell) -> Option<$computation_type> {
243 self.$field.try_get_input(cell)
244 }
245
246 fn get_output(&self, cell: $crate::Cell) -> Option<<$computation_type as $crate::Computation>::Output> {
247 self.$field.get_output(cell)
248 }
249
250 fn update_output(&self, cell: $crate::Cell, new_value: <$computation_type as $crate::Computation>::Output) -> bool {
251 self.$field.update_output(cell, new_value)
252 }
253
254 fn gc(&mut self, used_cells: &std::collections::HashSet<$crate::Cell>) {
255 self.$field.gc(used_cells);
256 }
257 })*
258
259 $($(
260 impl $crate::accumulate::Accumulate<$acc_type> for $typ {
261 fn accumulate(&self, cell: $crate::Cell, item: $acc_type) {
262 self.$acc_field.accumulate(cell, item)
263 }
264
265 fn get_accumulated<Items>(&self, cell: $crate::Cell) -> Items
266 where Items: FromIterator<$acc_type>
267 {
268 self.$acc_field.get_accumulated(cell)
269 }
270 }
271 )*)?
272 };
273}
274
275#[doc(hidden)]
276#[macro_export]
277macro_rules! run_computation {
278 ( $($field:ident: $computation_type:ty),* ) => {
279 fn run_computation(db: &$crate::DbHandle<Self>, cell: $crate::Cell, computation_id: u32) -> bool {
280 use $crate::{ StorageFor, Run };
281 match computation_id {
282 $(
283 x if x == <$computation_type as $crate::Computation>::computation_id() => {
284 let new_value = db.storage().$field.get_input(cell).run(db);
285 db.storage().$field.update_output(cell, new_value)
286 }
287 )*
288 id => panic!("Unknown computation id: {id}"),
289 }
290 }
291 }
292}