use crate::{Completable, DynComputable, Incomplete};
use cancel_this::Cancellable;
pub trait Computable<T> {
fn try_compute(&mut self) -> Completable<T>;
fn compute_completable(&mut self) -> Completable<T> {
loop {
match self.try_compute() {
Ok(value) => return Ok(value),
Err(Incomplete::Suspended) => continue,
Err(e) => return Err(e),
}
}
}
fn compute(&mut self) -> Cancellable<T> {
match self.compute_completable() {
Ok(value) => Ok(value),
Err(Incomplete::Suspended) => unreachable!(
"`compute_completable` never returns `Incomplete::Suspended` by definition."
),
Err(Incomplete::Cancelled(c)) => Err(c),
Err(Incomplete::Exhausted) => panic!("Called `compute` on an exhausted `Computable`."),
}
}
fn dyn_computable(self) -> DynComputable<T>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ComputableResult<T, C: Computable<T>> {
computable: C,
result: Option<T>,
}
impl<T, C: Computable<T>> From<C> for ComputableResult<T, C> {
fn from(value: C) -> Self {
ComputableResult {
computable: value,
result: None,
}
}
}
impl<T, C: Computable<T>> ComputableResult<T, C> {
pub fn new(computable: C) -> Self {
computable.into()
}
pub fn try_compute(&mut self) -> Completable<&T> {
if self.result.is_none() {
let result = self.computable.try_compute()?;
self.result = Some(result);
}
if let Some(result) = self.result.as_ref() {
return Ok(result);
}
unreachable!("Both `result` and `computable` cannot be `None`.")
}
pub fn result_ref(&self) -> Option<&T> {
self.result.as_ref()
}
pub fn result(self) -> Option<T> {
self.result
}
pub fn computable_ref(&self) -> &C {
&self.computable
}
pub fn computable(self) -> C {
self.computable
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ComputableIdentity, Incomplete};
#[test]
fn test_computable_result_from() {
let identity: ComputableIdentity<i32> = 42.into();
let result: ComputableResult<i32, ComputableIdentity<i32>> = identity.into();
assert!(result.result_ref().is_none());
}
#[test]
fn test_computable_result_new() {
let identity: ComputableIdentity<i32> = 100.into();
let mut result = ComputableResult::new(identity);
assert!(result.result_ref().is_none());
let computed = result.try_compute().unwrap();
assert_eq!(*computed, 100);
assert_eq!(result.result_ref(), Some(&100));
}
#[test]
fn test_computable_result_try_compute_multiple_times() {
let identity: ComputableIdentity<String> = "test".to_string().into();
let mut result = ComputableResult::new(identity);
let first = result.try_compute().unwrap();
assert_eq!(*first, "test");
let first_ptr = first as *const String;
let second = result.try_compute().unwrap();
assert_eq!(*second, "test");
let second_ptr = second as *const String;
assert_eq!(first_ptr, second_ptr);
}
#[test]
fn test_computable_result_result() {
let identity: ComputableIdentity<i32> = 42.into();
let mut result = ComputableResult::new(identity);
let _ = result.try_compute().unwrap();
let value = result.result();
assert_eq!(value, Some(42));
}
#[test]
fn test_computable_result_result_none() {
let identity: ComputableIdentity<i32> = 42.into();
let result = ComputableResult::new(identity);
let value = result.result();
assert_eq!(value, None);
}
#[test]
fn test_computable_result_computable_ref() {
let identity: ComputableIdentity<i32> = 42.into();
let result = ComputableResult::new(identity);
let _computable_ref = result.computable_ref();
}
#[test]
fn test_computable_result_computable() {
let identity: ComputableIdentity<i32> = 42.into();
let result = ComputableResult::new(identity);
let mut computable = result.computable();
let value = computable.try_compute().unwrap();
assert_eq!(value, 42);
}
#[test]
fn test_dyn_computable() {
let identity: ComputableIdentity<i32> = 42.into();
let mut dyn_computable = identity.dyn_computable();
let result = dyn_computable.try_compute().unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_compute_method() {
let mut identity: ComputableIdentity<i32> = 42.into();
let result = identity.compute().unwrap();
assert_eq!(result, 42);
}
struct SuspendingComputable {
count: u32,
target: u32,
}
impl Computable<u32> for SuspendingComputable {
fn try_compute(&mut self) -> Completable<u32> {
self.count += 1;
if self.count < self.target {
Err(Incomplete::Suspended)
} else {
Ok(self.count)
}
}
}
#[test]
fn test_compute_with_suspensions() {
let mut computable = SuspendingComputable {
count: 0,
target: 3,
};
let result = computable.compute().unwrap();
assert_eq!(result, 3);
}
#[test]
fn test_try_compute_with_suspensions() {
let mut computable = SuspendingComputable {
count: 0,
target: 3,
};
assert_eq!(computable.try_compute(), Err(Incomplete::Suspended));
assert_eq!(computable.try_compute(), Err(Incomplete::Suspended));
assert_eq!(computable.try_compute(), Ok(3));
}
}