use std::sync::{Arc, Mutex};
type Cb<T, E> = Box<Fn(&Result<T, E>) -> () + Send + Sync>;
type RunCb<T, E> = Box<Fn(&Thunky<T, E>) -> () + Send + Sync>;
pub struct Thunky<T, E> {
run: RunCb<T, E>,
state: Mutex<Option<Box<State<T, E> + Send + Sync>>>,
stack: Mutex<Vec<Cb<T, E>>>,
cache: Mutex<Option<Result<T, E>>>
}
impl<T, E> Thunky<T, E> {
pub fn new (run: RunCb<T, E>) -> Arc<Thunky<T, E>> {
Arc::new(Thunky {
run,
state: Mutex::new(Some(Box::new(Run {}))),
stack: Mutex::new(Vec::new()),
cache: Mutex::new(None)
})
}
pub fn cache(&self, a: Result<T, E>) -> () {
while self.stack.lock().unwrap().len() > 0 {
let cb = self.stack.lock().unwrap().pop().unwrap();
cb(&a);
}
#[allow(unused_assignments)]
let mut is_cached = false;
match self.cache.lock().unwrap().as_ref() {
Some(v) => {
if v.is_ok() {
is_cached = true
} else {
is_cached = false
}
},
None => {
is_cached = false
}
}
if !is_cached {
*self.cache.lock().unwrap() = Some(a);
}
}
pub fn run(&self, callback: Cb<T, E>) -> () {
let state = self.state.lock().unwrap().take().unwrap();
state.run(self, callback)
}
}
trait State<T, E> {
fn run(&self, thunky: &Thunky<T, E>, callback: Cb<T, E>) -> ();
}
struct Run {}
impl<T, E> State<T, E> for Run {
fn run (&self, thunky: &Thunky<T, E>, callback: Cb<T, E>) -> () {
thunky.stack.lock().unwrap().push(callback);
(thunky.run)(thunky);
match thunky.cache.lock().unwrap().as_ref() {
Some(cache) => {
if cache.is_ok() {
*thunky.state.lock().unwrap() = Some(Box::new(Finish {}));
} else if cache.is_err() {
*thunky.state.lock().unwrap() = Some(Box::new(Run {}));
}
},
None => {
*thunky.state.lock().unwrap() = Some(Box::new(Wait {}));
}
}
}
}
struct Wait {}
impl<T, E> State<T, E> for Wait {
fn run (&self, thunky: &Thunky<T, E>, callback: Cb<T, E>) -> () {
thunky.stack.lock().unwrap().push(callback);
*thunky.state.lock().unwrap() = Some(Box::new(Wait {}));
}
}
struct Finish {}
impl<T, E> State<T, E> for Finish {
fn run (&self, thunky: &Thunky<T, E>, callback: Cb<T, E>) -> () {
while thunky.stack.lock().unwrap().len() > 0 {
let cb = thunky.stack.lock().unwrap().pop().unwrap();
cb(thunky.cache.lock().unwrap().as_ref().unwrap());
}
callback(thunky.cache.lock().unwrap().as_ref().unwrap());
*thunky.state.lock().unwrap() = Some(Box::new(Finish {}));
}
}