Skip to main content

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