use super::core::{RegularSetupCore, SetupCore}; use super::runnable::Runnable; use super::util::{AddResult, AddableResource}; use super::SymbolRunner; use crate::loggers::Logger; use crate::resources::{DefaultArtifacts, DefaultResources, FromArtifact, FromResource}; use crate::{DefaultBuilder, DefaultLocator}; use futures::future::FutureExt; use futures::future::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; type Cache = HashMap>>>>; #[derive(Debug)] struct SetupInner { core: CORE, logger: LOG, resources: RefCell>, } #[derive(Debug)] pub struct Setup< SR, LOG, L = DefaultLocator, B = DefaultBuilder, Rs = DefaultResources<'static, &'static str>, As = DefaultArtifacts<'static, &'static str>, >(Rc, LOG, Rs, As>>); // https://github.com/rust-lang/rust/issues/27336 impl Setup { pub fn new(symbol_runner: SR, logger: LOG) -> Self { Self::new_with(symbol_runner, logger) } } impl Setup { pub fn new_with(symbol_runner: SR, logger: LOG) -> Self { Self(Rc::new(SetupInner { core: RegularSetupCore::new(symbol_runner), logger, resources: RefCell::default(), })) } } impl< L: 'static, B: 'static, SR: 'static, LOG: 'static + Logger, Rs: Hash + Eq + 'static, As: 'static, > Setup { fn borrow_resources(&self) -> RefMut<'_, Cache> { self.0.resources.borrow_mut() } pub async fn add_force( &self, resource: R, force_run: bool, ) -> AddResult 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(); 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); Ok(future.await) } 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 this .0 .core .add(&this, &this.0.logger, resource, force_run) .await .map(|(t, did_run)| (As::from_artifact(t), did_run)) .map_err(|e| e.to_string()) }) .shared(); let future_clone = future.clone(); resources.insert( storable_resource, (Box::pin(async move { future_clone.await.unwrap() }) as Pin>>) .shared(), ); drop(resources); future.await.map_err(|e| e.into()) } .map(|(t, did_run)| (t.into_artifact(), did_run)) } // // Legacy // pub async fn add(&self, resource: R) -> AddResult where RegularSetupCore: SetupCore, Rs: FromResource, As: FromArtifact + Clone, R::Artifact: Clone, { self.add_force(resource, false).await } pub async fn run_symbol(&self, symbol: S, force: bool) -> Result> where RegularSetupCore: SymbolRunner, { symbol.run(&self.0.core, &self.0.logger, 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 } fn into_artifact(self) -> () { () } } 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); }); } }