use super::core::{RegularSetupCore, SetupCore}; use super::runnable::Runnable; use super::util::{AddResult, AddableResource, InternalAddResult}; use super::SymbolRunner; use crate::async_utils::sleep; use crate::loggers::{Logger, StoringLogger}; use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource}; use crate::{DefaultBuilder, DefaultLocator}; use futures_util::future::{FutureExt, Shared}; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::error::Error; use std::future::Future; use std::hash::Hash; use std::pin::Pin; use std::rc::Rc; use std::time::Duration; type Cache = HashMap>>>>; #[derive(Debug)] struct SetupInner { core: CORE, resources: RefCell>, } #[derive(Debug)] pub struct Setup(Rc, Rs, As>>); impl Setup { fn borrow_resources(&self) -> RefMut<'_, Cache> { self.0.resources.borrow_mut() } pub async fn add(&self, resource: R, force_run: bool) -> InternalAddResult where Rs: FromResource, As: FromArtifact + Clone, R::Artifact: Clone, RegularSetupCore: SetupCore, { let (storable_resource, weak_resource) = Rs::from_resource(resource); let mut resources = self.borrow_resources(); let result = if let Some(future) = resources.remove(&storable_resource) { assert!( !force_run, "Forcing to run an already-added resource is a logical error" ); resources.insert(storable_resource, future.clone()); drop(resources); let logger = StoringLogger::default(); logger.trace(format!( "{:?} already added", weak_resource.upgrade().expect("Dangling!") )); let (t, did_run) = future.await; Ok((logger, t, did_run)) } else { let inner_weak = Rc::downgrade(&self.0); let future = Box::pin(async move { let this = Self(inner_weak.upgrade().expect("Dangling!")); let resource = weak_resource.upgrade().expect("Dangling!"); // Need to convert Box to String for Clone for Shared let result = this.0.core.add(&this, resource, force_run).await; result .map(|(logger, t, did_run)| (logger, As::from_artifact(t), did_run)) .map_err(|(logger, e)| (logger, e.to_string())) }) .shared(); let future_clone = future.clone(); resources.insert( storable_resource, (Box::pin(async move { let result = future_clone.await; if result.is_err() { // Step back to give the initial caller time to handle the error before unwrapping sleep(Duration::from_millis(0)).await; } result.map(|(_, t, did_run)| (t, did_run)).unwrap() }) as Pin>>) .shared(), ); drop(resources); let result = future.await; result.map_err(|(logger, e)| (logger, e.into())) }; result.map(|(logger, t, did_run)| (logger, t.into_artifact(), did_run)) } } #[derive(Debug)] pub struct SetupFacade< SR, LOG, L = DefaultLocator, B = DefaultBuilder, Rs = DefaultResources<'static, &'static str>, As = DefaultArtifacts<'static, &'static str>, >(LOG, Setup); impl SetupFacade { pub fn new(symbol_runner: SR, logger: LOG) -> Self { Self::new_with(symbol_runner, logger) } } impl SetupFacade { pub fn new_with(symbol_runner: SR, logger: LOG) -> Self { Self( logger, Setup(Rc::new(SetupInner { core: RegularSetupCore::new(symbol_runner), resources: RefCell::default(), })), ) } } impl< SR: 'static, LOG: 'static + Logger, L: 'static, B: 'static, Rs: 'static + Eq + Hash, As: 'static, > SetupFacade { pub async fn add_force(&self, resource: R, force_run: bool) -> AddResult where Rs: FromResource, As: FromArtifact + Clone, R::Artifact: Clone, RegularSetupCore: SetupCore>, { let result = self.1.add(resource, force_run).await; match result { Ok((logger, t, did_run)) => { if self .0 .put(logger.release().into_iter().filter(|e| e.0 <= 3)) == 0 { self.0.write(3, "."); } Ok((t, did_run)) } Err((logger, e)) => { self.0.put(logger.release()); Err(e) } } } pub async fn add(&self, resource: R) -> AddResult where RegularSetupCore: SetupCore>, Rs: FromResource, As: FromArtifact + Clone, R::Artifact: Clone, SR: SymbolRunner, { self.add_force(resource, false).await } pub async fn run_symbol( &self, symbol: S, force: bool, ) -> Result> where RegularSetupCore: SymbolRunner, { symbol.run(&(self.1).0.core, &self.0, force).await } } #[cfg(test)] mod test { use super::SymbolRunner; use crate::async_utils::run; use crate::loggers::{Logger, StoringLogger}; use crate::resources::{FromArtifact, FromResource, Resource}; use crate::symbols::Symbol; use crate::to_artifact::ToArtifact; use crate::{ImplementationBuilder, ResourceLocator, Setup}; use async_trait::async_trait; use std::cell::RefCell; use std::error::Error; use std::fmt::Debug; use std::rc::{Rc, Weak}; struct TestSymbolRunner { count: Rc>, } #[async_trait(?Send)] impl SymbolRunner for TestSymbolRunner { async fn run_symbol( &self, symbol: &S, _logger: &L, force: bool, ) -> Result> { let run = force || !symbol.target_reached().await?; if run { *self.count.borrow_mut() += 1; } Ok(run) } } #[derive(Debug, PartialEq, Eq, Hash)] struct TestResource(&'static str, T); impl Resource for TestResource { type Artifact = (); } #[derive(Debug, Hash, PartialEq, Eq)] enum Resources { A(Rc>), B(Rc>), } impl FromResource> for Resources { fn from_resource(from: TestResource<&'static str>) -> (Self, Weak>) { let inner = Rc::new(from); (Self::A(Rc::clone(&inner)), Rc::downgrade(&inner)) } } impl FromResource> for Resources { fn from_resource(from: TestResource<()>) -> (Self, Weak>) { let inner = Rc::new(from); (Self::B(Rc::clone(&inner)), Rc::downgrade(&inner)) } } #[derive(Clone)] struct Artifacts; impl FromArtifact> for Artifacts { fn from_artifact(_from: ()) -> Self { Self } #[allow(clippy::unused_unit)] fn into_artifact(self) -> () { #[allow(clippy::unused_unit)] () } } struct TestResourceLocator; impl ResourceLocator> for TestResourceLocator { type Prerequisites = (); fn locate(_resource: &TestResource) -> ( as ToArtifact>::Artifact, ()) { ((), ()) } } struct TestImplementationBuilder; impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = TestResource<()>; fn prerequisites(resource: &TestResource<&'static str>) -> Self::Prerequisites { TestResource(resource.1, ()) } fn create( resource: &TestResource<&'static str>, (): &(), _inputs: ::Artifact, ) -> Self::Implementation { TestSymbol { reached: resource.0.chars().next().unwrap().is_uppercase(), } } } impl ImplementationBuilder> for TestImplementationBuilder { type Implementation = TestSymbol; type Prerequisites = (); fn prerequisites(_resource: &TestResource<()>) -> Self::Prerequisites {} fn create(resource: &TestResource<()>, (): &(), (): ()) -> Self::Implementation { TestSymbol { reached: resource.0.chars().next().unwrap().is_uppercase(), } } } #[derive(Debug)] struct TestSymbol { reached: bool, } #[async_trait(?Send)] impl Symbol for TestSymbol { async fn target_reached(&self) -> Result> { Ok(self.reached) } async fn execute(&self) -> Result<(), Box> { Ok(()) } } fn get_setup() -> ( Rc>, Setup< TestSymbolRunner, StoringLogger, TestResourceLocator, TestImplementationBuilder, Resources, Artifacts, >, ) { let count = Rc::new(RefCell::new(0)); let runner = TestSymbolRunner { count: Rc::clone(&count), }; (count, Setup::new_with(runner, StoringLogger::new())) } #[test] fn correctly_uses_force() { run(async { let (count, setup) = get_setup(); setup.add(TestResource("A", "b")).await.unwrap(); assert_eq!(*count.borrow(), 2); setup.add(TestResource("A", "b")).await.unwrap(); assert_eq!(*count.borrow(), 2); let (count, setup) = get_setup(); setup.add(TestResource("A", "B")).await.unwrap(); assert_eq!(*count.borrow(), 0); }); } }