1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

/// A macro for defining functions whose return values will wrapped in a `Cache`.
///
/// # Example
/// ```
/// use engine::cache;
///
/// cache! {
///    fn fib(n: u32) -> u32 => {
///      match n {
///         0 => 1,
///         1 => 1,
///         _ => fib(n - 1) + fib(n - 2),
///      }
///    }
/// }
///
/// assert_eq!(fib(20), 10946);
/// assert_eq!(FIB_CACHE.lock().unwrap().get(&20), Some(&10946));
/// ```
#[macro_export]
macro_rules! cache {
    (fn $name:ident ($($arg:ident: $arg_type:ty), *) -> $ret:ty => $body:expr) => {
        use once_cell::sync::Lazy;
        use std::sync::Mutex;
        use engine::store::Cache;
        use paste::paste;

        paste! {
            // create a static instance of `Cache<K, V>` for the expression called `$EXPR_NAME_CACHE`.
            static [<$name:upper _CACHE>]: Lazy<Mutex<Cache<($($arg_type),*), $ret>>> = Lazy::new(|| Mutex::new(Cache::new()));

            #[allow(unused_parens)]
            fn $name($($arg: $arg_type), *) -> $ret {
                // create a static instance of `Cache<K, V>`
                // static CACHE: Lazy<Mutex<Cache<($($arg_type),*), $ret>>> =
                //     Lazy::new(|| Mutex::new(Cache::new()));

                // create key out of arg.
                let key = ($($arg.clone()), *);

                // get mutex to check for cached value.
                let cache = [<$name:upper _CACHE>].lock().unwrap();

                // check for cached value.
                match cache.get(&key) {
                    // if value is cached, return it.
                    Some(val) => val.clone(),
                    None => {
                        // drop the mutex before execution to avoid a deadlock.
                        drop(cache);
                        // execute the body of the function/expression.
                        let value = (||$body)();

                        // re-get mutex to add/update the cache.
                        let mut cache = [<$name:upper _CACHE>].lock().unwrap();
                        cache.insert(key, value, None);
                        value.clone()
                    }
                }
            }
        }
    };
}