use std::any::Any;
use std::fmt;
use std::sync::RwLock;
use futures::future::BoxFuture;
use super::AsyncSetQuery;
use super::BoxVertexStream;
use super::Hints;
use super::Set;
use crate::Result;
use crate::Vertex;
pub struct MetaSet {
evaluate: Box<dyn Fn() -> BoxFuture<'static, Result<Set>> + Send + Sync>,
evaluated: RwLock<Option<Set>>,
contains: Option<
Box<dyn for<'a> Fn(&'a MetaSet, &'a Vertex) -> BoxFuture<'a, Result<bool>> + Send + Sync>,
>,
hints: Hints,
}
impl fmt::Debug for MetaSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("<meta ")?;
if let Some(set) = self.evaluated() {
set.fmt(f)?;
} else {
f.write_str("?")?;
}
f.write_str(">")?;
Ok(())
}
}
impl MetaSet {
pub fn from_evaluate_hints(
evaluate: Box<dyn Fn() -> BoxFuture<'static, Result<Set>> + Send + Sync + 'static>,
hints: Hints,
) -> Self {
Self {
evaluate,
evaluated: Default::default(),
contains: None,
hints,
}
}
pub fn with_contains(
mut self,
contains: Box<
dyn for<'a> Fn(&'a MetaSet, &'a Vertex) -> BoxFuture<'a, Result<bool>> + Send + Sync,
>,
) -> Self {
self.contains = Some(contains);
self
}
pub async fn evaluate(&self) -> Result<Set> {
if let Some(s) = &*self.evaluated.read().unwrap() {
return Ok(s.clone());
}
let s = (self.evaluate)().await?;
*self.evaluated.write().unwrap() = Some(s.clone());
Ok(s)
}
pub fn evaluated(&self) -> Option<Set> {
self.evaluated.read().unwrap().clone()
}
}
#[async_trait::async_trait]
impl AsyncSetQuery for MetaSet {
async fn iter(&self) -> Result<BoxVertexStream> {
self.evaluate().await?.iter().await
}
async fn iter_rev(&self) -> Result<BoxVertexStream> {
self.evaluate().await?.iter_rev().await
}
async fn count_slow(&self) -> Result<u64> {
self.evaluate().await?.count_slow().await
}
async fn size_hint(&self) -> (u64, Option<u64>) {
match self.evaluated() {
Some(set) => set.size_hint().await,
None => (0, None),
}
}
async fn last(&self) -> Result<Option<Vertex>> {
self.evaluate().await?.last().await
}
async fn contains(&self, name: &Vertex) -> Result<bool> {
match self.evaluated() {
Some(set) => set.contains(name).await,
None => match &self.contains {
Some(f) => f(self, name).await,
None => self.evaluate().await?.contains(name).await,
},
}
}
async fn contains_fast(&self, name: &Vertex) -> Result<Option<bool>> {
match &self.contains {
Some(f) => Ok(Some(f(self, name).await?)),
None => Ok(None),
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn hints(&self) -> &Hints {
&self.hints
}
}
#[cfg(test)]
mod tests {
use nonblocking::non_blocking_result as r;
use super::super::tests::*;
use super::*;
fn meta_set(v: &[impl ToString]) -> MetaSet {
let v: Vec<_> = v.iter().map(|s| s.to_string()).collect();
let f = move || -> BoxFuture<_> {
let s = Set::from_static_names(v.clone().into_iter().map(Into::into));
Box::pin(async move { Ok(s) })
};
MetaSet::from_evaluate_hints(Box::new(f), Hints::default())
}
#[test]
fn test_meta_basic() -> Result<()> {
let set = meta_set(&["1", "3", "2", "7", "5"]);
assert!(set.evaluated().is_none());
assert!(nb(set.contains(&"2".into()))?);
assert!(set.evaluated().is_some());
check_invariants(&set)?;
Ok(())
}
#[test]
fn test_meta_contains() -> Result<()> {
let set = meta_set(&["1", "3", "2", "7", "5"]).with_contains(Box::new(|_, v| {
let r = Ok(v.as_ref().len() == 1 && b"12357".contains(&v.as_ref()[0]));
Box::pin(async move { r })
}));
assert!(nb(set.contains(&"2".into()))?);
assert!(!nb(set.contains(&"6".into()))?);
assert!(set.evaluated().is_none());
check_invariants(&set)?;
Ok(())
}
#[test]
fn test_size_hint() -> Result<()> {
let set = meta_set(&["1", "2"]);
assert!(set.evaluated().is_none());
assert_eq!(nb(set.size_hint()), (0, None));
assert!(nb(set.contains(&"2".into()))?);
assert_eq!(nb(set.size_hint()), (2, Some(2)));
Ok(())
}
#[test]
fn test_debug() {
let set = meta_set(&["1", "3", "2", "7", "5"]);
assert_eq!(dbg(&set), "<meta ?>");
r(set.evaluate()).unwrap();
assert_eq!(
format!("{:5?}", &set),
"<meta <static [31, 33, 32, 37, 35]>>"
);
}
quickcheck::quickcheck! {
fn test_meta_quickcheck(v: Vec<String>) -> bool {
let set = meta_set(&v);
check_invariants(&set).unwrap();
true
}
}
}