From a74ba61cc1cf7e356c940974e961ca987358d269 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Mon, 30 Jan 2023 17:14:31 -0600 Subject: [PATCH 01/13] experiment --- mantle/Cargo.lock | 7 + mantle/rbx_mantle/Cargo.toml | 1 + mantle/rbx_mantle/src/lib.rs | 2 + mantle/rbx_mantle/src/resource_graph.rs | 4 +- mantle/rbx_mantle/src/resource_graph_v2.rs | 124 ++++++++++++ mantle/rbx_mantle/src/resources.rs | 190 ++++++++++++++++++ .../rbx_mantle/src/roblox_resource_manager.rs | 4 +- 7 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 mantle/rbx_mantle/src/resource_graph_v2.rs create mode 100644 mantle/rbx_mantle/src/resources.rs diff --git a/mantle/Cargo.lock b/mantle/Cargo.lock index a41e950..40117b0 100644 --- a/mantle/Cargo.lock +++ b/mantle/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + [[package]] name = "approx" version = "0.5.1" @@ -2036,6 +2042,7 @@ dependencies = [ name = "rbx_mantle" version = "0.11.6" dependencies = [ + "anyhow", "async-trait", "chrono", "clap", diff --git a/mantle/rbx_mantle/Cargo.toml b/mantle/rbx_mantle/Cargo.toml index 204eb69..b24f74e 100644 --- a/mantle/rbx_mantle/Cargo.toml +++ b/mantle/rbx_mantle/Cargo.toml @@ -32,3 +32,4 @@ yansi = "0.5.0" url = { version = "2.2.2", features = ["serde"] } log = "0.4.14" schemars = { version = "=0.8.8-blake.2", git = "https://github.com/blake-mealey/schemars", branch = "raw-comments", features = ["derive", "url", "preserve_order"] } +anyhow = "1.0.68" diff --git a/mantle/rbx_mantle/src/lib.rs b/mantle/rbx_mantle/src/lib.rs index e095d6e..c059998 100644 --- a/mantle/rbx_mantle/src/lib.rs +++ b/mantle/rbx_mantle/src/lib.rs @@ -1,5 +1,7 @@ pub mod config; pub mod project; pub mod resource_graph; +pub mod resource_graph_v2; +pub mod resources; pub mod roblox_resource_manager; pub mod state; diff --git a/mantle/rbx_mantle/src/resource_graph.rs b/mantle/rbx_mantle/src/resource_graph.rs index bdda778..de79ef7 100644 --- a/mantle/rbx_mantle/src/resource_graph.rs +++ b/mantle/rbx_mantle/src/resource_graph.rs @@ -43,10 +43,10 @@ pub(crate) use optional_output; pub type ResourceId = String; pub trait Resource: Clone { - fn get_id(&self) -> ResourceId; + fn id(&self) -> ResourceId; fn get_inputs_hash(&self) -> String; fn get_outputs_hash(&self) -> String; - fn get_inputs(&self) -> TInputs; + fn inputs(&self) -> TInputs; fn get_outputs(&self) -> Option; fn get_dependencies(&self) -> Vec; fn set_outputs(&mut self, outputs: TOutputs); diff --git a/mantle/rbx_mantle/src/resource_graph_v2.rs b/mantle/rbx_mantle/src/resource_graph_v2.rs new file mode 100644 index 0000000..ecdcedc --- /dev/null +++ b/mantle/rbx_mantle/src/resource_graph_v2.rs @@ -0,0 +1,124 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + cell::RefCell, + collections::BTreeMap, + rc::Rc, +}; + +use crate::resources::{ + ExperienceInputs, ExperienceResource, PlaceInputs, PlaceResource, ResourceId, ResourceManager, + ResourceManagerContext, ResourceOutputs, ResourceRef, ResourceVec, WeakResourceVec, +}; + +fn create_graph() { + let experience = Rc::new(RefCell::new(ExperienceResource { + id: "singleton".to_owned(), + inputs: Box::new(ExperienceInputs { group_id: None }), + outputs: None, + })); + + let place = Rc::new(RefCell::new(PlaceResource { + id: "start".to_owned(), + inputs: Box::new(PlaceInputs { is_start: true }), + outputs: None, + experience: Rc::downgrade(&experience), + })); + + let resources: ResourceVec = vec![experience, place]; + let graph = ResourceGraph::new(&resources); +} + +pub struct ResourceGraph { + resources: BTreeMap, +} + +impl ResourceGraph { + pub fn new(resources: &[ResourceRef]) -> Self { + Self { + resources: resources + .iter() + .map(|resource| (resource.borrow().id(), *resource)) + .collect(), + } + } + + pub fn outputs(&self, resource_id: &str) -> Option> { + self.resources.get(resource_id).and_then(|resource| { + let resource = resource.borrow(); + if resource.has_outputs() { + Some(resource.outputs()) + } else { + None + } + }) + } + + fn topological_order(&self) -> anyhow::Result { + let mut dependency_graph: BTreeMap = self + .resources + .iter() + .map(|(id, resource)| (id.clone(), resource.borrow().dependencies())) + .collect(); + + let mut start_nodes: Vec = dependency_graph + .iter() + .filter_map(|(node, deps)| { + if deps.is_empty() { + Some(node.clone()) + } else { + None + } + }) + .collect(); + + let mut ordered: Vec = Vec::new(); + while let Some(start_node) = start_nodes.pop() { + ordered.push(start_node.clone()); + for (node, deps) in dependency_graph.iter_mut() { + if deps.iter().any(|dep| dep.borrow().id() == start_node) { + deps.retain(|dep| dep.borrow().id() != start_node); + if deps.is_empty() { + start_nodes.push(node.clone()); + } + } + } + } + + let has_cycles = dependency_graph.iter().any(|(_, deps)| !deps.is_empty()); + match has_cycles { + true => Err(anyhow::Error::msg( + "Cannot evaluate resource graph because it has cycles", + )), + false => Ok(ordered + .iter() + .map(|id| *self.resources.get(id).unwrap()) + .collect()), + } + } + + pub async fn evaluate_delete( + resource: Rc>, + context: &mut ResourceManagerContext, + ) -> anyhow::Result<()> { + resource.borrow_mut().create(context, None); + Ok(()) + } + + pub async fn evaluate( + &self, + previous_graph: &ResourceGraph, + context: &ResourceManagerContext, + ) -> anyhow::Result<()> { + let mut previous_resources = previous_graph.topological_order()?; + previous_resources.reverse(); + for resource in previous_resources { + if self.resources.get(&resource.id()).is_some() { + continue; + } + + // TODO: delete + } + + Ok(()) + } +} diff --git a/mantle/rbx_mantle/src/resources.rs b/mantle/rbx_mantle/src/resources.rs new file mode 100644 index 0000000..0a46170 --- /dev/null +++ b/mantle/rbx_mantle/src/resources.rs @@ -0,0 +1,190 @@ +use std::{ + borrow::Borrow, + cell::RefCell, + path::PathBuf, + rc::{Rc, Weak}, +}; + +use async_trait::async_trait; +use rbx_api::{ + experiences::models::CreateExperienceResponse, + models::{AssetId, CreatorType}, + RobloxApi, +}; + +pub struct ResourceManagerContext { + pub roblox_api: RobloxApi, + pub project_path: PathBuf, + pub payment_source: CreatorType, + pub allow_purchases: bool, +} + +pub enum UpdateStrategy { + UpdateInPlace, + Recreate, +} + +pub type ResourceRef = Rc>; +pub type ResourceVec = Vec; +pub type WeakResourceRef = Weak>; +pub type WeakResourceVec = Vec; + +pub trait ResourceInputs {} + +pub trait ResourceOutputs {} + +pub type ResourceId = String; + +pub trait Resource { + fn id(&self) -> ResourceId; + + fn inputs(&self) -> Box; + + fn outputs(&self) -> Box; + + fn has_outputs(&self) -> bool; + + fn dependencies(&self) -> WeakResourceVec; +} + +#[async_trait] +pub trait ResourceManager: Resource { + // async fn creation_price( + // &self, + // context: &mut ResourceManagerContext, + // ) -> anyhow::Result> { + // Ok(None) + // } + + async fn create( + &mut self, + context: &mut ResourceManagerContext, + price: Option, + ) -> anyhow::Result<()>; + + // TODO: separate traits dependening on strategy + fn update_strategy(&self) -> UpdateStrategy { + UpdateStrategy::UpdateInPlace + } + + async fn update( + &mut self, + context: &mut ResourceManagerContext, + price: Option, + ) -> anyhow::Result<()>; + + async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()>; +} + +pub struct ExperienceResource { + pub id: ResourceId, + pub inputs: Box, + pub outputs: Option>, + pub something_else: bool, +} + +pub struct ExperienceInputs { + pub group_id: Option, +} +impl ResourceInputs for ExperienceInputs {} + +pub struct ExperienceOutputs { + pub asset_id: AssetId, + pub start_place_id: AssetId, +} +impl ResourceOutputs for ExperienceOutputs {} + +impl Resource for ExperienceResource { + fn id(&self) -> ResourceId { + self.id + } + + fn inputs(&self) -> Box { + self.inputs + } + + fn has_outputs(&self) -> bool { + self.outputs.is_some() + } + + fn outputs(&self) -> Box { + self.outputs.unwrap() + } + + fn dependencies(&self) -> WeakResourceVec { + vec![] + } +} + +#[async_trait] +impl ResourceManager for ExperienceResource { + async fn create( + &mut self, + context: &mut ResourceManagerContext, + price: Option, + ) -> anyhow::Result<()> { + let CreateExperienceResponse { + universe_id, + root_place_id, + } = context + .roblox_api + .create_experience(self.inputs.group_id) + .await?; + + self.outputs = Some(Box::new(ExperienceOutputs { + asset_id: universe_id, + start_place_id: root_place_id, + })); + + Ok(()) + } + + async fn update( + &mut self, + context: &mut ResourceManagerContext, + price: Option, + ) -> anyhow::Result<()> { + Ok(()) + } +} + +pub struct PlaceResource { + pub id: ResourceId, + pub inputs: Box, + pub outputs: Option>, + pub experience: Weak>, +} + +pub struct PlaceInputs { + is_start: bool, +} +impl ResourceInputs for PlaceInputs {} + +pub struct PlaceOutputs { + pub asset_id: AssetId, +} +impl ResourceOutputs for PlaceOutputs {} + +impl Resource for PlaceResource { + fn id(&self) -> ResourceId { + let experience = self.experience.upgrade().unwrap(); + experience.borrow().something_else; + id + } + + fn inputs(&self) -> Box { + self.inputs + } + + fn has_outputs(&self) -> bool { + self.outputs.is_some() + } + + fn outputs(&self) -> Box { + self.outputs.unwrap() + } + + fn dependencies(&self) -> WeakResourceVec { + vec![self.experience] + } +} diff --git a/mantle/rbx_mantle/src/roblox_resource_manager.rs b/mantle/rbx_mantle/src/roblox_resource_manager.rs index b21bf70..5e9eea7 100644 --- a/mantle/rbx_mantle/src/roblox_resource_manager.rs +++ b/mantle/rbx_mantle/src/roblox_resource_manager.rs @@ -253,7 +253,7 @@ impl RobloxResource { } impl Resource for RobloxResource { - fn get_id(&self) -> String { + fn id(&self) -> String { self.id.clone() } @@ -285,7 +285,7 @@ impl Resource for RobloxResource { .to_owned() } - fn get_inputs(&self) -> RobloxInputs { + fn inputs(&self) -> RobloxInputs { self.inputs.clone() } From ae3df5cc6a5c5eecbe9e4118b42a7fd3e6e6bc2e Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Mon, 30 Jan 2023 20:23:36 -0600 Subject: [PATCH 02/13] more experiments --- mantle/rbx_mantle/src/resource_graph.rs | 4 +- mantle/rbx_mantle/src/resource_graph_v2.rs | 79 ++++---- mantle/rbx_mantle/src/resources.rs | 190 ------------------ mantle/rbx_mantle/src/resources/experience.rs | 79 ++++++++ mantle/rbx_mantle/src/resources/mod.rs | 64 ++++++ mantle/rbx_mantle/src/resources/place.rs | 79 ++++++++ .../rbx_mantle/src/roblox_resource_manager.rs | 4 +- 7 files changed, 260 insertions(+), 239 deletions(-) delete mode 100644 mantle/rbx_mantle/src/resources.rs create mode 100644 mantle/rbx_mantle/src/resources/experience.rs create mode 100644 mantle/rbx_mantle/src/resources/mod.rs create mode 100644 mantle/rbx_mantle/src/resources/place.rs diff --git a/mantle/rbx_mantle/src/resource_graph.rs b/mantle/rbx_mantle/src/resource_graph.rs index de79ef7..bdda778 100644 --- a/mantle/rbx_mantle/src/resource_graph.rs +++ b/mantle/rbx_mantle/src/resource_graph.rs @@ -43,10 +43,10 @@ pub(crate) use optional_output; pub type ResourceId = String; pub trait Resource: Clone { - fn id(&self) -> ResourceId; + fn get_id(&self) -> ResourceId; fn get_inputs_hash(&self) -> String; fn get_outputs_hash(&self) -> String; - fn inputs(&self) -> TInputs; + fn get_inputs(&self) -> TInputs; fn get_outputs(&self) -> Option; fn get_dependencies(&self) -> Vec; fn set_outputs(&mut self, outputs: TOutputs); diff --git a/mantle/rbx_mantle/src/resource_graph_v2.rs b/mantle/rbx_mantle/src/resource_graph_v2.rs index ecdcedc..79b932b 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2.rs @@ -1,63 +1,46 @@ -use std::{ - borrow::{Borrow, BorrowMut}, - cell::RefCell, - collections::BTreeMap, - rc::Rc, -}; +use std::collections::BTreeMap; use crate::resources::{ - ExperienceInputs, ExperienceResource, PlaceInputs, PlaceResource, ResourceId, ResourceManager, - ResourceManagerContext, ResourceOutputs, ResourceRef, ResourceVec, WeakResourceVec, + experience::*, place::*, ManagedResource, ResourceId, ResourceManagerContext, }; fn create_graph() { - let experience = Rc::new(RefCell::new(ExperienceResource { + let mut experience = ExperienceResource { id: "singleton".to_owned(), - inputs: Box::new(ExperienceInputs { group_id: None }), + inputs: ExperienceInputs { group_id: None }, outputs: None, - })); + }; - let place = Rc::new(RefCell::new(PlaceResource { + let mut place = PlaceResource { id: "start".to_owned(), - inputs: Box::new(PlaceInputs { is_start: true }), + inputs: PlaceInputs { is_start: true }, outputs: None, - experience: Rc::downgrade(&experience), - })); + experience: &experience, + }; - let resources: ResourceVec = vec![experience, place]; + let resources: Vec<&mut dyn ManagedResource> = vec![&mut experience, &mut place]; let graph = ResourceGraph::new(&resources); } -pub struct ResourceGraph { - resources: BTreeMap, +pub struct ResourceGraph<'a> { + resources: BTreeMap>, } -impl ResourceGraph { - pub fn new(resources: &[ResourceRef]) -> Self { +impl<'a> ResourceGraph<'a> { + pub fn new(resources: &[&mut dyn ManagedResource]) -> Self { Self { resources: resources .iter() - .map(|resource| (resource.borrow().id(), *resource)) + .map(|resource| (resource.id(), *resource)) .collect(), } } - pub fn outputs(&self, resource_id: &str) -> Option> { - self.resources.get(resource_id).and_then(|resource| { - let resource = resource.borrow(); - if resource.has_outputs() { - Some(resource.outputs()) - } else { - None - } - }) - } - - fn topological_order(&self) -> anyhow::Result { - let mut dependency_graph: BTreeMap = self + fn topological_order(&self) -> anyhow::Result> { + let mut dependency_graph: BTreeMap> = self .resources .iter() - .map(|(id, resource)| (id.clone(), resource.borrow().dependencies())) + .map(|(id, resource)| (id.clone(), resource.dependencies())) .collect(); let mut start_nodes: Vec = dependency_graph @@ -75,8 +58,8 @@ impl ResourceGraph { while let Some(start_node) = start_nodes.pop() { ordered.push(start_node.clone()); for (node, deps) in dependency_graph.iter_mut() { - if deps.iter().any(|dep| dep.borrow().id() == start_node) { - deps.retain(|dep| dep.borrow().id() != start_node); + if deps.iter().any(|dep| dep.id() == start_node) { + deps.retain(|dep| dep.id() != start_node); if deps.is_empty() { start_nodes.push(node.clone()); } @@ -96,18 +79,18 @@ impl ResourceGraph { } } - pub async fn evaluate_delete( - resource: Rc>, + pub async fn evaluate_delete<'c>( + &self, + resource: &'c mut dyn ManagedResource<'c>, context: &mut ResourceManagerContext, ) -> anyhow::Result<()> { - resource.borrow_mut().create(context, None); - Ok(()) + resource.delete(context).await } - pub async fn evaluate( - &self, - previous_graph: &ResourceGraph, - context: &ResourceManagerContext, + pub async fn evaluate<'b: 'a>( + &'a self, + previous_graph: &'b ResourceGraph<'b>, + context: &mut ResourceManagerContext, ) -> anyhow::Result<()> { let mut previous_resources = previous_graph.topological_order()?; previous_resources.reverse(); @@ -117,6 +100,12 @@ impl ResourceGraph { } // TODO: delete + self.evaluate_delete(resource, context); + } + + let current_resources = self.topological_order()?; + for resource in current_resources { + self.evaluate_delete(resource, context); } Ok(()) diff --git a/mantle/rbx_mantle/src/resources.rs b/mantle/rbx_mantle/src/resources.rs deleted file mode 100644 index 0a46170..0000000 --- a/mantle/rbx_mantle/src/resources.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::{ - borrow::Borrow, - cell::RefCell, - path::PathBuf, - rc::{Rc, Weak}, -}; - -use async_trait::async_trait; -use rbx_api::{ - experiences::models::CreateExperienceResponse, - models::{AssetId, CreatorType}, - RobloxApi, -}; - -pub struct ResourceManagerContext { - pub roblox_api: RobloxApi, - pub project_path: PathBuf, - pub payment_source: CreatorType, - pub allow_purchases: bool, -} - -pub enum UpdateStrategy { - UpdateInPlace, - Recreate, -} - -pub type ResourceRef = Rc>; -pub type ResourceVec = Vec; -pub type WeakResourceRef = Weak>; -pub type WeakResourceVec = Vec; - -pub trait ResourceInputs {} - -pub trait ResourceOutputs {} - -pub type ResourceId = String; - -pub trait Resource { - fn id(&self) -> ResourceId; - - fn inputs(&self) -> Box; - - fn outputs(&self) -> Box; - - fn has_outputs(&self) -> bool; - - fn dependencies(&self) -> WeakResourceVec; -} - -#[async_trait] -pub trait ResourceManager: Resource { - // async fn creation_price( - // &self, - // context: &mut ResourceManagerContext, - // ) -> anyhow::Result> { - // Ok(None) - // } - - async fn create( - &mut self, - context: &mut ResourceManagerContext, - price: Option, - ) -> anyhow::Result<()>; - - // TODO: separate traits dependening on strategy - fn update_strategy(&self) -> UpdateStrategy { - UpdateStrategy::UpdateInPlace - } - - async fn update( - &mut self, - context: &mut ResourceManagerContext, - price: Option, - ) -> anyhow::Result<()>; - - async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()>; -} - -pub struct ExperienceResource { - pub id: ResourceId, - pub inputs: Box, - pub outputs: Option>, - pub something_else: bool, -} - -pub struct ExperienceInputs { - pub group_id: Option, -} -impl ResourceInputs for ExperienceInputs {} - -pub struct ExperienceOutputs { - pub asset_id: AssetId, - pub start_place_id: AssetId, -} -impl ResourceOutputs for ExperienceOutputs {} - -impl Resource for ExperienceResource { - fn id(&self) -> ResourceId { - self.id - } - - fn inputs(&self) -> Box { - self.inputs - } - - fn has_outputs(&self) -> bool { - self.outputs.is_some() - } - - fn outputs(&self) -> Box { - self.outputs.unwrap() - } - - fn dependencies(&self) -> WeakResourceVec { - vec![] - } -} - -#[async_trait] -impl ResourceManager for ExperienceResource { - async fn create( - &mut self, - context: &mut ResourceManagerContext, - price: Option, - ) -> anyhow::Result<()> { - let CreateExperienceResponse { - universe_id, - root_place_id, - } = context - .roblox_api - .create_experience(self.inputs.group_id) - .await?; - - self.outputs = Some(Box::new(ExperienceOutputs { - asset_id: universe_id, - start_place_id: root_place_id, - })); - - Ok(()) - } - - async fn update( - &mut self, - context: &mut ResourceManagerContext, - price: Option, - ) -> anyhow::Result<()> { - Ok(()) - } -} - -pub struct PlaceResource { - pub id: ResourceId, - pub inputs: Box, - pub outputs: Option>, - pub experience: Weak>, -} - -pub struct PlaceInputs { - is_start: bool, -} -impl ResourceInputs for PlaceInputs {} - -pub struct PlaceOutputs { - pub asset_id: AssetId, -} -impl ResourceOutputs for PlaceOutputs {} - -impl Resource for PlaceResource { - fn id(&self) -> ResourceId { - let experience = self.experience.upgrade().unwrap(); - experience.borrow().something_else; - id - } - - fn inputs(&self) -> Box { - self.inputs - } - - fn has_outputs(&self) -> bool { - self.outputs.is_some() - } - - fn outputs(&self) -> Box { - self.outputs.unwrap() - } - - fn dependencies(&self) -> WeakResourceVec { - vec![self.experience] - } -} diff --git a/mantle/rbx_mantle/src/resources/experience.rs b/mantle/rbx_mantle/src/resources/experience.rs new file mode 100644 index 0000000..6d7b6c5 --- /dev/null +++ b/mantle/rbx_mantle/src/resources/experience.rs @@ -0,0 +1,79 @@ +use async_trait::async_trait; +use rbx_api::models::AssetId; + +use super::{ + ManagedResource, Resource, ResourceId, ResourceInputs, ResourceManagerContext, ResourceOutputs, +}; + +pub struct ExperienceResource { + pub id: ResourceId, + pub inputs: ExperienceInputs, + pub outputs: Option, +} + +pub struct ExperienceInputs { + pub group_id: Option, +} +impl ResourceInputs for ExperienceInputs {} + +pub struct ExperienceOutputs { + pub asset_id: AssetId, + pub start_place_id: AssetId, +} +impl ResourceOutputs for ExperienceOutputs {} + +impl<'a> Resource<'a> for ExperienceResource { + fn id(&self) -> ResourceId { + self.id + } + + // TODO: Should this be a Box? Is there a better container for it? + fn inputs(&self) -> Box { + Box::new(self.inputs) + } + + fn outputs(&self) -> Option> { + self.outputs + .map(|o| Box::new(o) as Box) + } + + fn dependencies(&self) -> Vec<&'a dyn ManagedResource> { + vec![] + } +} + +#[async_trait] +impl<'a> ManagedResource<'a> for ExperienceResource { + // async fn create( + // &mut self, + // context: &mut ResourceManagerContext, + // price: Option, + // ) -> anyhow::Result<()> { + // let CreateExperienceResponse { + // universe_id, + // root_place_id, + // } = context + // .roblox_api + // .create_experience(self.inputs.group_id) + // .await?; + + // self.outputs = Some(Box::new(ExperienceOutputs { + // asset_id: universe_id, + // start_place_id: root_place_id, + // })); + + // Ok(()) + // } + + // async fn update( + // &mut self, + // context: &mut ResourceManagerContext, + // price: Option, + // ) -> anyhow::Result<()> { + // Ok(()) + // } + + async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()> { + Ok(()) + } +} diff --git a/mantle/rbx_mantle/src/resources/mod.rs b/mantle/rbx_mantle/src/resources/mod.rs new file mode 100644 index 0000000..c7165a6 --- /dev/null +++ b/mantle/rbx_mantle/src/resources/mod.rs @@ -0,0 +1,64 @@ +use std::path::PathBuf; + +use async_trait::async_trait; +use rbx_api::{models::CreatorType, RobloxApi}; + +pub mod experience; +pub mod place; + +pub struct ResourceManagerContext { + pub roblox_api: RobloxApi, + pub project_path: PathBuf, + pub payment_source: CreatorType, + pub allow_purchases: bool, +} + +pub enum UpdateStrategy { + UpdateInPlace, + Recreate, +} + +pub trait ResourceInputs {} + +pub trait ResourceOutputs {} + +pub type ResourceId = String; + +pub trait Resource<'a> { + fn id(&self) -> ResourceId; + + fn inputs(&self) -> Box; + + fn outputs(&self) -> Option>; + + fn dependencies(&self) -> Vec<&'a dyn ManagedResource>; +} + +#[async_trait] +pub trait ManagedResource<'a>: Resource<'a> { + // async fn creation_price( + // &self, + // context: &mut ResourceManagerContext, + // ) -> anyhow::Result> { + // Ok(None) + // } + + // async fn create( + // &mut self, + // context: &mut ResourceManagerContext, + // price: Option, + // ) -> anyhow::Result<()>; + + // // TODO: separate traits dependening on strategy + // fn update_strategy(&self) -> UpdateStrategy { + // UpdateStrategy::UpdateInPlace + // } + + // async fn update( + // &mut self, + // context: &mut ResourceManagerContext, + // price: Option, + // ) -> anyhow::Result<()>; + + async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()>; +} diff --git a/mantle/rbx_mantle/src/resources/place.rs b/mantle/rbx_mantle/src/resources/place.rs new file mode 100644 index 0000000..3bb30eb --- /dev/null +++ b/mantle/rbx_mantle/src/resources/place.rs @@ -0,0 +1,79 @@ +use async_trait::async_trait; +use rbx_api::models::AssetId; + +use super::{ + experience::ExperienceResource, ManagedResource, Resource, ResourceId, ResourceInputs, + ResourceManagerContext, ResourceOutputs, +}; + +pub struct PlaceResource<'a> { + pub id: ResourceId, + pub inputs: PlaceInputs, + pub outputs: Option, + pub experience: &'a ExperienceResource, +} + +pub struct PlaceInputs { + is_start: bool, +} +impl ResourceInputs for PlaceInputs {} + +pub struct PlaceOutputs { + pub asset_id: AssetId, +} +impl ResourceOutputs for PlaceOutputs {} + +impl<'a> Resource<'a> for PlaceResource<'a> { + fn id(&self) -> ResourceId { + self.id + } + + fn inputs(&self) -> Box { + Box::new(self.inputs) + } + + fn outputs(&self) -> Option> { + self.outputs + .map(|o| Box::new(o) as Box) + } + + fn dependencies(&self) -> Vec<&'a dyn ManagedResource> { + vec![self.experience] + } +} + +#[async_trait] +impl<'a> ManagedResource<'a> for PlaceResource<'a> { + // async fn create( + // &mut self, + // context: &mut ResourceManagerContext, + // price: Option, + // ) -> anyhow::Result<()> { + // let CreateExperienceResponse { + // universe_id, + // root_place_id, + // } = context + // .roblox_api + // .create_experience(self.inputs.group_id) + // .await?; + + // self.outputs = Some(Box::new(ExperienceOutputs { + // asset_id: universe_id, + // start_place_id: root_place_id, + // })); + + // Ok(()) + // } + + // async fn update( + // &mut self, + // context: &mut ResourceManagerContext, + // price: Option, + // ) -> anyhow::Result<()> { + // Ok(()) + // } + + async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()> { + Ok(()) + } +} diff --git a/mantle/rbx_mantle/src/roblox_resource_manager.rs b/mantle/rbx_mantle/src/roblox_resource_manager.rs index 5e9eea7..b21bf70 100644 --- a/mantle/rbx_mantle/src/roblox_resource_manager.rs +++ b/mantle/rbx_mantle/src/roblox_resource_manager.rs @@ -253,7 +253,7 @@ impl RobloxResource { } impl Resource for RobloxResource { - fn id(&self) -> String { + fn get_id(&self) -> String { self.id.clone() } @@ -285,7 +285,7 @@ impl Resource for RobloxResource { .to_owned() } - fn inputs(&self) -> RobloxInputs { + fn get_inputs(&self) -> RobloxInputs { self.inputs.clone() } From 72d07f6fdff2c21200df97758b90617301465cf2 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Mon, 30 Jan 2023 23:07:41 -0600 Subject: [PATCH 03/13] great success --- mantle/Cargo.lock | 159 +++++++++--------- mantle/Cargo.toml | 3 +- mantle/derive_resource/Cargo.toml | 11 ++ mantle/derive_resource/src/lib.rs | 67 ++++++++ mantle/rbx_mantle/Cargo.toml | 1 + mantle/rbx_mantle/src/resource_graph_v2.rs | 79 +++++---- mantle/rbx_mantle/src/resources/experience.rs | 49 +++--- mantle/rbx_mantle/src/resources/mod.rs | 24 ++- mantle/rbx_mantle/src/resources/place.rs | 51 +++--- 9 files changed, 274 insertions(+), 170 deletions(-) create mode 100644 mantle/derive_resource/Cargo.toml create mode 100644 mantle/derive_resource/src/lib.rs diff --git a/mantle/Cargo.lock b/mantle/Cargo.lock index 40117b0..0e38691 100644 --- a/mantle/Cargo.lock +++ b/mantle/Cargo.lock @@ -77,9 +77,9 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -417,10 +417,10 @@ dependencies = [ "itoa 0.4.8", "matches", "phf", - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "smallvec", - "syn 1.0.96", + "syn 1.0.107", ] [[package]] @@ -429,8 +429,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" dependencies = [ - "quote 1.0.18", - "syn 1.0.96", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -439,8 +439,8 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ - "quote 1.0.18", - "syn 1.0.96", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -465,10 +465,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "rustc_version 0.4.0", - "syn 1.0.96", + "syn 1.0.107", +] + +[[package]] +name = "derive_resource" +version = "0.1.0" +dependencies = [ + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -745,9 +753,9 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -922,9 +930,9 @@ dependencies = [ "log", "mac", "markup5ever", - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -1527,9 +1535,9 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -1654,9 +1662,9 @@ dependencies = [ "phf_generator 0.8.0", "phf_shared 0.8.0", "proc-macro-hack", - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -1692,9 +1700,9 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -1782,9 +1790,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -1804,8 +1812,8 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98eee3c112f2a6f784b6713fe1d7fb7d6506e066121c0a49371fdb976f72bae5" dependencies = [ - "quote 1.0.18", - "syn 1.0.96", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -1837,11 +1845,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ - "proc-macro2 1.0.39", + "proc-macro2 1.0.50", ] [[package]] @@ -2046,6 +2054,7 @@ dependencies = [ "async-trait", "chrono", "clap", + "derive_resource", "difference", "glob", "log", @@ -2412,10 +2421,10 @@ name = "schemars_derive" version = "0.8.8-blake.2" source = "git+https://github.com/blake-mealey/schemars?branch=raw-comments#7c0bf9940c16a894059904a2a0583e35cf224d1e" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "serde_derive_internals", - "syn 1.0.96", + "syn 1.0.107", ] [[package]] @@ -2525,9 +2534,9 @@ version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -2536,9 +2545,9 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -2558,9 +2567,9 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -2725,11 +2734,11 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "serde", "serde_derive", - "syn 1.0.96", + "syn 1.0.107", ] [[package]] @@ -2739,13 +2748,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "serde", "serde_derive", "serde_json", "sha1", - "syn 1.0.96", + "syn 1.0.107", ] [[package]] @@ -2776,8 +2785,8 @@ checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ "phf_generator 0.10.0", "phf_shared 0.10.0", - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", ] [[package]] @@ -2805,12 +2814,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.96" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "unicode-ident", ] @@ -2896,9 +2905,9 @@ version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -2982,10 +2991,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.39", - "quote 1.0.18", + "proc-macro2 1.0.50", + "quote 1.0.23", "standback", - "syn 1.0.96", + "syn 1.0.107", ] [[package]] @@ -3029,9 +3038,9 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", ] [[package]] @@ -3242,9 +3251,9 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -3266,7 +3275,7 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ - "quote 1.0.18", + "quote 1.0.23", "wasm-bindgen-macro-support", ] @@ -3276,9 +3285,9 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ - "proc-macro2 1.0.39", - "quote 1.0.18", - "syn 1.0.96", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/mantle/Cargo.toml b/mantle/Cargo.toml index bfac622..3a9fdd9 100644 --- a/mantle/Cargo.toml +++ b/mantle/Cargo.toml @@ -10,5 +10,6 @@ members = [ "rbx_auth", "rbx_cookie", "logger", - "integration_executor" + "integration_executor", + "derive_resource" ] diff --git a/mantle/derive_resource/Cargo.toml b/mantle/derive_resource/Cargo.toml new file mode 100644 index 0000000..1c472c7 --- /dev/null +++ b/mantle/derive_resource/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "derive_resource" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.23" +syn = "1.0.107" diff --git a/mantle/derive_resource/src/lib.rs b/mantle/derive_resource/src/lib.rs new file mode 100644 index 0000000..a631651 --- /dev/null +++ b/mantle/derive_resource/src/lib.rs @@ -0,0 +1,67 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput}; + +#[proc_macro_derive(Resource, attributes(dependency))] +pub fn derive_resource(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + + let dependency_fields: Vec<_> = match &input.data { + Data::Struct(data) => data + .fields + .iter() + .filter(|field| field.attrs.iter().any(|a| a.path.is_ident("dependency"))) + .collect(), + _ => panic!("expected struct to derive Resource"), + }; + + let deps = dependency_fields + .iter() + .filter_map(|d| d.ident.clone()) + .map(|ident| quote! {self.#ident.clone()}); + + let expanded = quote! { + impl Resource for #name { + fn id(&self) -> &str { + &self.id + } + + fn inputs(&self) -> &dyn ResourceInputs { + &self.inputs + } + + fn outputs(&self) -> &dyn ResourceOutputs { + &self.outputs + } + + fn dependencies(&self) -> Vec { + vec![ + #(#deps),* + ] + } + } + }; + + TokenStream::from(expanded) +} + +// impl Resource for ExperienceResource { +// fn id(&self) -> &str { +// &self.id +// } + +// fn inputs(&self) -> &dyn ResourceInputs { +// &self.inputs +// } + +// fn outputs(&self) -> &dyn ResourceOutputs { +// &self.outputs +// } + +// fn dependencies(&self) -> Vec { +// vec![] +// } +// } diff --git a/mantle/rbx_mantle/Cargo.toml b/mantle/rbx_mantle/Cargo.toml index b24f74e..0c66a29 100644 --- a/mantle/rbx_mantle/Cargo.toml +++ b/mantle/rbx_mantle/Cargo.toml @@ -16,6 +16,7 @@ include = [ rbx_auth = { path = "../rbx_auth" } rbx_api = { path = "../rbx_api" } logger = { path = "../logger" } +derive_resource = { path = "../derive_resource" } serde_yaml = { version = "0.8" } serde = { version = "1.0", features = ["derive"] } diff --git a/mantle/rbx_mantle/src/resource_graph_v2.rs b/mantle/rbx_mantle/src/resource_graph_v2.rs index 79b932b..a63b467 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2.rs @@ -1,46 +1,63 @@ -use std::collections::BTreeMap; - -use crate::resources::{ - experience::*, place::*, ManagedResource, ResourceId, ResourceManagerContext, +use std::{ + collections::BTreeMap, + sync::{Arc, RwLock}, }; +use crate::resources::{experience::*, place::*, ResourceId, ResourceManagerContext, ResourceRef}; + fn create_graph() { - let mut experience = ExperienceResource { + let experience = Arc::new(RwLock::new(ExperienceResource { id: "singleton".to_owned(), inputs: ExperienceInputs { group_id: None }, - outputs: None, - }; + outputs: ExperienceOutputs::Empty, + })); - let mut place = PlaceResource { + let place = Arc::new(RwLock::new(PlaceResource { id: "start".to_owned(), inputs: PlaceInputs { is_start: true }, - outputs: None, - experience: &experience, - }; + outputs: PlaceOutputs::Empty, + experience: Arc::downgrade(&experience), + })); - let resources: Vec<&mut dyn ManagedResource> = vec![&mut experience, &mut place]; + let resources: Vec = vec![experience, place]; let graph = ResourceGraph::new(&resources); } -pub struct ResourceGraph<'a> { - resources: BTreeMap>, +pub struct ResourceGraph { + resources: BTreeMap, } -impl<'a> ResourceGraph<'a> { - pub fn new(resources: &[&mut dyn ManagedResource]) -> Self { +impl ResourceGraph { + pub fn new(resources: &[ResourceRef]) -> Self { Self { resources: resources .iter() - .map(|resource| (resource.id(), *resource)) + .map(|resource| { + ( + resource.read().unwrap().id().to_owned(), + Arc::clone(resource), + ) + }) .collect(), } } - fn topological_order(&self) -> anyhow::Result> { - let mut dependency_graph: BTreeMap> = self + fn topological_order(&self) -> anyhow::Result> { + let mut dependency_graph: BTreeMap> = self .resources .iter() - .map(|(id, resource)| (id.clone(), resource.dependencies())) + .map(|(id, resource)| { + ( + id.clone(), + resource + .read() + .unwrap() + .dependencies() + .iter() + .filter_map(|d| d.upgrade().map(|x| x.read().unwrap().id().to_owned())) + .collect(), + ) + }) .collect(); let mut start_nodes: Vec = dependency_graph @@ -58,8 +75,8 @@ impl<'a> ResourceGraph<'a> { while let Some(start_node) = start_nodes.pop() { ordered.push(start_node.clone()); for (node, deps) in dependency_graph.iter_mut() { - if deps.iter().any(|dep| dep.id() == start_node) { - deps.retain(|dep| dep.id() != start_node); + if deps.iter().any(|dep| *dep == start_node) { + deps.retain(|dep| *dep != start_node); if deps.is_empty() { start_nodes.push(node.clone()); } @@ -74,28 +91,30 @@ impl<'a> ResourceGraph<'a> { )), false => Ok(ordered .iter() - .map(|id| *self.resources.get(id).unwrap()) + .map(|id| Arc::clone(self.resources.get(id).unwrap())) .collect()), } } - pub async fn evaluate_delete<'c>( + pub async fn evaluate_delete( &self, - resource: &'c mut dyn ManagedResource<'c>, + resource: ResourceRef, context: &mut ResourceManagerContext, ) -> anyhow::Result<()> { - resource.delete(context).await + resource.write().unwrap().delete(context).await?; + // .resource.delete(context).await; + Ok(()) } - pub async fn evaluate<'b: 'a>( - &'a self, - previous_graph: &'b ResourceGraph<'b>, + pub async fn evaluate( + &self, + previous_graph: &ResourceGraph, context: &mut ResourceManagerContext, ) -> anyhow::Result<()> { let mut previous_resources = previous_graph.topological_order()?; previous_resources.reverse(); for resource in previous_resources { - if self.resources.get(&resource.id()).is_some() { + if self.resources.get(resource.read().unwrap().id()).is_some() { continue; } diff --git a/mantle/rbx_mantle/src/resources/experience.rs b/mantle/rbx_mantle/src/resources/experience.rs index 6d7b6c5..f1ee042 100644 --- a/mantle/rbx_mantle/src/resources/experience.rs +++ b/mantle/rbx_mantle/src/resources/experience.rs @@ -1,49 +1,42 @@ use async_trait::async_trait; +use derive_resource::Resource; use rbx_api::models::AssetId; use super::{ ManagedResource, Resource, ResourceId, ResourceInputs, ResourceManagerContext, ResourceOutputs, + WeakResourceRef, }; -pub struct ExperienceResource { - pub id: ResourceId, - pub inputs: ExperienceInputs, - pub outputs: Option, -} - pub struct ExperienceInputs { pub group_id: Option, } impl ResourceInputs for ExperienceInputs {} -pub struct ExperienceOutputs { - pub asset_id: AssetId, - pub start_place_id: AssetId, +pub enum ExperienceOutputs { + Data { + asset_id: AssetId, + start_place_id: AssetId, + }, + Empty, } -impl ResourceOutputs for ExperienceOutputs {} - -impl<'a> Resource<'a> for ExperienceResource { - fn id(&self) -> ResourceId { - self.id - } - - // TODO: Should this be a Box? Is there a better container for it? - fn inputs(&self) -> Box { - Box::new(self.inputs) - } - - fn outputs(&self) -> Option> { - self.outputs - .map(|o| Box::new(o) as Box) +impl ResourceOutputs for ExperienceOutputs { + fn has_outputs(&self) -> bool { + match self { + Self::Empty => false, + _ => true, + } } +} - fn dependencies(&self) -> Vec<&'a dyn ManagedResource> { - vec![] - } +#[derive(Resource)] +pub struct ExperienceResource { + pub id: ResourceId, + pub inputs: ExperienceInputs, + pub outputs: ExperienceOutputs, } #[async_trait] -impl<'a> ManagedResource<'a> for ExperienceResource { +impl ManagedResource for ExperienceResource { // async fn create( // &mut self, // context: &mut ResourceManagerContext, diff --git a/mantle/rbx_mantle/src/resources/mod.rs b/mantle/rbx_mantle/src/resources/mod.rs index c7165a6..f811ec5 100644 --- a/mantle/rbx_mantle/src/resources/mod.rs +++ b/mantle/rbx_mantle/src/resources/mod.rs @@ -1,4 +1,7 @@ -use std::path::PathBuf; +use std::{ + path::PathBuf, + sync::{Arc, RwLock, Weak}, +}; use async_trait::async_trait; use rbx_api::{models::CreatorType, RobloxApi}; @@ -20,22 +23,27 @@ pub enum UpdateStrategy { pub trait ResourceInputs {} -pub trait ResourceOutputs {} +pub trait ResourceOutputs { + fn has_outputs(&self) -> bool; +} pub type ResourceId = String; -pub trait Resource<'a> { - fn id(&self) -> ResourceId; +pub type ResourceRef = Arc>; +pub type WeakResourceRef = Weak>; + +pub trait Resource { + fn id(&self) -> &str; - fn inputs(&self) -> Box; + fn inputs(&self) -> &dyn ResourceInputs; - fn outputs(&self) -> Option>; + fn outputs(&self) -> &dyn ResourceOutputs; - fn dependencies(&self) -> Vec<&'a dyn ManagedResource>; + fn dependencies(&self) -> Vec; } #[async_trait] -pub trait ManagedResource<'a>: Resource<'a> { +pub trait ManagedResource: Resource { // async fn creation_price( // &self, // context: &mut ResourceManagerContext, diff --git a/mantle/rbx_mantle/src/resources/place.rs b/mantle/rbx_mantle/src/resources/place.rs index 3bb30eb..08f5c2c 100644 --- a/mantle/rbx_mantle/src/resources/place.rs +++ b/mantle/rbx_mantle/src/resources/place.rs @@ -1,49 +1,44 @@ +use std::sync::{RwLock, Weak}; + use async_trait::async_trait; +use derive_resource::Resource; use rbx_api::models::AssetId; use super::{ experience::ExperienceResource, ManagedResource, Resource, ResourceId, ResourceInputs, - ResourceManagerContext, ResourceOutputs, + ResourceManagerContext, ResourceOutputs, WeakResourceRef, }; -pub struct PlaceResource<'a> { - pub id: ResourceId, - pub inputs: PlaceInputs, - pub outputs: Option, - pub experience: &'a ExperienceResource, -} - pub struct PlaceInputs { - is_start: bool, + pub is_start: bool, } impl ResourceInputs for PlaceInputs {} -pub struct PlaceOutputs { - pub asset_id: AssetId, +pub enum PlaceOutputs { + Data { asset_id: AssetId }, + Empty, } -impl ResourceOutputs for PlaceOutputs {} - -impl<'a> Resource<'a> for PlaceResource<'a> { - fn id(&self) -> ResourceId { - self.id - } - - fn inputs(&self) -> Box { - Box::new(self.inputs) +impl ResourceOutputs for PlaceOutputs { + fn has_outputs(&self) -> bool { + match self { + Self::Empty => false, + _ => true, + } } +} - fn outputs(&self) -> Option> { - self.outputs - .map(|o| Box::new(o) as Box) - } +#[derive(Resource)] +pub struct PlaceResource { + pub id: ResourceId, + pub inputs: PlaceInputs, + pub outputs: PlaceOutputs, - fn dependencies(&self) -> Vec<&'a dyn ManagedResource> { - vec![self.experience] - } + #[dependency] + pub experience: Weak>, } #[async_trait] -impl<'a> ManagedResource<'a> for PlaceResource<'a> { +impl ManagedResource for PlaceResource { // async fn create( // &mut self, // context: &mut ResourceManagerContext, From 5c32521338d81d0552562a167c3518fb1ec2ba95 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Tue, 31 Jan 2023 22:03:01 -0600 Subject: [PATCH 04/13] progress --- .../src/resource_graph_v2/evaluator.rs | 282 ++++++++++++++++++ .../mod.rs} | 81 +++-- mantle/rbx_mantle/src/resources/experience.rs | 54 ++-- mantle/rbx_mantle/src/resources/mod.rs | 51 ++-- mantle/rbx_mantle/src/resources/place.rs | 54 ++-- 5 files changed, 412 insertions(+), 110 deletions(-) create mode 100644 mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs rename mantle/rbx_mantle/src/{resource_graph_v2.rs => resource_graph_v2/mod.rs} (64%) diff --git a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs new file mode 100644 index 0000000..2df16d8 --- /dev/null +++ b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs @@ -0,0 +1,282 @@ +use std::path::PathBuf; + +use rbx_api::{models::CreatorType, RobloxApi}; +use rbx_auth::RobloxAuth; + +use crate::resources::{ResourceId, ResourceRef}; + +use super::ResourceGraph; + +pub struct ResourceGraphEvaluatorOptions { + pub project_path: PathBuf, + pub payment_source: CreatorType, + pub allow_purchases: bool, +} + +pub struct ResourceGraphEvaluatorContext { + pub options: ResourceGraphEvaluatorOptions, + pub roblox_api: RobloxApi, +} + +pub enum SkipReason { + PurchasesNotAllowed, +} + +pub enum OperationType { + Create, + Update, + Recreate, + Delete, + Noop, + Skip(SkipReason), +} + +pub enum OperationStatus { + Success, + Failure(anyhow::Error), +} + +pub struct OperationResult { + pub resource_id: ResourceId, + pub operation_type: OperationType, + pub status: OperationStatus, +} + +#[derive(Default)] +pub struct EvaluatorResults { + pub operation_results: Vec, +} + +impl EvaluatorResults { + pub fn is_empty(&self) -> bool { + self.operation_results.is_empty() + } + + pub fn create_succeeded(&mut self, resource_id: ResourceId) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Create, + status: OperationStatus::Success, + }) + } + pub fn create_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Create, + status: OperationStatus::Failure(error), + }) + } + + pub fn update_succeeded(&mut self, resource_id: ResourceId) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Update, + status: OperationStatus::Success, + }) + } + pub fn update_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Update, + status: OperationStatus::Failure(error), + }) + } + + pub fn recreate_succeeded(&mut self, resource_id: ResourceId) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Recreate, + status: OperationStatus::Success, + }) + } + pub fn recreate_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Recreate, + status: OperationStatus::Failure(error), + }) + } + + pub fn delete_succeeded(&mut self, resource_id: ResourceId) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Delete, + status: OperationStatus::Success, + }) + } + pub fn delete_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Delete, + status: OperationStatus::Failure(error), + }) + } + + pub fn noop(&mut self, resource_id: ResourceId) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Noop, + status: OperationStatus::Success, + }) + } + + pub fn skip(&mut self, resource_id: ResourceId, reason: SkipReason) { + self.operation_results.push(OperationResult { + resource_id, + operation_type: OperationType::Skip(reason), + status: OperationStatus::Success, + }) + } +} + +pub struct ResouceGraphEvaluator<'a> { + context: ResourceGraphEvaluatorContext, + previous_graph: &'a ResourceGraph, + desired_graph: &'a ResourceGraph, + next_graph: ResourceGraph, + results: EvaluatorResults, +} + +impl<'a> ResouceGraphEvaluator<'a> { + pub async fn new( + options: ResourceGraphEvaluatorOptions, + previous_graph: &'a ResourceGraph, + current_graph: &'a ResourceGraph, + ) -> anyhow::Result> { + let roblox_auth = RobloxAuth::new().await?; + let roblox_api = RobloxApi::new(roblox_auth)?; + Ok(ResouceGraphEvaluator { + context: ResourceGraphEvaluatorContext { + options, + roblox_api, + }, + previous_graph, + desired_graph: current_graph, + next_graph: ResourceGraph::default(), + results: EvaluatorResults::default(), + }) + } + + pub async fn evaluate( + &'a mut self, + ) -> anyhow::Result<(&'a EvaluatorResults, &'a ResourceGraph)> { + if !self.results.is_empty() { + panic!("Cannot use a graph evaluator more than once"); + } + + self.delete_removed_resources().await?; + self.create_or_update_added_or_changed_resources().await?; + + Ok((&self.results, &self.next_graph)) + } + + async fn delete_removed_resources(&mut self) -> anyhow::Result<()> { + let mut previous_resources = self.previous_graph.topological_order()?; + + // Iterate over previous resources in reverse order so that leaf resources are removed first + previous_resources.reverse(); + + for resource in previous_resources.iter() { + // no need to delete resources that still exist + let mut resource_write_ref = resource.write().unwrap(); + let resource_id = resource_write_ref.id().to_owned(); + if self.desired_graph.contains(&resource_id) { + continue; + } + + println!("Deleting: {}", resource_id); + // TODO: diff + println!("Dependencies: {:?}", resource_write_ref.dependencies()); + println!("Inputs: {:?}", resource_write_ref.inputs()); + + let delete_result = resource_write_ref.delete(&mut self.context).await; + + match delete_result { + Ok(()) => { + println!("Deleted resource {}", resource_id); + self.results.delete_succeeded(resource_id); + } + Err(error) => { + println!("Failed to delete resource {}: {}", resource_id, error); + self.results.delete_failed(resource_id, error); + } + } + } + + Ok(()) + } + + async fn create_resource(&mut self, resource: ResourceRef) { + let mut resource_write_ref = resource.write().unwrap(); + let resource_id = resource_write_ref.id().to_owned(); + + println!("Creating: {}", resource_id); + // TODO: diff + println!("Dependencies: {:?}", resource_write_ref.dependencies()); + println!("Inputs: {:?}", resource_write_ref.inputs()); + + let create_price_result = resource_write_ref.price(&mut self.context).await; + + let price = match create_price_result { + Ok(Some(price)) if price > 0 => { + if self.context.options.allow_purchases { + println!("{} Robux will be charged from your account.", price); + Some(price) + } else { + self.results + .skip(resource_id, SkipReason::PurchasesNotAllowed); + println!("Resource would cost {} to create. Give Mantle permission to make purchases with --allow-purchases.", price); + return; + } + } + Ok(_) => None, + Err(error) => { + self.results.create_failed(resource_id, error); + return; + } + }; + + let create_result = resource_write_ref.create(&mut self.context, price).await; + + match create_result { + Ok(()) => { + println!( + "Created resource {} with outputs: {:?}", + resource_id, + resource_write_ref.outputs() + ); + self.next_graph.insert(&resource_id, resource.clone()); + self.results.create_succeeded(resource_id); + } + Err(error) => { + println!("Failed to create resource {}: {}", resource_id, error); + self.results.create_failed(resource_id, error); + } + } + } + + fn update_resource(&mut self, resource: ResourceRef) { + let mut resource_write_ref = resource.write().unwrap(); + let resource_id = resource_write_ref.id().to_owned(); + + println!("Updating: {}", resource_id); + } + + async fn create_or_update_added_or_changed_resources(&mut self) -> anyhow::Result<()> { + let resources = self.desired_graph.topological_order()?; + + for resource in resources.iter() { + let resource_read_ref = resource.read().unwrap(); + let resource_id = resource_read_ref.id().to_owned(); + drop(resource_read_ref); + + if let Some(previous_resource) = self.previous_graph.get(&resource_id) { + // compare, check strategy, update or recreate + } else { + self.create_resource(resource.clone()).await; + } + } + + Ok(()) + } +} diff --git a/mantle/rbx_mantle/src/resource_graph_v2.rs b/mantle/rbx_mantle/src/resource_graph_v2/mod.rs similarity index 64% rename from mantle/rbx_mantle/src/resource_graph_v2.rs rename to mantle/rbx_mantle/src/resource_graph_v2/mod.rs index a63b467..f211021 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/mod.rs @@ -3,9 +3,11 @@ use std::{ sync::{Arc, RwLock}, }; -use crate::resources::{experience::*, place::*, ResourceId, ResourceManagerContext, ResourceRef}; +use crate::resources::{experience::*, place::*, ResourceId, ResourceRef}; -fn create_graph() { +pub mod evaluator; + +fn _create_graph() { let experience = Arc::new(RwLock::new(ExperienceResource { id: "singleton".to_owned(), inputs: ExperienceInputs { group_id: None }, @@ -20,9 +22,10 @@ fn create_graph() { })); let resources: Vec = vec![experience, place]; - let graph = ResourceGraph::new(&resources); + let _graph = ResourceGraph::new(&resources); } +#[derive(Default)] pub struct ResourceGraph { resources: BTreeMap, } @@ -42,7 +45,19 @@ impl ResourceGraph { } } - fn topological_order(&self) -> anyhow::Result> { + pub fn contains(&self, resource_id: &str) -> bool { + self.resources.contains_key(resource_id) + } + + pub fn get(&self, resource_id: &str) -> Option { + self.resources.get(resource_id).map(|x| Arc::clone(x)) + } + + pub fn insert(&mut self, id: &ResourceId, resource: ResourceRef) { + self.resources.insert(id.to_owned(), resource); + } + + pub fn topological_order(&self) -> anyhow::Result> { let mut dependency_graph: BTreeMap> = self .resources .iter() @@ -96,37 +111,37 @@ impl ResourceGraph { } } - pub async fn evaluate_delete( - &self, - resource: ResourceRef, - context: &mut ResourceManagerContext, - ) -> anyhow::Result<()> { - resource.write().unwrap().delete(context).await?; - // .resource.delete(context).await; - Ok(()) - } + // pub async fn evaluate_delete( + // &self, + // resource: ResourceRef, + // context: &mut ResourceManagerContext, + // ) -> anyhow::Result<()> { + // resource.write().unwrap().delete(context).await?; + // // .resource.delete(context).await; + // Ok(()) + // } - pub async fn evaluate( - &self, - previous_graph: &ResourceGraph, - context: &mut ResourceManagerContext, - ) -> anyhow::Result<()> { - let mut previous_resources = previous_graph.topological_order()?; - previous_resources.reverse(); - for resource in previous_resources { - if self.resources.get(resource.read().unwrap().id()).is_some() { - continue; - } + // pub async fn evaluate( + // &self, + // previous_graph: &ResourceGraph, + // context: &mut ResourceManagerContext, + // ) -> anyhow::Result<()> { + // let mut previous_resources = previous_graph.topological_order()?; + // previous_resources.reverse(); + // for resource in previous_resources { + // if self.resources.get(resource.read().unwrap().id()).is_some() { + // continue; + // } - // TODO: delete - self.evaluate_delete(resource, context); - } + // // TODO: delete + // self.evaluate_delete(resource, context); + // } - let current_resources = self.topological_order()?; - for resource in current_resources { - self.evaluate_delete(resource, context); - } + // let current_resources = self.topological_order()?; + // for resource in current_resources { + // self.evaluate_delete(resource, context); + // } - Ok(()) - } + // Ok(()) + // } } diff --git a/mantle/rbx_mantle/src/resources/experience.rs b/mantle/rbx_mantle/src/resources/experience.rs index f1ee042..dd8e026 100644 --- a/mantle/rbx_mantle/src/resources/experience.rs +++ b/mantle/rbx_mantle/src/resources/experience.rs @@ -2,16 +2,20 @@ use async_trait::async_trait; use derive_resource::Resource; use rbx_api::models::AssetId; +use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; + use super::{ - ManagedResource, Resource, ResourceId, ResourceInputs, ResourceManagerContext, ResourceOutputs, + ManagedResource, Resource, ResourceId, ResourceInputs, ResourceOutputs, UpdateStrategy, WeakResourceRef, }; +#[derive(Debug)] pub struct ExperienceInputs { pub group_id: Option, } impl ResourceInputs for ExperienceInputs {} +#[derive(Debug)] pub enum ExperienceOutputs { Data { asset_id: AssetId, @@ -28,7 +32,7 @@ impl ResourceOutputs for ExperienceOutputs { } } -#[derive(Resource)] +#[derive(Resource, Debug)] pub struct ExperienceResource { pub id: ResourceId, pub inputs: ExperienceInputs, @@ -37,36 +41,26 @@ pub struct ExperienceResource { #[async_trait] impl ManagedResource for ExperienceResource { - // async fn create( - // &mut self, - // context: &mut ResourceManagerContext, - // price: Option, - // ) -> anyhow::Result<()> { - // let CreateExperienceResponse { - // universe_id, - // root_place_id, - // } = context - // .roblox_api - // .create_experience(self.inputs.group_id) - // .await?; - - // self.outputs = Some(Box::new(ExperienceOutputs { - // asset_id: universe_id, - // start_place_id: root_place_id, - // })); + async fn delete(&mut self, _context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()> { + todo!() + } - // Ok(()) - // } + async fn price( + &mut self, + _context: &mut ResourceGraphEvaluatorContext, + ) -> anyhow::Result> { + todo!() + } - // async fn update( - // &mut self, - // context: &mut ResourceManagerContext, - // price: Option, - // ) -> anyhow::Result<()> { - // Ok(()) - // } + async fn create( + &mut self, + _context: &mut ResourceGraphEvaluatorContext, + _price: Option, + ) -> anyhow::Result<()> { + todo!() + } - async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()> { - Ok(()) + fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a> { + UpdateStrategy::Recreate } } diff --git a/mantle/rbx_mantle/src/resources/mod.rs b/mantle/rbx_mantle/src/resources/mod.rs index f811ec5..3cea5d0 100644 --- a/mantle/rbx_mantle/src/resources/mod.rs +++ b/mantle/rbx_mantle/src/resources/mod.rs @@ -1,29 +1,23 @@ use std::{ - path::PathBuf, + fmt::Debug, sync::{Arc, RwLock, Weak}, }; use async_trait::async_trait; -use rbx_api::{models::CreatorType, RobloxApi}; + +use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; pub mod experience; pub mod place; -pub struct ResourceManagerContext { - pub roblox_api: RobloxApi, - pub project_path: PathBuf, - pub payment_source: CreatorType, - pub allow_purchases: bool, -} +// pub enum UpdateStrategy { +// UpdateInPlace, +// Recreate, +// } -pub enum UpdateStrategy { - UpdateInPlace, - Recreate, -} +pub trait ResourceInputs: Debug {} -pub trait ResourceInputs {} - -pub trait ResourceOutputs { +pub trait ResourceOutputs: Debug { fn has_outputs(&self) -> bool; } @@ -32,7 +26,7 @@ pub type ResourceId = String; pub type ResourceRef = Arc>; pub type WeakResourceRef = Weak>; -pub trait Resource { +pub trait Resource: Debug { fn id(&self) -> &str; fn inputs(&self) -> &dyn ResourceInputs; @@ -68,5 +62,28 @@ pub trait ManagedResource: Resource { // price: Option, // ) -> anyhow::Result<()>; - async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()>; + async fn delete(&mut self, context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()>; + + async fn price( + &mut self, + context: &mut ResourceGraphEvaluatorContext, + ) -> anyhow::Result>; + + async fn create( + &mut self, + context: &mut ResourceGraphEvaluatorContext, + price: Option, + ) -> anyhow::Result<()>; + + fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a>; +} + +pub enum UpdateStrategy<'a> { + Update(&'a mut dyn UpdatableResource), + Recreate, +} + +#[async_trait] +pub trait UpdatableResource: ManagedResource { + async fn update(&mut self, context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()>; } diff --git a/mantle/rbx_mantle/src/resources/place.rs b/mantle/rbx_mantle/src/resources/place.rs index 08f5c2c..6ca4512 100644 --- a/mantle/rbx_mantle/src/resources/place.rs +++ b/mantle/rbx_mantle/src/resources/place.rs @@ -4,16 +4,20 @@ use async_trait::async_trait; use derive_resource::Resource; use rbx_api::models::AssetId; +use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; + use super::{ experience::ExperienceResource, ManagedResource, Resource, ResourceId, ResourceInputs, - ResourceManagerContext, ResourceOutputs, WeakResourceRef, + ResourceOutputs, UpdateStrategy, WeakResourceRef, }; +#[derive(Debug)] pub struct PlaceInputs { pub is_start: bool, } impl ResourceInputs for PlaceInputs {} +#[derive(Debug)] pub enum PlaceOutputs { Data { asset_id: AssetId }, Empty, @@ -27,7 +31,7 @@ impl ResourceOutputs for PlaceOutputs { } } -#[derive(Resource)] +#[derive(Resource, Debug)] pub struct PlaceResource { pub id: ResourceId, pub inputs: PlaceInputs, @@ -39,36 +43,26 @@ pub struct PlaceResource { #[async_trait] impl ManagedResource for PlaceResource { - // async fn create( - // &mut self, - // context: &mut ResourceManagerContext, - // price: Option, - // ) -> anyhow::Result<()> { - // let CreateExperienceResponse { - // universe_id, - // root_place_id, - // } = context - // .roblox_api - // .create_experience(self.inputs.group_id) - // .await?; - - // self.outputs = Some(Box::new(ExperienceOutputs { - // asset_id: universe_id, - // start_place_id: root_place_id, - // })); + async fn delete(&mut self, _context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()> { + todo!() + } - // Ok(()) - // } + async fn price( + &mut self, + _context: &mut ResourceGraphEvaluatorContext, + ) -> anyhow::Result> { + todo!() + } - // async fn update( - // &mut self, - // context: &mut ResourceManagerContext, - // price: Option, - // ) -> anyhow::Result<()> { - // Ok(()) - // } + async fn create( + &mut self, + _context: &mut ResourceGraphEvaluatorContext, + _price: Option, + ) -> anyhow::Result<()> { + todo!() + } - async fn delete(&mut self, context: &mut ResourceManagerContext) -> anyhow::Result<()> { - Ok(()) + fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a> { + UpdateStrategy::Recreate } } From e86dd524b28de0e01c2ff23cb53ad2d218522ddb Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Wed, 22 Feb 2023 09:33:25 -0600 Subject: [PATCH 05/13] fiddle with enum dispatcher --- mantle/Cargo.lock | 13 +++++++++++++ mantle/rbx_mantle/Cargo.toml | 1 + .../src/resource_graph_v2/evaluator.rs | 13 +++++++++++-- mantle/rbx_mantle/src/resources/mod.rs | 17 +++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/mantle/Cargo.lock b/mantle/Cargo.lock index 0e38691..2cb2610 100644 --- a/mantle/Cargo.lock +++ b/mantle/Cargo.lock @@ -597,6 +597,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum_dispatch" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f36e95862220b211a6e2aa5eca09b4fa391b13cd52ceb8035a24bf65a79de2" +dependencies = [ + "once_cell", + "proc-macro2 1.0.50", + "quote 1.0.23", + "syn 1.0.107", +] + [[package]] name = "env_logger" version = "0.9.0" @@ -2056,6 +2068,7 @@ dependencies = [ "clap", "derive_resource", "difference", + "enum_dispatch", "glob", "log", "logger", diff --git a/mantle/rbx_mantle/Cargo.toml b/mantle/rbx_mantle/Cargo.toml index 0c66a29..e0839b7 100644 --- a/mantle/rbx_mantle/Cargo.toml +++ b/mantle/rbx_mantle/Cargo.toml @@ -34,3 +34,4 @@ url = { version = "2.2.2", features = ["serde"] } log = "0.4.14" schemars = { version = "=0.8.8-blake.2", git = "https://github.com/blake-mealey/schemars", branch = "raw-comments", features = ["derive", "url", "preserve_order"] } anyhow = "1.0.68" +enum_dispatch = "0.3.11" diff --git a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs index 2df16d8..ba32f2b 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs @@ -3,9 +3,8 @@ use std::path::PathBuf; use rbx_api::{models::CreatorType, RobloxApi}; use rbx_auth::RobloxAuth; -use crate::resources::{ResourceId, ResourceRef}; - use super::ResourceGraph; +use crate::resources::{ResourceId, ResourceRef, UpdateStrategy}; pub struct ResourceGraphEvaluatorOptions { pub project_path: PathBuf, @@ -259,6 +258,16 @@ impl<'a> ResouceGraphEvaluator<'a> { let mut resource_write_ref = resource.write().unwrap(); let resource_id = resource_write_ref.id().to_owned(); + // match resource.write().unwrap().update_strategy() { + // UpdateStrategy::Recreate => { + // self.create_resource(resource.clone()).await; + // return; + // } + // UpdateStrategy::Update(updatable_resource) => { + // updatable_resource.update(context); + // } + // } + println!("Updating: {}", resource_id); } diff --git a/mantle/rbx_mantle/src/resources/mod.rs b/mantle/rbx_mantle/src/resources/mod.rs index 3cea5d0..eb63a54 100644 --- a/mantle/rbx_mantle/src/resources/mod.rs +++ b/mantle/rbx_mantle/src/resources/mod.rs @@ -4,7 +4,9 @@ use std::{ }; use async_trait::async_trait; +use enum_dispatch::enum_dispatch; +use self::{experience::ExperienceResource, place::PlaceResource}; use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; pub mod experience; @@ -26,6 +28,7 @@ pub type ResourceId = String; pub type ResourceRef = Arc>; pub type WeakResourceRef = Weak>; +#[enum_dispatch] pub trait Resource: Debug { fn id(&self) -> &str; @@ -34,9 +37,12 @@ pub trait Resource: Debug { fn outputs(&self) -> &dyn ResourceOutputs; fn dependencies(&self) -> Vec; + + // TODO: return simple update strategy enum here } #[async_trait] +#[enum_dispatch] pub trait ManagedResource: Resource { // async fn creation_price( // &self, @@ -78,12 +84,23 @@ pub trait ManagedResource: Resource { fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a>; } +// TODO: simplify - just implement a noop update method for resources that use the recreate strategy pub enum UpdateStrategy<'a> { Update(&'a mut dyn UpdatableResource), Recreate, } #[async_trait] +#[enum_dispatch] pub trait UpdatableResource: ManagedResource { async fn update(&mut self, context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()>; } + +#[enum_dispatch(Resource, ManagedResource)] +#[derive(Debug)] +pub enum ResourceDispatch { + PlaceResource, + ExperienceResource, +} + +fn x(r: ResourceDispatch) {} From 5141e130956be0ee49cc2b2bba8ab64608ca80fc Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 15:27:05 -0500 Subject: [PATCH 06/13] new iteration --- mantle/rbx_mantle/src/lib.rs | 6 +- .../src/resource_graph_v2/evaluator.rs | 6 +- .../src/resource_graph_v3/evaluator.rs | 280 ++++++++++++++++++ .../resource_graph_v3/evaluator_results.rs | 124 ++++++++ .../rbx_mantle/src/resource_graph_v3/mod.rs | 91 ++++++ .../rbx_mantle/src/resources_v2/experience.rs | 38 +++ mantle/rbx_mantle/src/resources_v2/mod.rs | 96 ++++++ mantle/rbx_mantle/src/resources_v2/notes.txt | 113 +++++++ mantle/rbx_mantle/src/resources_v2/place.rs | 43 +++ 9 files changed, 791 insertions(+), 6 deletions(-) create mode 100644 mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs create mode 100644 mantle/rbx_mantle/src/resource_graph_v3/evaluator_results.rs create mode 100644 mantle/rbx_mantle/src/resource_graph_v3/mod.rs create mode 100644 mantle/rbx_mantle/src/resources_v2/experience.rs create mode 100644 mantle/rbx_mantle/src/resources_v2/mod.rs create mode 100644 mantle/rbx_mantle/src/resources_v2/notes.txt create mode 100644 mantle/rbx_mantle/src/resources_v2/place.rs diff --git a/mantle/rbx_mantle/src/lib.rs b/mantle/rbx_mantle/src/lib.rs index c059998..6825e93 100644 --- a/mantle/rbx_mantle/src/lib.rs +++ b/mantle/rbx_mantle/src/lib.rs @@ -1,7 +1,9 @@ pub mod config; pub mod project; pub mod resource_graph; -pub mod resource_graph_v2; -pub mod resources; +// pub mod resource_graph_v2; +pub mod resource_graph_v3; +// pub mod resources; +pub mod resources_v2; pub mod roblox_resource_manager; pub mod state; diff --git a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs index ba32f2b..d532b6c 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs @@ -163,13 +163,13 @@ impl<'a> ResouceGraphEvaluator<'a> { panic!("Cannot use a graph evaluator more than once"); } - self.delete_removed_resources().await?; + self.delete_removed_resources().await; self.create_or_update_added_or_changed_resources().await?; Ok((&self.results, &self.next_graph)) } - async fn delete_removed_resources(&mut self) -> anyhow::Result<()> { + async fn delete_removed_resources(&mut self) { let mut previous_resources = self.previous_graph.topological_order()?; // Iterate over previous resources in reverse order so that leaf resources are removed first @@ -201,8 +201,6 @@ impl<'a> ResouceGraphEvaluator<'a> { } } } - - Ok(()) } async fn create_resource(&mut self, resource: ResourceRef) { diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs new file mode 100644 index 0000000..9a089ab --- /dev/null +++ b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs @@ -0,0 +1,280 @@ +use super::{evaluator_results::EvaluatorResults, ResourceGraph}; + +pub struct Evaluator<'a> { + previous_graph: &'a ResourceGraph, + desired_graph: &'a ResourceGraph, + next_graph: ResourceGraph, + + results: EvaluatorResults, +} + +impl<'a> Evaluator<'a> { + pub fn new(previous_graph: &'a ResourceGraph, desired_graph: &'a ResourceGraph) -> Self { + Self { + previous_graph, + desired_graph, + next_graph: ResourceGraph::default(), + results: EvaluatorResults::default(), + } + } + + pub async fn evaluate( + &'a mut self, + ) -> anyhow::Result<(&'a EvaluatorResults, &'a ResourceGraph)> { + if !self.results.is_empty() { + return anyhow::Result::Err(anyhow::Error::msg( + "A graph evaluator can only be used once.", + )); + } + + self.delete_removed_resources().await?; + self.create_or_update_added_or_changed_resources().await?; + + Ok((&self.results, &self.next_graph)) + } + + async fn delete_removed_resources(&mut self) -> anyhow::Result<()> { + let mut previous_resources = self.previous_graph.topological_order()?; + previous_resources.reverse(); + + for resource in previous_resources.into_iter() { + if self.desired_graph.contains(resource.id()) { + continue; + } + + println!("Deleting: {}", resource.id()); + + let mut next_resource = resource.clone(); + + match next_resource.delete().await { + Ok(()) => self.results.delete_succeeded(resource.id()), + Err(error) => { + self.results.delete_failed(resource.id(), error); + self.next_graph.insert(next_resource); + } + } + } + + Ok(()) + } + + async fn create_or_update_added_or_changed_resources(&mut self) -> anyhow::Result<()> { + let desired_resources = self.desired_graph.topological_order()?; + + for desired_resource in desired_resources.into_iter() { + if let Some(previous_resource) = self.previous_graph.get(desired_resource.id()) { + match desired_resource.next(&self.next_graph) { + Ok(mut next_resource) => { + if *previous_resource == next_resource { + self.results.noop(next_resource.id()); + self.next_graph.insert(next_resource); + } else { + match next_resource.update().await { + Ok(()) => { + self.results.update_succeeded(next_resource.id()); + self.next_graph.insert(next_resource); + } + Err(error) => { + self.results.update_failed(next_resource.id(), error); + self.next_graph.insert(previous_resource.clone()); + } + } + } + } + Err(error) => self.results.update_failed(desired_resource.id(), error), + } + } else { + match desired_resource.next(&self.next_graph) { + Ok(mut next_resource) => match next_resource.create().await { + Ok(()) => { + self.results.create_succeeded(next_resource.id()); + self.next_graph.insert(next_resource); + } + Err(error) => { + self.results.create_failed(next_resource.id(), error); + } + }, + Err(error) => self.results.create_failed(desired_resource.id(), error), + } + } + } + + Ok(()) + } +} + +#[cfg(test)] +pub mod tests { + use crate::{ + resource_graph_v3::{ + evaluator::Evaluator, + evaluator_results::{ + EvaluatorResults, OperationResult, OperationStatus, OperationType, + }, + ResourceGraph, + }, + resources_v2::{ + experience::{ExperienceInputs, ExperienceResource}, + place::{PlaceInputs, PlaceResource}, + Resource, + }, + }; + + #[tokio::test] + pub async fn create_resources() { + let mut desired_graph = ResourceGraph::default(); + let desired_experience = ExperienceResource { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let desired_start_place = PlaceResource { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: desired_experience.clone(), + }; + let desired_other_place = PlaceResource { + id: "place_other".to_owned(), + inputs: PlaceInputs { is_start: false }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(Resource::Place(desired_start_place)); + desired_graph.insert(Resource::Place(desired_other_place)); + desired_graph.insert(Resource::Experience(desired_experience)); + + let previous_graph = ResourceGraph::default(); + + let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); + let (results, _next_graph) = evaluator.evaluate().await.unwrap(); + + assert_eq!( + *results, + EvaluatorResults { + operation_results: vec![ + OperationResult { + resource_id: "experience_singleton".to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_start".to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success + } + ] + } + ); + } + + #[tokio::test] + pub async fn update_resources_noop() { + let mut previous_graph = ResourceGraph::default(); + let previous_experience = ExperienceResource { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let previous_start_place = PlaceResource { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: previous_experience.clone(), + }; + previous_graph.insert(Resource::Place(previous_start_place)); + previous_graph.insert(Resource::Experience(previous_experience)); + + let mut desired_graph = ResourceGraph::default(); + let desired_experience = ExperienceResource { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let desired_start_place = PlaceResource { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(Resource::Place(desired_start_place)); + desired_graph.insert(Resource::Experience(desired_experience)); + + let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); + let (results, _next_graph) = evaluator.evaluate().await.unwrap(); + + assert_eq!( + *results, + EvaluatorResults { + operation_results: vec![ + OperationResult { + resource_id: "experience_singleton".to_owned(), + operation_type: OperationType::Noop, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_start".to_owned(), + operation_type: OperationType::Noop, + status: OperationStatus::Success + } + ] + } + ); + } + + #[tokio::test] + pub async fn update_resources_changes() { + let mut previous_graph = ResourceGraph::default(); + let previous_experience = ExperienceResource { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let previous_start_place = PlaceResource { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: previous_experience.clone(), + }; + previous_graph.insert(Resource::Place(previous_start_place)); + previous_graph.insert(Resource::Experience(previous_experience)); + + let mut desired_graph = ResourceGraph::default(); + let desired_experience = ExperienceResource { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { + group_id: Some(123), + }, + outputs: None, + }; + let desired_start_place = PlaceResource { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(Resource::Place(desired_start_place)); + desired_graph.insert(Resource::Experience(desired_experience)); + + let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); + let (results, _next_graph) = evaluator.evaluate().await.unwrap(); + + assert_eq!( + *results, + EvaluatorResults { + operation_results: vec![ + OperationResult { + resource_id: "experience_singleton".to_owned(), + operation_type: OperationType::Update, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_start".to_owned(), + operation_type: OperationType::Update, + status: OperationStatus::Success + } + ] + } + ); + } +} diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator_results.rs b/mantle/rbx_mantle/src/resource_graph_v3/evaluator_results.rs new file mode 100644 index 0000000..4313463 --- /dev/null +++ b/mantle/rbx_mantle/src/resource_graph_v3/evaluator_results.rs @@ -0,0 +1,124 @@ +#[derive(Debug, PartialEq)] +pub enum SkipReason { + PurchasesNotAllowed, +} + +#[derive(Debug, PartialEq)] +pub enum OperationType { + Create, + Update, + Recreate, + Delete, + Noop, + Skip(SkipReason), +} + +#[derive(Debug)] +pub enum OperationStatus { + Success, + Failure(anyhow::Error), +} +// Ignores error messages +impl PartialEq for OperationStatus { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Success, Self::Success) => true, + (Self::Failure(_), Self::Failure(_)) => true, + _ => false, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct OperationResult { + pub resource_id: String, + pub operation_type: OperationType, + pub status: OperationStatus, +} + +#[derive(Default, Debug, PartialEq)] +pub struct EvaluatorResults { + pub operation_results: Vec, +} + +impl EvaluatorResults { + pub fn is_empty(&self) -> bool { + self.operation_results.is_empty() + } + + pub fn create_succeeded(&mut self, resource_id: &str) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success, + }) + } + pub fn create_failed(&mut self, resource_id: &str, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Failure(error), + }) + } + + pub fn update_succeeded(&mut self, resource_id: &str) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Update, + status: OperationStatus::Success, + }) + } + pub fn update_failed(&mut self, resource_id: &str, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Update, + status: OperationStatus::Failure(error), + }) + } + + pub fn recreate_succeeded(&mut self, resource_id: &str) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Recreate, + status: OperationStatus::Success, + }) + } + pub fn recreate_failed(&mut self, resource_id: &str, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Recreate, + status: OperationStatus::Failure(error), + }) + } + + pub fn delete_succeeded(&mut self, resource_id: &str) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Delete, + status: OperationStatus::Success, + }) + } + pub fn delete_failed(&mut self, resource_id: &str, error: anyhow::Error) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Delete, + status: OperationStatus::Failure(error), + }) + } + + pub fn noop(&mut self, resource_id: &str) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Noop, + status: OperationStatus::Success, + }) + } + + pub fn skip(&mut self, resource_id: &str, reason: SkipReason) { + self.operation_results.push(OperationResult { + resource_id: resource_id.to_owned(), + operation_type: OperationType::Skip(reason), + status: OperationStatus::Success, + }) + } +} diff --git a/mantle/rbx_mantle/src/resource_graph_v3/mod.rs b/mantle/rbx_mantle/src/resource_graph_v3/mod.rs new file mode 100644 index 0000000..6deaf2c --- /dev/null +++ b/mantle/rbx_mantle/src/resource_graph_v3/mod.rs @@ -0,0 +1,91 @@ +pub mod evaluator; +pub mod evaluator_results; + +use std::collections::BTreeMap; + +use crate::resources_v2::Resource; + +#[derive(Debug)] +pub struct ResourceGraph { + resources: BTreeMap, +} + +impl ResourceGraph { + pub fn new(resources: Vec) -> Self { + Self { + resources: resources + .into_iter() + .map(|resource| (resource.id().to_owned(), resource)) + .collect(), + } + } + + pub fn default() -> Self { + Self::new(vec![]) + } + + pub fn contains(&self, resource_id: &str) -> bool { + self.resources.contains_key(resource_id) + } + + pub fn get(&self, resource_id: &str) -> Option<&Resource> { + self.resources.get(resource_id) + } + + pub fn insert(&mut self, resource: Resource) { + self.resources.insert(resource.id().to_owned(), resource); + } + + // TODO: Can we make this less clone-y? Can we use actual resource references? + pub fn topological_order(&self) -> anyhow::Result> { + let mut dependency_graph: BTreeMap> = self + .resources + .iter() + .map(|(id, resource)| { + ( + id.clone(), + resource + .dependency_ids() + .iter() + .map(|d| d.to_owned().to_owned()) + .collect(), + ) + }) + .collect(); + + let mut start_nodes: Vec = dependency_graph + .iter() + .filter_map(|(node, deps)| { + if deps.is_empty() { + Some(node.clone()) + } else { + None + } + }) + .collect(); + + let mut ordered: Vec = Vec::new(); + while let Some(start_node) = start_nodes.pop() { + ordered.push(start_node.clone()); + for (node, deps) in dependency_graph.iter_mut() { + if deps.iter().any(|dep| *dep == start_node) { + deps.retain(|dep| *dep != start_node); + if deps.is_empty() { + start_nodes.push(node.clone()); + } + } + } + } + + let has_cycles = dependency_graph.iter().any(|(_, deps)| !deps.is_empty()); + match has_cycles { + true => Err(anyhow::Error::msg( + "Cannot evaluate resource graph because it has cycles", + )), + false => Ok(ordered + .iter() + .map(|id| self.resources.get(id).unwrap()) + .collect()), + } + } +} diff --git a/mantle/rbx_mantle/src/resources_v2/experience.rs b/mantle/rbx_mantle/src/resources_v2/experience.rs new file mode 100644 index 0000000..f110407 --- /dev/null +++ b/mantle/rbx_mantle/src/resources_v2/experience.rs @@ -0,0 +1,38 @@ +use async_trait::async_trait; +use rbx_api::models::AssetId; + +use super::ManagedResource; + +#[derive(Debug, Clone, PartialEq)] +pub struct ExperienceInputs { + pub group_id: Option, +} +#[derive(Debug, Clone, PartialEq)] +pub struct ExperienceOutputs { + pub asset_id: AssetId, + pub start_place_id: AssetId, +} +#[derive(Debug, Clone, PartialEq)] +pub struct ExperienceResource { + pub id: String, + pub inputs: ExperienceInputs, + pub outputs: Option, +} + +#[async_trait] +impl ManagedResource for ExperienceResource { + async fn create(&mut self) -> anyhow::Result<()> { + self.outputs = Some(ExperienceOutputs { + asset_id: 1, + start_place_id: 2, + }); + Ok(()) + } + async fn update(&mut self) -> anyhow::Result<()> { + Ok(()) + } + async fn delete(&mut self) -> anyhow::Result<()> { + self.outputs = None; + Ok(()) + } +} diff --git a/mantle/rbx_mantle/src/resources_v2/mod.rs b/mantle/rbx_mantle/src/resources_v2/mod.rs new file mode 100644 index 0000000..1a55dcc --- /dev/null +++ b/mantle/rbx_mantle/src/resources_v2/mod.rs @@ -0,0 +1,96 @@ +pub mod experience; +pub mod place; + +use std::{fmt::Debug, vec}; + +use async_trait::async_trait; + +use crate::resource_graph_v3::ResourceGraph; + +use self::{experience::ExperienceResource, place::PlaceResource}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Resource { + Experience(ExperienceResource), + Place(PlaceResource), +} + +impl Resource { + pub fn id(&self) -> &str { + match self { + Self::Experience(resource) => &resource.id, + Self::Place(resource) => &resource.id, + } + } + + pub fn inputs(&self) -> &dyn Debug { + match self { + Self::Experience(resource) => &resource.inputs, + Self::Place(resource) => &resource.inputs, + } + } + + pub fn outputs(&self) -> &dyn Debug { + match self { + Self::Experience(resource) => &resource.outputs, + Self::Place(resource) => &resource.outputs, + } + } + + pub fn dependency_ids(&self) -> Vec<&str> { + match self { + Self::Experience(_resource) => vec![], + Self::Place(resource) => vec![&resource.experience.id], + } + } + + pub fn next(&self, graph: &ResourceGraph) -> anyhow::Result { + match self { + Self::Experience(resource) => Ok(Self::Experience(ExperienceResource { + id: resource.id.clone(), + inputs: resource.inputs.clone(), + outputs: resource.outputs.clone(), + })), + Self::Place(resource) => Ok(Self::Place(PlaceResource { + id: resource.id.clone(), + inputs: resource.inputs.clone(), + outputs: resource.outputs.clone(), + experience: match graph + .get(&resource.experience.id) + .ok_or(anyhow::Error::msg("Unable to find resource"))? + { + Resource::Experience(experience) => experience.clone(), + _ => return anyhow::Result::Err(anyhow::Error::msg("Expected 'experience'")), + }, + })), + } + } + + pub async fn create(&mut self) -> anyhow::Result<()> { + match self { + Self::Experience(resource) => resource.create().await, + Self::Place(resource) => resource.create().await, + } + } + + pub async fn update(&mut self) -> anyhow::Result<()> { + match self { + Self::Experience(resource) => resource.update().await, + Self::Place(resource) => resource.update().await, + } + } + + pub async fn delete(&mut self) -> anyhow::Result<()> { + match self { + Self::Experience(resource) => resource.delete().await, + Self::Place(resource) => resource.delete().await, + } + } +} + +#[async_trait] +pub trait ManagedResource { + async fn create(&mut self) -> anyhow::Result<()>; + async fn update(&mut self) -> anyhow::Result<()>; + async fn delete(&mut self) -> anyhow::Result<()>; +} diff --git a/mantle/rbx_mantle/src/resources_v2/notes.txt b/mantle/rbx_mantle/src/resources_v2/notes.txt new file mode 100644 index 0000000..6b8ab82 --- /dev/null +++ b/mantle/rbx_mantle/src/resources_v2/notes.txt @@ -0,0 +1,113 @@ +current_graph + initially: empty + later: graph containing resources with inputs, outputs, and dependencies +desired_graph + graph containing resources with inputs, outputs, and dependencies +next_graph + graph containing resources with inputs, outputs, and dependencies + +should resources be mutable?? + we should leave resources as-is within each graph and build up each graph independently + delete: + experience + place + + experience has dependents (place) - cannot be deleted yet + place has no dependents + delete place + place is no longer a dependent of experience - delete it + + this is achieved simply by deleting in reverse topological order + + when deleting; + call delete function + don't add to next_graph + + create: + experience + place + + place has dependencies (experience) - cannot be created yet + experience has no dependencies + create experience + place has created experiences - create it + + this is achieved simply by creating in topological order + + when creating: + call create function + add to next_graph: resource with the same inputs and dependencies, but with the outputs from the creation + !!we can reference dependencies within the same graph!! + dependency references are read-only (in fact can be written as copies!) + and.. resource actions can be immutable! + + update: + when updating (update-in-place strategy): + call update function + add to next_graph: resource with the same inputs and dependencies, but with the outputs from the update + + when updating (recreate strategy): + call delete function + call create function + add to next_graph: resource with the same inputs and dependencies, but with the outputs from the creation + +also: using enums will be easier (but introduces duplication... which can be solved with macros!) + + + + +Experience { + inputs: ExperienceInputs + outputs: Option + dependencies: [] +} + +Place { + inputs: PlaceInputs + outputs: Option + dependencies: [Experience] +} + +current_graph: + empty + +desired_graph: + ex = Experience(inputs, None, []) + insert(ex) + insert(Place(inputs, None, [ex])) + +next_graph: + nothing to delete + create experience: + let mut next_ex = Experience(ex.inputs, None, next_graph.get_deps(ex.deps)) + next_ex.create() // populates outputs + next_graph.insert(next_ex) + create place: + let mut next_place = Place(pl.inputs, None, next_graph.get_deps(pl.deps)) + next_place.create() // populates outputs + next_graph.insert(next_place) + + +later: + delete place: + let mut next_place = Place(pl.inputs, pl.outputs, next_graph.get_deps(pl.deps)) + next_place.delete() // deletes outputs (not important) + // don't add to next_graph + update place: + let mut next_place = Place(pl.inputs, pl.outputs, next_graph.get_deps(pl.deps)) + next_place.update() // populates new outputs + next_graph.insert(next_place) + recreate place: + let mut next_place = Place(pl.inputs, pl.outputs, next_graph.get_deps(pl.deps)) + next_place.delete() // deletes outputs + next_place.create() // populates new outputs + next_graph.insert(next_place) + +WAIT... can we just... CLONE it? And then manually SET deps? (i.e. a set_dependencies()) + +NEED: + way to load deps from next_graph + pretty easy? + way to construct resource + not so sure... + gonna need some macros I think diff --git a/mantle/rbx_mantle/src/resources_v2/place.rs b/mantle/rbx_mantle/src/resources_v2/place.rs new file mode 100644 index 0000000..c162fe2 --- /dev/null +++ b/mantle/rbx_mantle/src/resources_v2/place.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; +use rbx_api::models::AssetId; + +use super::{experience::ExperienceResource, ManagedResource}; + +#[derive(Debug, Clone, PartialEq)] +pub struct PlaceInputs { + pub is_start: bool, +} +#[derive(Debug, Clone, PartialEq)] +pub struct PlaceOutputs { + pub asset_id: AssetId, +} +#[derive(Debug, Clone, PartialEq)] +pub struct PlaceResource { + pub id: String, + pub inputs: PlaceInputs, + pub outputs: Option, + + //#[dependency] + pub experience: ExperienceResource, +} + +#[async_trait] +impl ManagedResource for PlaceResource { + async fn create(&mut self) -> anyhow::Result<()> { + if self.inputs.is_start { + self.outputs = Some(PlaceOutputs { + asset_id: self.experience.outputs.as_ref().unwrap().start_place_id, + }) + } else { + self.outputs = Some(PlaceOutputs { asset_id: 3 }); + } + Ok(()) + } + async fn update(&mut self) -> anyhow::Result<()> { + Ok(()) + } + async fn delete(&mut self) -> anyhow::Result<()> { + self.outputs = None; + Ok(()) + } +} From 49e070aa925e4bf12c6d57f05910c26cf19e48dd Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 15:38:56 -0500 Subject: [PATCH 07/13] pretty assertions --- mantle/Cargo.lock | 7 ++++--- mantle/rbx_mantle/Cargo.toml | 3 +++ mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs | 6 ++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mantle/Cargo.lock b/mantle/Cargo.lock index 2cb2610..9714281 100644 --- a/mantle/Cargo.lock +++ b/mantle/Cargo.lock @@ -1775,14 +1775,14 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" dependencies = [ - "ansi_term", "ctor", "diff", "output_vt100", + "yansi", ] [[package]] @@ -2072,6 +2072,7 @@ dependencies = [ "glob", "log", "logger", + "pretty_assertions", "rbx_api", "rbx_auth", "rusoto_core", diff --git a/mantle/rbx_mantle/Cargo.toml b/mantle/rbx_mantle/Cargo.toml index e0839b7..55cfa88 100644 --- a/mantle/rbx_mantle/Cargo.toml +++ b/mantle/rbx_mantle/Cargo.toml @@ -35,3 +35,6 @@ log = "0.4.14" schemars = { version = "=0.8.8-blake.2", git = "https://github.com/blake-mealey/schemars", branch = "raw-comments", features = ["derive", "url", "preserve_order"] } anyhow = "1.0.68" enum_dispatch = "0.3.11" + +[dev-dependencies] +pretty_assertions = "1.3.0" diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs index 9a089ab..440785e 100644 --- a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs @@ -119,6 +119,7 @@ pub mod tests { Resource, }, }; + use pretty_assertions::assert_eq; #[tokio::test] pub async fn create_resources() { @@ -162,6 +163,11 @@ pub mod tests { resource_id: "place_start".to_owned(), operation_type: OperationType::Create, status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_other".to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success } ] } From 2ead398c167deda3c17f1b2ae4b463f7d93e3ad9 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 16:06:52 -0500 Subject: [PATCH 08/13] outputs come from previous graph --- .../src/resource_graph_v3/evaluator.rs | 39 +++++++------- .../rbx_mantle/src/resources_v2/experience.rs | 4 +- mantle/rbx_mantle/src/resources_v2/mod.rs | 51 ++++++++++++------- mantle/rbx_mantle/src/resources_v2/place.rs | 8 +-- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs index 440785e..b3e5f2e 100644 --- a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs @@ -63,7 +63,7 @@ impl<'a> Evaluator<'a> { for desired_resource in desired_resources.into_iter() { if let Some(previous_resource) = self.previous_graph.get(desired_resource.id()) { - match desired_resource.next(&self.next_graph) { + match desired_resource.next(&self.previous_graph, &self.next_graph) { Ok(mut next_resource) => { if *previous_resource == next_resource { self.results.noop(next_resource.id()); @@ -84,7 +84,7 @@ impl<'a> Evaluator<'a> { Err(error) => self.results.update_failed(desired_resource.id(), error), } } else { - match desired_resource.next(&self.next_graph) { + match desired_resource.next(&self.previous_graph, &self.next_graph) { Ok(mut next_resource) => match next_resource.create().await { Ok(()) => { self.results.create_succeeded(next_resource.id()); @@ -114,8 +114,8 @@ pub mod tests { ResourceGraph, }, resources_v2::{ - experience::{ExperienceInputs, ExperienceResource}, - place::{PlaceInputs, PlaceResource}, + experience::{Experience, ExperienceInputs, ExperienceOutputs}, + place::{Place, PlaceInputs, PlaceOutputs}, Resource, }, }; @@ -124,24 +124,24 @@ pub mod tests { #[tokio::test] pub async fn create_resources() { let mut desired_graph = ResourceGraph::default(); - let desired_experience = ExperienceResource { + let desired_experience = Experience { id: "experience_singleton".to_owned(), inputs: ExperienceInputs { group_id: None }, outputs: None, }; - let desired_start_place = PlaceResource { + let desired_start_place = Place { id: "place_start".to_owned(), inputs: PlaceInputs { is_start: true }, outputs: None, experience: desired_experience.clone(), }; - let desired_other_place = PlaceResource { + desired_graph.insert(Resource::Place(desired_start_place)); + let desired_other_place = Place { id: "place_other".to_owned(), inputs: PlaceInputs { is_start: false }, outputs: None, experience: desired_experience.clone(), }; - desired_graph.insert(Resource::Place(desired_start_place)); desired_graph.insert(Resource::Place(desired_other_place)); desired_graph.insert(Resource::Experience(desired_experience)); @@ -177,27 +177,30 @@ pub mod tests { #[tokio::test] pub async fn update_resources_noop() { let mut previous_graph = ResourceGraph::default(); - let previous_experience = ExperienceResource { + let previous_experience = Experience { id: "experience_singleton".to_owned(), inputs: ExperienceInputs { group_id: None }, - outputs: None, + outputs: Some(ExperienceOutputs { + asset_id: 1, + start_place_id: 2, + }), }; - let previous_start_place = PlaceResource { + let previous_start_place = Place { id: "place_start".to_owned(), inputs: PlaceInputs { is_start: true }, - outputs: None, + outputs: Some(PlaceOutputs { asset_id: 2 }), experience: previous_experience.clone(), }; previous_graph.insert(Resource::Place(previous_start_place)); previous_graph.insert(Resource::Experience(previous_experience)); let mut desired_graph = ResourceGraph::default(); - let desired_experience = ExperienceResource { + let desired_experience = Experience { id: "experience_singleton".to_owned(), inputs: ExperienceInputs { group_id: None }, outputs: None, }; - let desired_start_place = PlaceResource { + let desired_start_place = Place { id: "place_start".to_owned(), inputs: PlaceInputs { is_start: true }, outputs: None, @@ -231,12 +234,12 @@ pub mod tests { #[tokio::test] pub async fn update_resources_changes() { let mut previous_graph = ResourceGraph::default(); - let previous_experience = ExperienceResource { + let previous_experience = Experience { id: "experience_singleton".to_owned(), inputs: ExperienceInputs { group_id: None }, outputs: None, }; - let previous_start_place = PlaceResource { + let previous_start_place = Place { id: "place_start".to_owned(), inputs: PlaceInputs { is_start: true }, outputs: None, @@ -246,14 +249,14 @@ pub mod tests { previous_graph.insert(Resource::Experience(previous_experience)); let mut desired_graph = ResourceGraph::default(); - let desired_experience = ExperienceResource { + let desired_experience = Experience { id: "experience_singleton".to_owned(), inputs: ExperienceInputs { group_id: Some(123), }, outputs: None, }; - let desired_start_place = PlaceResource { + let desired_start_place = Place { id: "place_start".to_owned(), inputs: PlaceInputs { is_start: true }, outputs: None, diff --git a/mantle/rbx_mantle/src/resources_v2/experience.rs b/mantle/rbx_mantle/src/resources_v2/experience.rs index f110407..e1a7ed1 100644 --- a/mantle/rbx_mantle/src/resources_v2/experience.rs +++ b/mantle/rbx_mantle/src/resources_v2/experience.rs @@ -13,14 +13,14 @@ pub struct ExperienceOutputs { pub start_place_id: AssetId, } #[derive(Debug, Clone, PartialEq)] -pub struct ExperienceResource { +pub struct Experience { pub id: String, pub inputs: ExperienceInputs, pub outputs: Option, } #[async_trait] -impl ManagedResource for ExperienceResource { +impl ManagedResource for Experience { async fn create(&mut self) -> anyhow::Result<()> { self.outputs = Some(ExperienceOutputs { asset_id: 1, diff --git a/mantle/rbx_mantle/src/resources_v2/mod.rs b/mantle/rbx_mantle/src/resources_v2/mod.rs index 1a55dcc..bd67f88 100644 --- a/mantle/rbx_mantle/src/resources_v2/mod.rs +++ b/mantle/rbx_mantle/src/resources_v2/mod.rs @@ -7,12 +7,12 @@ use async_trait::async_trait; use crate::resource_graph_v3::ResourceGraph; -use self::{experience::ExperienceResource, place::PlaceResource}; +use self::{experience::Experience, place::Place}; #[derive(Debug, Clone, PartialEq)] pub enum Resource { - Experience(ExperienceResource), - Place(PlaceResource), + Experience(Experience), + Place(Place), } impl Resource { @@ -23,17 +23,10 @@ impl Resource { } } - pub fn inputs(&self) -> &dyn Debug { + pub fn has_outputs(&self) -> bool { match self { - Self::Experience(resource) => &resource.inputs, - Self::Place(resource) => &resource.inputs, - } - } - - pub fn outputs(&self) -> &dyn Debug { - match self { - Self::Experience(resource) => &resource.outputs, - Self::Place(resource) => &resource.outputs, + Self::Experience(resource) => resource.outputs.is_some(), + Self::Place(resource) => resource.outputs.is_some(), } } @@ -44,18 +37,38 @@ impl Resource { } } - pub fn next(&self, graph: &ResourceGraph) -> anyhow::Result { + pub fn next( + &self, + previous_graph: &ResourceGraph, + next_graph: &ResourceGraph, + ) -> anyhow::Result { match self { - Self::Experience(resource) => Ok(Self::Experience(ExperienceResource { + Self::Experience(resource) => Ok(Self::Experience(Experience { id: resource.id.clone(), inputs: resource.inputs.clone(), - outputs: resource.outputs.clone(), + outputs: match previous_graph.get(&resource.id) { + Some(previous_resource) => match previous_resource { + Resource::Experience(previous_resource) => { + previous_resource.outputs.clone() + } + _ => { + return anyhow::Result::Err(anyhow::Error::msg("Expected 'experience'")) + } + }, + _ => None, + }, })), - Self::Place(resource) => Ok(Self::Place(PlaceResource { + Self::Place(resource) => Ok(Self::Place(Place { id: resource.id.clone(), inputs: resource.inputs.clone(), - outputs: resource.outputs.clone(), - experience: match graph + outputs: match previous_graph.get(&resource.id) { + Some(previous_resource) => match previous_resource { + Resource::Place(previous_resource) => previous_resource.outputs.clone(), + _ => return anyhow::Result::Err(anyhow::Error::msg("Expected 'place'")), + }, + _ => None, + }, + experience: match next_graph .get(&resource.experience.id) .ok_or(anyhow::Error::msg("Unable to find resource"))? { diff --git a/mantle/rbx_mantle/src/resources_v2/place.rs b/mantle/rbx_mantle/src/resources_v2/place.rs index c162fe2..ae7e606 100644 --- a/mantle/rbx_mantle/src/resources_v2/place.rs +++ b/mantle/rbx_mantle/src/resources_v2/place.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use rbx_api::models::AssetId; -use super::{experience::ExperienceResource, ManagedResource}; +use super::{experience::Experience, ManagedResource}; #[derive(Debug, Clone, PartialEq)] pub struct PlaceInputs { @@ -12,17 +12,17 @@ pub struct PlaceOutputs { pub asset_id: AssetId, } #[derive(Debug, Clone, PartialEq)] -pub struct PlaceResource { +pub struct Place { pub id: String, pub inputs: PlaceInputs, pub outputs: Option, //#[dependency] - pub experience: ExperienceResource, + pub experience: Experience, } #[async_trait] -impl ManagedResource for PlaceResource { +impl ManagedResource for Place { async fn create(&mut self) -> anyhow::Result<()> { if self.inputs.is_start { self.outputs = Some(PlaceOutputs { From 1567aaf05da4f5c017f6718010721e2c358d5a2b Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 21:50:29 -0500 Subject: [PATCH 09/13] macros --- mantle/derive_resource/src/lib.rs | 193 ++++++++++++++---- .../src/resource_graph_v3/evaluator.rs | 26 +-- .../rbx_mantle/src/resource_graph_v3/mod.rs | 19 +- .../rbx_mantle/src/resources_v2/experience.rs | 5 +- mantle/rbx_mantle/src/resources_v2/mod.rs | 108 +++------- mantle/rbx_mantle/src/resources_v2/place.rs | 8 +- 6 files changed, 215 insertions(+), 144 deletions(-) diff --git a/mantle/derive_resource/src/lib.rs b/mantle/derive_resource/src/lib.rs index a631651..f4b65c6 100644 --- a/mantle/derive_resource/src/lib.rs +++ b/mantle/derive_resource/src/lib.rs @@ -3,44 +3,76 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput}; -#[proc_macro_derive(Resource, attributes(dependency))] -pub fn derive_resource(input: TokenStream) -> TokenStream { +#[proc_macro_derive(ResourceGroup)] +pub fn derive_resource_group(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; - let dependency_fields: Vec<_> = match &input.data { - Data::Struct(data) => data - .fields - .iter() - .filter(|field| field.attrs.iter().any(|a| a.path.is_ident("dependency"))) - .collect(), - _ => panic!("expected struct to derive Resource"), + let variants: Vec<_> = match &input.data { + Data::Enum(data) => data.variants.iter().collect(), + _ => panic!("expected enum to derive ResourceGroup"), }; - let deps = dependency_fields - .iter() - .filter_map(|d| d.ident.clone()) - .map(|ident| quote! {self.#ident.clone()}); + let variant_idents = variants.iter().map(|variant| variant.ident.clone()); + let variant_idents2 = variant_idents.clone(); + let variant_idents3 = variant_idents.clone(); + let variant_idents4 = variant_idents.clone(); + let variant_idents5 = variant_idents.clone(); + let variant_idents6 = variant_idents.clone(); + let variant_idents7 = variant_idents.clone(); let expanded = quote! { - impl Resource for #name { + #[async_trait] + impl ResourceGroup for #name { fn id(&self) -> &str { - &self.id + match self { + #(Self::#variant_idents(resource) => &resource.id),* + } } - fn inputs(&self) -> &dyn ResourceInputs { - &self.inputs + fn has_outputs(&self) -> bool { + match self { + #(Self::#variant_idents2(resource) => resource.outputs.is_some()),* + } } - fn outputs(&self) -> &dyn ResourceOutputs { - &self.outputs + fn dependency_ids(&self) -> Vec<&str> { + match self { + #(Self::#variant_idents3(resource) => resource.dependency_ids()),* + } } - fn dependencies(&self) -> Vec { - vec![ - #(#deps),* - ] + fn next( + &self, + previous_graph: &ResourceGraph, + next_graph: &ResourceGraph, + ) -> anyhow::Result { + match self { + #(Self::#variant_idents4(resource) => Ok(Self::#variant_idents4(#variant_idents4::next( + resource, + previous_graph.get(&resource.id), + next_graph.get_many(resource.dependency_ids()), + )?))),* + } + } + + async fn create(&mut self) -> anyhow::Result<()> { + match self { + #(Self::#variant_idents5(resource) => resource.create().await),* + } + } + + async fn update(&mut self) -> anyhow::Result<()> { + match self { + #(Self::#variant_idents6(resource) => resource.update().await),* + } + } + + async fn delete(&mut self) -> anyhow::Result<()> { + match self { + #(Self::#variant_idents7(resource) => resource.delete().await),* + } } } }; @@ -48,20 +80,107 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -// impl Resource for ExperienceResource { -// fn id(&self) -> &str { -// &self.id -// } +#[proc_macro_derive(Resource, attributes(dependency, resource_group))] +pub fn derive_resource(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); -// fn inputs(&self) -> &dyn ResourceInputs { -// &self.inputs -// } + let name = &input.ident; -// fn outputs(&self) -> &dyn ResourceOutputs { -// &self.outputs -// } + let data = match &input.data { + Data::Struct(data) => data, + _ => panic!("expected struct to derive Resource"), + }; -// fn dependencies(&self) -> Vec { -// vec![] -// } -// } + let dependency_fields: Vec<_> = data + .fields + .iter() + .filter_map(|field| { + if field.attrs.iter().any(|a| a.path.is_ident("dependency")) { + let var_name = field.ident.clone().unwrap(); + let field_type = if let syn::Type::Path(path) = &field.ty { + path.path.get_ident().unwrap().clone() + } else { + panic!("expected dependency type to be a type path"); + }; + Some((var_name, field_type)) + } else { + None + } + }) + .collect(); + let dependency_field_idents = dependency_fields + .iter() + .map(|(var_name, _field_type)| var_name); + + let dependency_variables = dependency_fields.iter().map(|(var_name, field_type)| { + quote! { + let mut #var_name: Option<#field_type> = None; + } + }); + + let dependency_matchers = dependency_fields.iter().map(|(var_name, field_type)| { + quote! { + RbxResource::#field_type(resource) => { + #var_name = Some(resource.clone()); + } + } + }); + + let dependency_values = dependency_fields.iter().map(|(var_name, field_type)| { + let field_type_str = field_type.to_string(); + quote! { + #var_name: #var_name.ok_or(anyhow::Error::msg(format!( + "Expected dependency of type {} to be present", + #field_type_str + )))? + } + }); + + let expanded = quote! { + impl Resource for #name { + // TODO: RbxResource should come from a variable/attribute + fn next( + resource: &Self, + previous_resource: Option<&RbxResource>, + dependencies: Vec<&RbxResource> + ) -> anyhow::Result { + #(#dependency_variables)* + + for dependency in dependencies { + match dependency { + #(#dependency_matchers)* + _ => {} + } + } + + let outputs = match previous_resource { + Some(RbxResource::#name(resource)) => { + resource.outputs.clone() + } + Some(_) => { + return anyhow::Result::Err(anyhow::Error::msg(format!( + "Expected previous resource with ID {} to be of the same type", + resource.id + ))) + } + None => None + }; + + Ok(Self { + id: resource.id.clone(), + inputs: resource.inputs.clone(), + outputs, + #(#dependency_values),* + }) + } + + fn dependency_ids(&self) -> Vec<&str> { + vec![ + #(&self.#dependency_field_idents.id),* + ] + } + } + }; + + TokenStream::from(expanded) +} diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs index b3e5f2e..ef1967e 100644 --- a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs @@ -1,3 +1,5 @@ +use crate::resources_v2::ResourceGroup; + use super::{evaluator_results::EvaluatorResults, ResourceGraph}; pub struct Evaluator<'a> { @@ -116,7 +118,7 @@ pub mod tests { resources_v2::{ experience::{Experience, ExperienceInputs, ExperienceOutputs}, place::{Place, PlaceInputs, PlaceOutputs}, - Resource, + RbxResource, }, }; use pretty_assertions::assert_eq; @@ -135,15 +137,15 @@ pub mod tests { outputs: None, experience: desired_experience.clone(), }; - desired_graph.insert(Resource::Place(desired_start_place)); + desired_graph.insert(RbxResource::Place(desired_start_place)); let desired_other_place = Place { id: "place_other".to_owned(), inputs: PlaceInputs { is_start: false }, outputs: None, experience: desired_experience.clone(), }; - desired_graph.insert(Resource::Place(desired_other_place)); - desired_graph.insert(Resource::Experience(desired_experience)); + desired_graph.insert(RbxResource::Place(desired_other_place)); + desired_graph.insert(RbxResource::Experience(desired_experience)); let previous_graph = ResourceGraph::default(); @@ -191,8 +193,8 @@ pub mod tests { outputs: Some(PlaceOutputs { asset_id: 2 }), experience: previous_experience.clone(), }; - previous_graph.insert(Resource::Place(previous_start_place)); - previous_graph.insert(Resource::Experience(previous_experience)); + previous_graph.insert(RbxResource::Place(previous_start_place)); + previous_graph.insert(RbxResource::Experience(previous_experience)); let mut desired_graph = ResourceGraph::default(); let desired_experience = Experience { @@ -206,8 +208,8 @@ pub mod tests { outputs: None, experience: desired_experience.clone(), }; - desired_graph.insert(Resource::Place(desired_start_place)); - desired_graph.insert(Resource::Experience(desired_experience)); + desired_graph.insert(RbxResource::Place(desired_start_place)); + desired_graph.insert(RbxResource::Experience(desired_experience)); let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); let (results, _next_graph) = evaluator.evaluate().await.unwrap(); @@ -245,8 +247,8 @@ pub mod tests { outputs: None, experience: previous_experience.clone(), }; - previous_graph.insert(Resource::Place(previous_start_place)); - previous_graph.insert(Resource::Experience(previous_experience)); + previous_graph.insert(RbxResource::Place(previous_start_place)); + previous_graph.insert(RbxResource::Experience(previous_experience)); let mut desired_graph = ResourceGraph::default(); let desired_experience = Experience { @@ -262,8 +264,8 @@ pub mod tests { outputs: None, experience: desired_experience.clone(), }; - desired_graph.insert(Resource::Place(desired_start_place)); - desired_graph.insert(Resource::Experience(desired_experience)); + desired_graph.insert(RbxResource::Place(desired_start_place)); + desired_graph.insert(RbxResource::Experience(desired_experience)); let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); let (results, _next_graph) = evaluator.evaluate().await.unwrap(); diff --git a/mantle/rbx_mantle/src/resource_graph_v3/mod.rs b/mantle/rbx_mantle/src/resource_graph_v3/mod.rs index 6deaf2c..e884ab3 100644 --- a/mantle/rbx_mantle/src/resource_graph_v3/mod.rs +++ b/mantle/rbx_mantle/src/resource_graph_v3/mod.rs @@ -3,15 +3,15 @@ pub mod evaluator_results; use std::collections::BTreeMap; -use crate::resources_v2::Resource; +use crate::resources_v2::{RbxResource, ResourceGroup}; #[derive(Debug)] pub struct ResourceGraph { - resources: BTreeMap, + resources: BTreeMap, } impl ResourceGraph { - pub fn new(resources: Vec) -> Self { + pub fn new(resources: Vec) -> Self { Self { resources: resources .into_iter() @@ -28,16 +28,23 @@ impl ResourceGraph { self.resources.contains_key(resource_id) } - pub fn get(&self, resource_id: &str) -> Option<&Resource> { + pub fn get(&self, resource_id: &str) -> Option<&RbxResource> { self.resources.get(resource_id) } - pub fn insert(&mut self, resource: Resource) { + pub fn get_many(&self, resource_ids: Vec<&str>) -> Vec<&RbxResource> { + resource_ids + .iter() + .filter_map(|id| self.resources.get(*id)) + .collect() + } + + pub fn insert(&mut self, resource: RbxResource) { self.resources.insert(resource.id().to_owned(), resource); } // TODO: Can we make this less clone-y? Can we use actual resource references? - pub fn topological_order(&self) -> anyhow::Result> { + pub fn topological_order(&self) -> anyhow::Result> { let mut dependency_graph: BTreeMap> = self .resources .iter() diff --git a/mantle/rbx_mantle/src/resources_v2/experience.rs b/mantle/rbx_mantle/src/resources_v2/experience.rs index e1a7ed1..dae7425 100644 --- a/mantle/rbx_mantle/src/resources_v2/experience.rs +++ b/mantle/rbx_mantle/src/resources_v2/experience.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; +use derive_resource::Resource; use rbx_api::models::AssetId; -use super::ManagedResource; +use super::{ManagedResource, RbxResource, Resource}; #[derive(Debug, Clone, PartialEq)] pub struct ExperienceInputs { @@ -12,7 +13,7 @@ pub struct ExperienceOutputs { pub asset_id: AssetId, pub start_place_id: AssetId, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Resource)] pub struct Experience { pub id: String, pub inputs: ExperienceInputs, diff --git a/mantle/rbx_mantle/src/resources_v2/mod.rs b/mantle/rbx_mantle/src/resources_v2/mod.rs index bd67f88..5507e5b 100644 --- a/mantle/rbx_mantle/src/resources_v2/mod.rs +++ b/mantle/rbx_mantle/src/resources_v2/mod.rs @@ -1,104 +1,46 @@ pub mod experience; pub mod place; -use std::{fmt::Debug, vec}; +use std::fmt::Debug; use async_trait::async_trait; +use derive_resource::ResourceGroup; use crate::resource_graph_v3::ResourceGraph; use self::{experience::Experience, place::Place}; -#[derive(Debug, Clone, PartialEq)] -pub enum Resource { +pub trait Resource: Sized { + fn next( + resource: &Self, + previous_resource: Option<&RbxResource>, + dependencies: Vec<&RbxResource>, + ) -> anyhow::Result; + + fn dependency_ids(&self) -> Vec<&str>; +} + +#[derive(Debug, Clone, PartialEq, ResourceGroup)] +pub enum RbxResource { Experience(Experience), Place(Place), } -impl Resource { - pub fn id(&self) -> &str { - match self { - Self::Experience(resource) => &resource.id, - Self::Place(resource) => &resource.id, - } - } - - pub fn has_outputs(&self) -> bool { - match self { - Self::Experience(resource) => resource.outputs.is_some(), - Self::Place(resource) => resource.outputs.is_some(), - } - } - - pub fn dependency_ids(&self) -> Vec<&str> { - match self { - Self::Experience(_resource) => vec![], - Self::Place(resource) => vec![&resource.experience.id], - } - } - - pub fn next( +#[async_trait] +pub trait ResourceGroup { + fn id(&self) -> &str; + fn has_outputs(&self) -> bool; + fn dependency_ids(&self) -> Vec<&str>; + fn next( &self, previous_graph: &ResourceGraph, next_graph: &ResourceGraph, - ) -> anyhow::Result { - match self { - Self::Experience(resource) => Ok(Self::Experience(Experience { - id: resource.id.clone(), - inputs: resource.inputs.clone(), - outputs: match previous_graph.get(&resource.id) { - Some(previous_resource) => match previous_resource { - Resource::Experience(previous_resource) => { - previous_resource.outputs.clone() - } - _ => { - return anyhow::Result::Err(anyhow::Error::msg("Expected 'experience'")) - } - }, - _ => None, - }, - })), - Self::Place(resource) => Ok(Self::Place(Place { - id: resource.id.clone(), - inputs: resource.inputs.clone(), - outputs: match previous_graph.get(&resource.id) { - Some(previous_resource) => match previous_resource { - Resource::Place(previous_resource) => previous_resource.outputs.clone(), - _ => return anyhow::Result::Err(anyhow::Error::msg("Expected 'place'")), - }, - _ => None, - }, - experience: match next_graph - .get(&resource.experience.id) - .ok_or(anyhow::Error::msg("Unable to find resource"))? - { - Resource::Experience(experience) => experience.clone(), - _ => return anyhow::Result::Err(anyhow::Error::msg("Expected 'experience'")), - }, - })), - } - } - - pub async fn create(&mut self) -> anyhow::Result<()> { - match self { - Self::Experience(resource) => resource.create().await, - Self::Place(resource) => resource.create().await, - } - } - - pub async fn update(&mut self) -> anyhow::Result<()> { - match self { - Self::Experience(resource) => resource.update().await, - Self::Place(resource) => resource.update().await, - } - } + ) -> anyhow::Result; - pub async fn delete(&mut self) -> anyhow::Result<()> { - match self { - Self::Experience(resource) => resource.delete().await, - Self::Place(resource) => resource.delete().await, - } - } + // TODO: should these be separate somehow? + async fn create(&mut self) -> anyhow::Result<()>; + async fn update(&mut self) -> anyhow::Result<()>; + async fn delete(&mut self) -> anyhow::Result<()>; } #[async_trait] diff --git a/mantle/rbx_mantle/src/resources_v2/place.rs b/mantle/rbx_mantle/src/resources_v2/place.rs index ae7e606..158ef30 100644 --- a/mantle/rbx_mantle/src/resources_v2/place.rs +++ b/mantle/rbx_mantle/src/resources_v2/place.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; +use derive_resource::Resource; use rbx_api::models::AssetId; -use super::{experience::Experience, ManagedResource}; +use super::{experience::Experience, ManagedResource, RbxResource, Resource}; #[derive(Debug, Clone, PartialEq)] pub struct PlaceInputs { @@ -11,13 +12,12 @@ pub struct PlaceInputs { pub struct PlaceOutputs { pub asset_id: AssetId, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Resource)] pub struct Place { pub id: String, pub inputs: PlaceInputs, pub outputs: Option, - - //#[dependency] + #[dependency] pub experience: Experience, } From 27f2a3bf59b01341cf759c98773a9555ec0803b6 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 21:56:28 -0500 Subject: [PATCH 10/13] remove v2 --- mantle/rbx_mantle/src/lib.rs | 2 +- .../src/resource_graph_v2/evaluator.rs | 488 +++++++++--------- .../evaluator_results.rs | 0 .../rbx_mantle/src/resource_graph_v2/mod.rs | 111 ++-- .../src/resource_graph_v3/evaluator.rs | 291 ----------- .../rbx_mantle/src/resource_graph_v3/mod.rs | 98 ---- mantle/rbx_mantle/src/resources/experience.rs | 66 --- mantle/rbx_mantle/src/resources/mod.rs | 106 ---- mantle/rbx_mantle/src/resources/place.rs | 68 --- mantle/rbx_mantle/src/resources_v2/mod.rs | 2 +- mantle/rbx_mantle/src/resources_v2/notes.txt | 113 ---- 11 files changed, 281 insertions(+), 1064 deletions(-) rename mantle/rbx_mantle/src/{resource_graph_v3 => resource_graph_v2}/evaluator_results.rs (100%) delete mode 100644 mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs delete mode 100644 mantle/rbx_mantle/src/resource_graph_v3/mod.rs delete mode 100644 mantle/rbx_mantle/src/resources/experience.rs delete mode 100644 mantle/rbx_mantle/src/resources/mod.rs delete mode 100644 mantle/rbx_mantle/src/resources/place.rs delete mode 100644 mantle/rbx_mantle/src/resources_v2/notes.txt diff --git a/mantle/rbx_mantle/src/lib.rs b/mantle/rbx_mantle/src/lib.rs index 6825e93..d49f76f 100644 --- a/mantle/rbx_mantle/src/lib.rs +++ b/mantle/rbx_mantle/src/lib.rs @@ -2,7 +2,7 @@ pub mod config; pub mod project; pub mod resource_graph; // pub mod resource_graph_v2; -pub mod resource_graph_v3; +pub mod resource_graph_v2; // pub mod resources; pub mod resources_v2; pub mod roblox_resource_manager; diff --git a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs index d532b6c..568eb19 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs @@ -1,289 +1,297 @@ -use std::path::PathBuf; +use crate::resources_v2::ResourceGroup; -use rbx_api::{models::CreatorType, RobloxApi}; -use rbx_auth::RobloxAuth; +use super::{evaluator_results::EvaluatorResults, ResourceGraph}; -use super::ResourceGraph; -use crate::resources::{ResourceId, ResourceRef, UpdateStrategy}; - -pub struct ResourceGraphEvaluatorOptions { - pub project_path: PathBuf, - pub payment_source: CreatorType, - pub allow_purchases: bool, -} - -pub struct ResourceGraphEvaluatorContext { - pub options: ResourceGraphEvaluatorOptions, - pub roblox_api: RobloxApi, -} - -pub enum SkipReason { - PurchasesNotAllowed, -} - -pub enum OperationType { - Create, - Update, - Recreate, - Delete, - Noop, - Skip(SkipReason), -} - -pub enum OperationStatus { - Success, - Failure(anyhow::Error), -} - -pub struct OperationResult { - pub resource_id: ResourceId, - pub operation_type: OperationType, - pub status: OperationStatus, -} - -#[derive(Default)] -pub struct EvaluatorResults { - pub operation_results: Vec, -} - -impl EvaluatorResults { - pub fn is_empty(&self) -> bool { - self.operation_results.is_empty() - } - - pub fn create_succeeded(&mut self, resource_id: ResourceId) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Create, - status: OperationStatus::Success, - }) - } - pub fn create_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Create, - status: OperationStatus::Failure(error), - }) - } - - pub fn update_succeeded(&mut self, resource_id: ResourceId) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Update, - status: OperationStatus::Success, - }) - } - pub fn update_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Update, - status: OperationStatus::Failure(error), - }) - } - - pub fn recreate_succeeded(&mut self, resource_id: ResourceId) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Recreate, - status: OperationStatus::Success, - }) - } - pub fn recreate_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Recreate, - status: OperationStatus::Failure(error), - }) - } - - pub fn delete_succeeded(&mut self, resource_id: ResourceId) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Delete, - status: OperationStatus::Success, - }) - } - pub fn delete_failed(&mut self, resource_id: ResourceId, error: anyhow::Error) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Delete, - status: OperationStatus::Failure(error), - }) - } - - pub fn noop(&mut self, resource_id: ResourceId) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Noop, - status: OperationStatus::Success, - }) - } - - pub fn skip(&mut self, resource_id: ResourceId, reason: SkipReason) { - self.operation_results.push(OperationResult { - resource_id, - operation_type: OperationType::Skip(reason), - status: OperationStatus::Success, - }) - } -} - -pub struct ResouceGraphEvaluator<'a> { - context: ResourceGraphEvaluatorContext, +pub struct Evaluator<'a> { previous_graph: &'a ResourceGraph, desired_graph: &'a ResourceGraph, next_graph: ResourceGraph, + results: EvaluatorResults, } -impl<'a> ResouceGraphEvaluator<'a> { - pub async fn new( - options: ResourceGraphEvaluatorOptions, - previous_graph: &'a ResourceGraph, - current_graph: &'a ResourceGraph, - ) -> anyhow::Result> { - let roblox_auth = RobloxAuth::new().await?; - let roblox_api = RobloxApi::new(roblox_auth)?; - Ok(ResouceGraphEvaluator { - context: ResourceGraphEvaluatorContext { - options, - roblox_api, - }, +impl<'a> Evaluator<'a> { + pub fn new(previous_graph: &'a ResourceGraph, desired_graph: &'a ResourceGraph) -> Self { + Self { previous_graph, - desired_graph: current_graph, + desired_graph, next_graph: ResourceGraph::default(), results: EvaluatorResults::default(), - }) + } } pub async fn evaluate( &'a mut self, ) -> anyhow::Result<(&'a EvaluatorResults, &'a ResourceGraph)> { if !self.results.is_empty() { - panic!("Cannot use a graph evaluator more than once"); + return anyhow::Result::Err(anyhow::Error::msg( + "A graph evaluator can only be used once.", + )); } - self.delete_removed_resources().await; + self.delete_removed_resources().await?; self.create_or_update_added_or_changed_resources().await?; Ok((&self.results, &self.next_graph)) } - async fn delete_removed_resources(&mut self) { + async fn delete_removed_resources(&mut self) -> anyhow::Result<()> { let mut previous_resources = self.previous_graph.topological_order()?; - - // Iterate over previous resources in reverse order so that leaf resources are removed first previous_resources.reverse(); - for resource in previous_resources.iter() { - // no need to delete resources that still exist - let mut resource_write_ref = resource.write().unwrap(); - let resource_id = resource_write_ref.id().to_owned(); - if self.desired_graph.contains(&resource_id) { + for resource in previous_resources.into_iter() { + if self.desired_graph.contains(resource.id()) { continue; } - println!("Deleting: {}", resource_id); - // TODO: diff - println!("Dependencies: {:?}", resource_write_ref.dependencies()); - println!("Inputs: {:?}", resource_write_ref.inputs()); + println!("Deleting: {}", resource.id()); - let delete_result = resource_write_ref.delete(&mut self.context).await; + let mut next_resource = resource.clone(); - match delete_result { - Ok(()) => { - println!("Deleted resource {}", resource_id); - self.results.delete_succeeded(resource_id); - } + match next_resource.delete().await { + Ok(()) => self.results.delete_succeeded(resource.id()), Err(error) => { - println!("Failed to delete resource {}: {}", resource_id, error); - self.results.delete_failed(resource_id, error); + self.results.delete_failed(resource.id(), error); + self.next_graph.insert(next_resource); } } } + + Ok(()) } - async fn create_resource(&mut self, resource: ResourceRef) { - let mut resource_write_ref = resource.write().unwrap(); - let resource_id = resource_write_ref.id().to_owned(); - - println!("Creating: {}", resource_id); - // TODO: diff - println!("Dependencies: {:?}", resource_write_ref.dependencies()); - println!("Inputs: {:?}", resource_write_ref.inputs()); - - let create_price_result = resource_write_ref.price(&mut self.context).await; - - let price = match create_price_result { - Ok(Some(price)) if price > 0 => { - if self.context.options.allow_purchases { - println!("{} Robux will be charged from your account.", price); - Some(price) - } else { - self.results - .skip(resource_id, SkipReason::PurchasesNotAllowed); - println!("Resource would cost {} to create. Give Mantle permission to make purchases with --allow-purchases.", price); - return; + async fn create_or_update_added_or_changed_resources(&mut self) -> anyhow::Result<()> { + let desired_resources = self.desired_graph.topological_order()?; + + for desired_resource in desired_resources.into_iter() { + if let Some(previous_resource) = self.previous_graph.get(desired_resource.id()) { + match desired_resource.next(&self.previous_graph, &self.next_graph) { + Ok(mut next_resource) => { + if *previous_resource == next_resource { + self.results.noop(next_resource.id()); + self.next_graph.insert(next_resource); + } else { + match next_resource.update().await { + Ok(()) => { + self.results.update_succeeded(next_resource.id()); + self.next_graph.insert(next_resource); + } + Err(error) => { + self.results.update_failed(next_resource.id(), error); + self.next_graph.insert(previous_resource.clone()); + } + } + } + } + Err(error) => self.results.update_failed(desired_resource.id(), error), + } + } else { + match desired_resource.next(&self.previous_graph, &self.next_graph) { + Ok(mut next_resource) => match next_resource.create().await { + Ok(()) => { + self.results.create_succeeded(next_resource.id()); + self.next_graph.insert(next_resource); + } + Err(error) => { + self.results.create_failed(next_resource.id(), error); + } + }, + Err(error) => self.results.create_failed(desired_resource.id(), error), } - } - Ok(_) => None, - Err(error) => { - self.results.create_failed(resource_id, error); - return; - } - }; - - let create_result = resource_write_ref.create(&mut self.context, price).await; - - match create_result { - Ok(()) => { - println!( - "Created resource {} with outputs: {:?}", - resource_id, - resource_write_ref.outputs() - ); - self.next_graph.insert(&resource_id, resource.clone()); - self.results.create_succeeded(resource_id); - } - Err(error) => { - println!("Failed to create resource {}: {}", resource_id, error); - self.results.create_failed(resource_id, error); } } - } - fn update_resource(&mut self, resource: ResourceRef) { - let mut resource_write_ref = resource.write().unwrap(); - let resource_id = resource_write_ref.id().to_owned(); - - // match resource.write().unwrap().update_strategy() { - // UpdateStrategy::Recreate => { - // self.create_resource(resource.clone()).await; - // return; - // } - // UpdateStrategy::Update(updatable_resource) => { - // updatable_resource.update(context); - // } - // } - - println!("Updating: {}", resource_id); + Ok(()) } +} - async fn create_or_update_added_or_changed_resources(&mut self) -> anyhow::Result<()> { - let resources = self.desired_graph.topological_order()?; - - for resource in resources.iter() { - let resource_read_ref = resource.read().unwrap(); - let resource_id = resource_read_ref.id().to_owned(); - drop(resource_read_ref); +#[cfg(test)] +pub mod tests { + use crate::{ + resource_graph_v2::{ + evaluator::Evaluator, + evaluator_results::{ + EvaluatorResults, OperationResult, OperationStatus, OperationType, + }, + ResourceGraph, + }, + resources_v2::{ + experience::{Experience, ExperienceInputs, ExperienceOutputs}, + place::{Place, PlaceInputs, PlaceOutputs}, + RbxResource, + }, + }; + use pretty_assertions::assert_eq; + + #[tokio::test] + pub async fn create_resources() { + let mut desired_graph = ResourceGraph::default(); + let desired_experience = Experience { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let desired_start_place = Place { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(RbxResource::Place(desired_start_place)); + let desired_other_place = Place { + id: "place_other".to_owned(), + inputs: PlaceInputs { is_start: false }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(RbxResource::Place(desired_other_place)); + desired_graph.insert(RbxResource::Experience(desired_experience)); + + let previous_graph = ResourceGraph::default(); + + let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); + let (results, _next_graph) = evaluator.evaluate().await.unwrap(); + + dbg!(_next_graph); + + assert_eq!( + *results, + EvaluatorResults { + operation_results: vec![ + OperationResult { + resource_id: "experience_singleton".to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_start".to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_other".to_owned(), + operation_type: OperationType::Create, + status: OperationStatus::Success + } + ] + } + ); + } - if let Some(previous_resource) = self.previous_graph.get(&resource_id) { - // compare, check strategy, update or recreate - } else { - self.create_resource(resource.clone()).await; + #[tokio::test] + pub async fn update_resources_noop() { + let mut previous_graph = ResourceGraph::default(); + let previous_experience = Experience { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: Some(ExperienceOutputs { + asset_id: 1, + start_place_id: 2, + }), + }; + let previous_start_place = Place { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: Some(PlaceOutputs { asset_id: 2 }), + experience: previous_experience.clone(), + }; + previous_graph.insert(RbxResource::Place(previous_start_place)); + previous_graph.insert(RbxResource::Experience(previous_experience)); + + let mut desired_graph = ResourceGraph::default(); + let desired_experience = Experience { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let desired_start_place = Place { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(RbxResource::Place(desired_start_place)); + desired_graph.insert(RbxResource::Experience(desired_experience)); + + let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); + let (results, _next_graph) = evaluator.evaluate().await.unwrap(); + + dbg!(_next_graph); + + assert_eq!( + *results, + EvaluatorResults { + operation_results: vec![ + OperationResult { + resource_id: "experience_singleton".to_owned(), + operation_type: OperationType::Noop, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_start".to_owned(), + operation_type: OperationType::Noop, + status: OperationStatus::Success + } + ] } - } + ); + } - Ok(()) + #[tokio::test] + pub async fn update_resources_changes() { + let mut previous_graph = ResourceGraph::default(); + let previous_experience = Experience { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { group_id: None }, + outputs: None, + }; + let previous_start_place = Place { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: previous_experience.clone(), + }; + previous_graph.insert(RbxResource::Place(previous_start_place)); + previous_graph.insert(RbxResource::Experience(previous_experience)); + + let mut desired_graph = ResourceGraph::default(); + let desired_experience = Experience { + id: "experience_singleton".to_owned(), + inputs: ExperienceInputs { + group_id: Some(123), + }, + outputs: None, + }; + let desired_start_place = Place { + id: "place_start".to_owned(), + inputs: PlaceInputs { is_start: true }, + outputs: None, + experience: desired_experience.clone(), + }; + desired_graph.insert(RbxResource::Place(desired_start_place)); + desired_graph.insert(RbxResource::Experience(desired_experience)); + + let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); + let (results, _next_graph) = evaluator.evaluate().await.unwrap(); + + dbg!(_next_graph); + + assert_eq!( + *results, + EvaluatorResults { + operation_results: vec![ + OperationResult { + resource_id: "experience_singleton".to_owned(), + operation_type: OperationType::Update, + status: OperationStatus::Success + }, + OperationResult { + resource_id: "place_start".to_owned(), + operation_type: OperationType::Update, + status: OperationStatus::Success + } + ] + } + ); } } diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator_results.rs b/mantle/rbx_mantle/src/resource_graph_v2/evaluator_results.rs similarity index 100% rename from mantle/rbx_mantle/src/resource_graph_v3/evaluator_results.rs rename to mantle/rbx_mantle/src/resource_graph_v2/evaluator_results.rs diff --git a/mantle/rbx_mantle/src/resource_graph_v2/mod.rs b/mantle/rbx_mantle/src/resource_graph_v2/mod.rs index f211021..e884ab3 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2/mod.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/mod.rs @@ -1,81 +1,66 @@ -use std::{ - collections::BTreeMap, - sync::{Arc, RwLock}, -}; - -use crate::resources::{experience::*, place::*, ResourceId, ResourceRef}; - pub mod evaluator; +pub mod evaluator_results; -fn _create_graph() { - let experience = Arc::new(RwLock::new(ExperienceResource { - id: "singleton".to_owned(), - inputs: ExperienceInputs { group_id: None }, - outputs: ExperienceOutputs::Empty, - })); +use std::collections::BTreeMap; - let place = Arc::new(RwLock::new(PlaceResource { - id: "start".to_owned(), - inputs: PlaceInputs { is_start: true }, - outputs: PlaceOutputs::Empty, - experience: Arc::downgrade(&experience), - })); - - let resources: Vec = vec![experience, place]; - let _graph = ResourceGraph::new(&resources); -} +use crate::resources_v2::{RbxResource, ResourceGroup}; -#[derive(Default)] +#[derive(Debug)] pub struct ResourceGraph { - resources: BTreeMap, + resources: BTreeMap, } impl ResourceGraph { - pub fn new(resources: &[ResourceRef]) -> Self { + pub fn new(resources: Vec) -> Self { Self { resources: resources - .iter() - .map(|resource| { - ( - resource.read().unwrap().id().to_owned(), - Arc::clone(resource), - ) - }) + .into_iter() + .map(|resource| (resource.id().to_owned(), resource)) .collect(), } } + pub fn default() -> Self { + Self::new(vec![]) + } + pub fn contains(&self, resource_id: &str) -> bool { self.resources.contains_key(resource_id) } - pub fn get(&self, resource_id: &str) -> Option { - self.resources.get(resource_id).map(|x| Arc::clone(x)) + pub fn get(&self, resource_id: &str) -> Option<&RbxResource> { + self.resources.get(resource_id) } - pub fn insert(&mut self, id: &ResourceId, resource: ResourceRef) { - self.resources.insert(id.to_owned(), resource); + pub fn get_many(&self, resource_ids: Vec<&str>) -> Vec<&RbxResource> { + resource_ids + .iter() + .filter_map(|id| self.resources.get(*id)) + .collect() } - pub fn topological_order(&self) -> anyhow::Result> { - let mut dependency_graph: BTreeMap> = self + pub fn insert(&mut self, resource: RbxResource) { + self.resources.insert(resource.id().to_owned(), resource); + } + + // TODO: Can we make this less clone-y? Can we use actual resource references? + pub fn topological_order(&self) -> anyhow::Result> { + let mut dependency_graph: BTreeMap> = self .resources .iter() .map(|(id, resource)| { ( id.clone(), resource - .read() - .unwrap() - .dependencies() + .dependency_ids() .iter() - .filter_map(|d| d.upgrade().map(|x| x.read().unwrap().id().to_owned())) + .map(|d| d.to_owned().to_owned()) .collect(), ) }) .collect(); - let mut start_nodes: Vec = dependency_graph + let mut start_nodes: Vec = dependency_graph .iter() .filter_map(|(node, deps)| { if deps.is_empty() { @@ -86,7 +71,7 @@ impl ResourceGraph { }) .collect(); - let mut ordered: Vec = Vec::new(); + let mut ordered: Vec = Vec::new(); while let Some(start_node) = start_nodes.pop() { ordered.push(start_node.clone()); for (node, deps) in dependency_graph.iter_mut() { @@ -106,42 +91,8 @@ impl ResourceGraph { )), false => Ok(ordered .iter() - .map(|id| Arc::clone(self.resources.get(id).unwrap())) + .map(|id| self.resources.get(id).unwrap()) .collect()), } } - - // pub async fn evaluate_delete( - // &self, - // resource: ResourceRef, - // context: &mut ResourceManagerContext, - // ) -> anyhow::Result<()> { - // resource.write().unwrap().delete(context).await?; - // // .resource.delete(context).await; - // Ok(()) - // } - - // pub async fn evaluate( - // &self, - // previous_graph: &ResourceGraph, - // context: &mut ResourceManagerContext, - // ) -> anyhow::Result<()> { - // let mut previous_resources = previous_graph.topological_order()?; - // previous_resources.reverse(); - // for resource in previous_resources { - // if self.resources.get(resource.read().unwrap().id()).is_some() { - // continue; - // } - - // // TODO: delete - // self.evaluate_delete(resource, context); - // } - - // let current_resources = self.topological_order()?; - // for resource in current_resources { - // self.evaluate_delete(resource, context); - // } - - // Ok(()) - // } } diff --git a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs deleted file mode 100644 index ef1967e..0000000 --- a/mantle/rbx_mantle/src/resource_graph_v3/evaluator.rs +++ /dev/null @@ -1,291 +0,0 @@ -use crate::resources_v2::ResourceGroup; - -use super::{evaluator_results::EvaluatorResults, ResourceGraph}; - -pub struct Evaluator<'a> { - previous_graph: &'a ResourceGraph, - desired_graph: &'a ResourceGraph, - next_graph: ResourceGraph, - - results: EvaluatorResults, -} - -impl<'a> Evaluator<'a> { - pub fn new(previous_graph: &'a ResourceGraph, desired_graph: &'a ResourceGraph) -> Self { - Self { - previous_graph, - desired_graph, - next_graph: ResourceGraph::default(), - results: EvaluatorResults::default(), - } - } - - pub async fn evaluate( - &'a mut self, - ) -> anyhow::Result<(&'a EvaluatorResults, &'a ResourceGraph)> { - if !self.results.is_empty() { - return anyhow::Result::Err(anyhow::Error::msg( - "A graph evaluator can only be used once.", - )); - } - - self.delete_removed_resources().await?; - self.create_or_update_added_or_changed_resources().await?; - - Ok((&self.results, &self.next_graph)) - } - - async fn delete_removed_resources(&mut self) -> anyhow::Result<()> { - let mut previous_resources = self.previous_graph.topological_order()?; - previous_resources.reverse(); - - for resource in previous_resources.into_iter() { - if self.desired_graph.contains(resource.id()) { - continue; - } - - println!("Deleting: {}", resource.id()); - - let mut next_resource = resource.clone(); - - match next_resource.delete().await { - Ok(()) => self.results.delete_succeeded(resource.id()), - Err(error) => { - self.results.delete_failed(resource.id(), error); - self.next_graph.insert(next_resource); - } - } - } - - Ok(()) - } - - async fn create_or_update_added_or_changed_resources(&mut self) -> anyhow::Result<()> { - let desired_resources = self.desired_graph.topological_order()?; - - for desired_resource in desired_resources.into_iter() { - if let Some(previous_resource) = self.previous_graph.get(desired_resource.id()) { - match desired_resource.next(&self.previous_graph, &self.next_graph) { - Ok(mut next_resource) => { - if *previous_resource == next_resource { - self.results.noop(next_resource.id()); - self.next_graph.insert(next_resource); - } else { - match next_resource.update().await { - Ok(()) => { - self.results.update_succeeded(next_resource.id()); - self.next_graph.insert(next_resource); - } - Err(error) => { - self.results.update_failed(next_resource.id(), error); - self.next_graph.insert(previous_resource.clone()); - } - } - } - } - Err(error) => self.results.update_failed(desired_resource.id(), error), - } - } else { - match desired_resource.next(&self.previous_graph, &self.next_graph) { - Ok(mut next_resource) => match next_resource.create().await { - Ok(()) => { - self.results.create_succeeded(next_resource.id()); - self.next_graph.insert(next_resource); - } - Err(error) => { - self.results.create_failed(next_resource.id(), error); - } - }, - Err(error) => self.results.create_failed(desired_resource.id(), error), - } - } - } - - Ok(()) - } -} - -#[cfg(test)] -pub mod tests { - use crate::{ - resource_graph_v3::{ - evaluator::Evaluator, - evaluator_results::{ - EvaluatorResults, OperationResult, OperationStatus, OperationType, - }, - ResourceGraph, - }, - resources_v2::{ - experience::{Experience, ExperienceInputs, ExperienceOutputs}, - place::{Place, PlaceInputs, PlaceOutputs}, - RbxResource, - }, - }; - use pretty_assertions::assert_eq; - - #[tokio::test] - pub async fn create_resources() { - let mut desired_graph = ResourceGraph::default(); - let desired_experience = Experience { - id: "experience_singleton".to_owned(), - inputs: ExperienceInputs { group_id: None }, - outputs: None, - }; - let desired_start_place = Place { - id: "place_start".to_owned(), - inputs: PlaceInputs { is_start: true }, - outputs: None, - experience: desired_experience.clone(), - }; - desired_graph.insert(RbxResource::Place(desired_start_place)); - let desired_other_place = Place { - id: "place_other".to_owned(), - inputs: PlaceInputs { is_start: false }, - outputs: None, - experience: desired_experience.clone(), - }; - desired_graph.insert(RbxResource::Place(desired_other_place)); - desired_graph.insert(RbxResource::Experience(desired_experience)); - - let previous_graph = ResourceGraph::default(); - - let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); - let (results, _next_graph) = evaluator.evaluate().await.unwrap(); - - assert_eq!( - *results, - EvaluatorResults { - operation_results: vec![ - OperationResult { - resource_id: "experience_singleton".to_owned(), - operation_type: OperationType::Create, - status: OperationStatus::Success - }, - OperationResult { - resource_id: "place_start".to_owned(), - operation_type: OperationType::Create, - status: OperationStatus::Success - }, - OperationResult { - resource_id: "place_other".to_owned(), - operation_type: OperationType::Create, - status: OperationStatus::Success - } - ] - } - ); - } - - #[tokio::test] - pub async fn update_resources_noop() { - let mut previous_graph = ResourceGraph::default(); - let previous_experience = Experience { - id: "experience_singleton".to_owned(), - inputs: ExperienceInputs { group_id: None }, - outputs: Some(ExperienceOutputs { - asset_id: 1, - start_place_id: 2, - }), - }; - let previous_start_place = Place { - id: "place_start".to_owned(), - inputs: PlaceInputs { is_start: true }, - outputs: Some(PlaceOutputs { asset_id: 2 }), - experience: previous_experience.clone(), - }; - previous_graph.insert(RbxResource::Place(previous_start_place)); - previous_graph.insert(RbxResource::Experience(previous_experience)); - - let mut desired_graph = ResourceGraph::default(); - let desired_experience = Experience { - id: "experience_singleton".to_owned(), - inputs: ExperienceInputs { group_id: None }, - outputs: None, - }; - let desired_start_place = Place { - id: "place_start".to_owned(), - inputs: PlaceInputs { is_start: true }, - outputs: None, - experience: desired_experience.clone(), - }; - desired_graph.insert(RbxResource::Place(desired_start_place)); - desired_graph.insert(RbxResource::Experience(desired_experience)); - - let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); - let (results, _next_graph) = evaluator.evaluate().await.unwrap(); - - assert_eq!( - *results, - EvaluatorResults { - operation_results: vec![ - OperationResult { - resource_id: "experience_singleton".to_owned(), - operation_type: OperationType::Noop, - status: OperationStatus::Success - }, - OperationResult { - resource_id: "place_start".to_owned(), - operation_type: OperationType::Noop, - status: OperationStatus::Success - } - ] - } - ); - } - - #[tokio::test] - pub async fn update_resources_changes() { - let mut previous_graph = ResourceGraph::default(); - let previous_experience = Experience { - id: "experience_singleton".to_owned(), - inputs: ExperienceInputs { group_id: None }, - outputs: None, - }; - let previous_start_place = Place { - id: "place_start".to_owned(), - inputs: PlaceInputs { is_start: true }, - outputs: None, - experience: previous_experience.clone(), - }; - previous_graph.insert(RbxResource::Place(previous_start_place)); - previous_graph.insert(RbxResource::Experience(previous_experience)); - - let mut desired_graph = ResourceGraph::default(); - let desired_experience = Experience { - id: "experience_singleton".to_owned(), - inputs: ExperienceInputs { - group_id: Some(123), - }, - outputs: None, - }; - let desired_start_place = Place { - id: "place_start".to_owned(), - inputs: PlaceInputs { is_start: true }, - outputs: None, - experience: desired_experience.clone(), - }; - desired_graph.insert(RbxResource::Place(desired_start_place)); - desired_graph.insert(RbxResource::Experience(desired_experience)); - - let mut evaluator = Evaluator::new(&previous_graph, &desired_graph); - let (results, _next_graph) = evaluator.evaluate().await.unwrap(); - - assert_eq!( - *results, - EvaluatorResults { - operation_results: vec![ - OperationResult { - resource_id: "experience_singleton".to_owned(), - operation_type: OperationType::Update, - status: OperationStatus::Success - }, - OperationResult { - resource_id: "place_start".to_owned(), - operation_type: OperationType::Update, - status: OperationStatus::Success - } - ] - } - ); - } -} diff --git a/mantle/rbx_mantle/src/resource_graph_v3/mod.rs b/mantle/rbx_mantle/src/resource_graph_v3/mod.rs deleted file mode 100644 index e884ab3..0000000 --- a/mantle/rbx_mantle/src/resource_graph_v3/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -pub mod evaluator; -pub mod evaluator_results; - -use std::collections::BTreeMap; - -use crate::resources_v2::{RbxResource, ResourceGroup}; - -#[derive(Debug)] -pub struct ResourceGraph { - resources: BTreeMap, -} - -impl ResourceGraph { - pub fn new(resources: Vec) -> Self { - Self { - resources: resources - .into_iter() - .map(|resource| (resource.id().to_owned(), resource)) - .collect(), - } - } - - pub fn default() -> Self { - Self::new(vec![]) - } - - pub fn contains(&self, resource_id: &str) -> bool { - self.resources.contains_key(resource_id) - } - - pub fn get(&self, resource_id: &str) -> Option<&RbxResource> { - self.resources.get(resource_id) - } - - pub fn get_many(&self, resource_ids: Vec<&str>) -> Vec<&RbxResource> { - resource_ids - .iter() - .filter_map(|id| self.resources.get(*id)) - .collect() - } - - pub fn insert(&mut self, resource: RbxResource) { - self.resources.insert(resource.id().to_owned(), resource); - } - - // TODO: Can we make this less clone-y? Can we use actual resource references? - pub fn topological_order(&self) -> anyhow::Result> { - let mut dependency_graph: BTreeMap> = self - .resources - .iter() - .map(|(id, resource)| { - ( - id.clone(), - resource - .dependency_ids() - .iter() - .map(|d| d.to_owned().to_owned()) - .collect(), - ) - }) - .collect(); - - let mut start_nodes: Vec = dependency_graph - .iter() - .filter_map(|(node, deps)| { - if deps.is_empty() { - Some(node.clone()) - } else { - None - } - }) - .collect(); - - let mut ordered: Vec = Vec::new(); - while let Some(start_node) = start_nodes.pop() { - ordered.push(start_node.clone()); - for (node, deps) in dependency_graph.iter_mut() { - if deps.iter().any(|dep| *dep == start_node) { - deps.retain(|dep| *dep != start_node); - if deps.is_empty() { - start_nodes.push(node.clone()); - } - } - } - } - - let has_cycles = dependency_graph.iter().any(|(_, deps)| !deps.is_empty()); - match has_cycles { - true => Err(anyhow::Error::msg( - "Cannot evaluate resource graph because it has cycles", - )), - false => Ok(ordered - .iter() - .map(|id| self.resources.get(id).unwrap()) - .collect()), - } - } -} diff --git a/mantle/rbx_mantle/src/resources/experience.rs b/mantle/rbx_mantle/src/resources/experience.rs deleted file mode 100644 index dd8e026..0000000 --- a/mantle/rbx_mantle/src/resources/experience.rs +++ /dev/null @@ -1,66 +0,0 @@ -use async_trait::async_trait; -use derive_resource::Resource; -use rbx_api::models::AssetId; - -use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; - -use super::{ - ManagedResource, Resource, ResourceId, ResourceInputs, ResourceOutputs, UpdateStrategy, - WeakResourceRef, -}; - -#[derive(Debug)] -pub struct ExperienceInputs { - pub group_id: Option, -} -impl ResourceInputs for ExperienceInputs {} - -#[derive(Debug)] -pub enum ExperienceOutputs { - Data { - asset_id: AssetId, - start_place_id: AssetId, - }, - Empty, -} -impl ResourceOutputs for ExperienceOutputs { - fn has_outputs(&self) -> bool { - match self { - Self::Empty => false, - _ => true, - } - } -} - -#[derive(Resource, Debug)] -pub struct ExperienceResource { - pub id: ResourceId, - pub inputs: ExperienceInputs, - pub outputs: ExperienceOutputs, -} - -#[async_trait] -impl ManagedResource for ExperienceResource { - async fn delete(&mut self, _context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()> { - todo!() - } - - async fn price( - &mut self, - _context: &mut ResourceGraphEvaluatorContext, - ) -> anyhow::Result> { - todo!() - } - - async fn create( - &mut self, - _context: &mut ResourceGraphEvaluatorContext, - _price: Option, - ) -> anyhow::Result<()> { - todo!() - } - - fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a> { - UpdateStrategy::Recreate - } -} diff --git a/mantle/rbx_mantle/src/resources/mod.rs b/mantle/rbx_mantle/src/resources/mod.rs deleted file mode 100644 index eb63a54..0000000 --- a/mantle/rbx_mantle/src/resources/mod.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::{ - fmt::Debug, - sync::{Arc, RwLock, Weak}, -}; - -use async_trait::async_trait; -use enum_dispatch::enum_dispatch; - -use self::{experience::ExperienceResource, place::PlaceResource}; -use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; - -pub mod experience; -pub mod place; - -// pub enum UpdateStrategy { -// UpdateInPlace, -// Recreate, -// } - -pub trait ResourceInputs: Debug {} - -pub trait ResourceOutputs: Debug { - fn has_outputs(&self) -> bool; -} - -pub type ResourceId = String; - -pub type ResourceRef = Arc>; -pub type WeakResourceRef = Weak>; - -#[enum_dispatch] -pub trait Resource: Debug { - fn id(&self) -> &str; - - fn inputs(&self) -> &dyn ResourceInputs; - - fn outputs(&self) -> &dyn ResourceOutputs; - - fn dependencies(&self) -> Vec; - - // TODO: return simple update strategy enum here -} - -#[async_trait] -#[enum_dispatch] -pub trait ManagedResource: Resource { - // async fn creation_price( - // &self, - // context: &mut ResourceManagerContext, - // ) -> anyhow::Result> { - // Ok(None) - // } - - // async fn create( - // &mut self, - // context: &mut ResourceManagerContext, - // price: Option, - // ) -> anyhow::Result<()>; - - // // TODO: separate traits dependening on strategy - // fn update_strategy(&self) -> UpdateStrategy { - // UpdateStrategy::UpdateInPlace - // } - - // async fn update( - // &mut self, - // context: &mut ResourceManagerContext, - // price: Option, - // ) -> anyhow::Result<()>; - - async fn delete(&mut self, context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()>; - - async fn price( - &mut self, - context: &mut ResourceGraphEvaluatorContext, - ) -> anyhow::Result>; - - async fn create( - &mut self, - context: &mut ResourceGraphEvaluatorContext, - price: Option, - ) -> anyhow::Result<()>; - - fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a>; -} - -// TODO: simplify - just implement a noop update method for resources that use the recreate strategy -pub enum UpdateStrategy<'a> { - Update(&'a mut dyn UpdatableResource), - Recreate, -} - -#[async_trait] -#[enum_dispatch] -pub trait UpdatableResource: ManagedResource { - async fn update(&mut self, context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()>; -} - -#[enum_dispatch(Resource, ManagedResource)] -#[derive(Debug)] -pub enum ResourceDispatch { - PlaceResource, - ExperienceResource, -} - -fn x(r: ResourceDispatch) {} diff --git a/mantle/rbx_mantle/src/resources/place.rs b/mantle/rbx_mantle/src/resources/place.rs deleted file mode 100644 index 6ca4512..0000000 --- a/mantle/rbx_mantle/src/resources/place.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::sync::{RwLock, Weak}; - -use async_trait::async_trait; -use derive_resource::Resource; -use rbx_api::models::AssetId; - -use crate::resource_graph_v2::evaluator::ResourceGraphEvaluatorContext; - -use super::{ - experience::ExperienceResource, ManagedResource, Resource, ResourceId, ResourceInputs, - ResourceOutputs, UpdateStrategy, WeakResourceRef, -}; - -#[derive(Debug)] -pub struct PlaceInputs { - pub is_start: bool, -} -impl ResourceInputs for PlaceInputs {} - -#[derive(Debug)] -pub enum PlaceOutputs { - Data { asset_id: AssetId }, - Empty, -} -impl ResourceOutputs for PlaceOutputs { - fn has_outputs(&self) -> bool { - match self { - Self::Empty => false, - _ => true, - } - } -} - -#[derive(Resource, Debug)] -pub struct PlaceResource { - pub id: ResourceId, - pub inputs: PlaceInputs, - pub outputs: PlaceOutputs, - - #[dependency] - pub experience: Weak>, -} - -#[async_trait] -impl ManagedResource for PlaceResource { - async fn delete(&mut self, _context: &mut ResourceGraphEvaluatorContext) -> anyhow::Result<()> { - todo!() - } - - async fn price( - &mut self, - _context: &mut ResourceGraphEvaluatorContext, - ) -> anyhow::Result> { - todo!() - } - - async fn create( - &mut self, - _context: &mut ResourceGraphEvaluatorContext, - _price: Option, - ) -> anyhow::Result<()> { - todo!() - } - - fn update_strategy<'a>(&'a mut self) -> UpdateStrategy<'a> { - UpdateStrategy::Recreate - } -} diff --git a/mantle/rbx_mantle/src/resources_v2/mod.rs b/mantle/rbx_mantle/src/resources_v2/mod.rs index 5507e5b..893f20e 100644 --- a/mantle/rbx_mantle/src/resources_v2/mod.rs +++ b/mantle/rbx_mantle/src/resources_v2/mod.rs @@ -6,7 +6,7 @@ use std::fmt::Debug; use async_trait::async_trait; use derive_resource::ResourceGroup; -use crate::resource_graph_v3::ResourceGraph; +use crate::resource_graph_v2::ResourceGraph; use self::{experience::Experience, place::Place}; diff --git a/mantle/rbx_mantle/src/resources_v2/notes.txt b/mantle/rbx_mantle/src/resources_v2/notes.txt deleted file mode 100644 index 6b8ab82..0000000 --- a/mantle/rbx_mantle/src/resources_v2/notes.txt +++ /dev/null @@ -1,113 +0,0 @@ -current_graph - initially: empty - later: graph containing resources with inputs, outputs, and dependencies -desired_graph - graph containing resources with inputs, outputs, and dependencies -next_graph - graph containing resources with inputs, outputs, and dependencies - -should resources be mutable?? - we should leave resources as-is within each graph and build up each graph independently - delete: - experience - place - - experience has dependents (place) - cannot be deleted yet - place has no dependents - delete place - place is no longer a dependent of experience - delete it - - this is achieved simply by deleting in reverse topological order - - when deleting; - call delete function - don't add to next_graph - - create: - experience - place - - place has dependencies (experience) - cannot be created yet - experience has no dependencies - create experience - place has created experiences - create it - - this is achieved simply by creating in topological order - - when creating: - call create function - add to next_graph: resource with the same inputs and dependencies, but with the outputs from the creation - !!we can reference dependencies within the same graph!! - dependency references are read-only (in fact can be written as copies!) - and.. resource actions can be immutable! - - update: - when updating (update-in-place strategy): - call update function - add to next_graph: resource with the same inputs and dependencies, but with the outputs from the update - - when updating (recreate strategy): - call delete function - call create function - add to next_graph: resource with the same inputs and dependencies, but with the outputs from the creation - -also: using enums will be easier (but introduces duplication... which can be solved with macros!) - - - - -Experience { - inputs: ExperienceInputs - outputs: Option - dependencies: [] -} - -Place { - inputs: PlaceInputs - outputs: Option - dependencies: [Experience] -} - -current_graph: - empty - -desired_graph: - ex = Experience(inputs, None, []) - insert(ex) - insert(Place(inputs, None, [ex])) - -next_graph: - nothing to delete - create experience: - let mut next_ex = Experience(ex.inputs, None, next_graph.get_deps(ex.deps)) - next_ex.create() // populates outputs - next_graph.insert(next_ex) - create place: - let mut next_place = Place(pl.inputs, None, next_graph.get_deps(pl.deps)) - next_place.create() // populates outputs - next_graph.insert(next_place) - - -later: - delete place: - let mut next_place = Place(pl.inputs, pl.outputs, next_graph.get_deps(pl.deps)) - next_place.delete() // deletes outputs (not important) - // don't add to next_graph - update place: - let mut next_place = Place(pl.inputs, pl.outputs, next_graph.get_deps(pl.deps)) - next_place.update() // populates new outputs - next_graph.insert(next_place) - recreate place: - let mut next_place = Place(pl.inputs, pl.outputs, next_graph.get_deps(pl.deps)) - next_place.delete() // deletes outputs - next_place.create() // populates new outputs - next_graph.insert(next_place) - -WAIT... can we just... CLONE it? And then manually SET deps? (i.e. a set_dependencies()) - -NEED: - way to load deps from next_graph - pretty easy? - way to construct resource - not so sure... - gonna need some macros I think From 7be5ea221e222184b73a0bb738c2e9edf1498a34 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 21:59:13 -0500 Subject: [PATCH 11/13] remove commented out mods --- mantle/rbx_mantle/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/mantle/rbx_mantle/src/lib.rs b/mantle/rbx_mantle/src/lib.rs index d49f76f..21c6325 100644 --- a/mantle/rbx_mantle/src/lib.rs +++ b/mantle/rbx_mantle/src/lib.rs @@ -1,9 +1,7 @@ pub mod config; pub mod project; pub mod resource_graph; -// pub mod resource_graph_v2; pub mod resource_graph_v2; -// pub mod resources; pub mod resources_v2; pub mod roblox_resource_manager; pub mod state; From 360d5ee3429aa4c354bea66424702ea1aaa81245 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 22:02:15 -0500 Subject: [PATCH 12/13] derive default for resourcegraph --- mantle/rbx_mantle/src/resource_graph_v2/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mantle/rbx_mantle/src/resource_graph_v2/mod.rs b/mantle/rbx_mantle/src/resource_graph_v2/mod.rs index e884ab3..77610c8 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2/mod.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/mod.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use crate::resources_v2::{RbxResource, ResourceGroup}; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ResourceGraph { resources: BTreeMap, } @@ -20,10 +20,6 @@ impl ResourceGraph { } } - pub fn default() -> Self { - Self::new(vec![]) - } - pub fn contains(&self, resource_id: &str) -> bool { self.resources.contains_key(resource_id) } From 35d35cf8410047d61190958fb4a3361ea03d1328 Mon Sep 17 00:00:00 2001 From: Blake Mealey Date: Sat, 15 Apr 2023 22:08:25 -0500 Subject: [PATCH 13/13] move fallible logic to before graph evaluation --- .../src/resource_graph_v2/evaluator.rs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs index 568eb19..d59e382 100644 --- a/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs +++ b/mantle/rbx_mantle/src/resource_graph_v2/evaluator.rs @@ -1,4 +1,4 @@ -use crate::resources_v2::ResourceGroup; +use crate::resources_v2::{RbxResource, ResourceGroup}; use super::{evaluator_results::EvaluatorResults, ResourceGraph}; @@ -29,17 +29,21 @@ impl<'a> Evaluator<'a> { )); } - self.delete_removed_resources().await?; - self.create_or_update_added_or_changed_resources().await?; + // ensure that both graphs are valid before we attempt to evaluate them + let mut previous_resources = self.previous_graph.topological_order()?; + previous_resources.reverse(); + + let desired_resources = self.desired_graph.topological_order()?; + + self.delete_removed_resources(previous_resources).await; + self.create_or_update_added_or_changed_resources(desired_resources) + .await; Ok((&self.results, &self.next_graph)) } - async fn delete_removed_resources(&mut self) -> anyhow::Result<()> { - let mut previous_resources = self.previous_graph.topological_order()?; - previous_resources.reverse(); - - for resource in previous_resources.into_iter() { + async fn delete_removed_resources(&mut self, previous_resources_reverse: Vec<&RbxResource>) { + for resource in previous_resources_reverse.into_iter() { if self.desired_graph.contains(resource.id()) { continue; } @@ -56,13 +60,12 @@ impl<'a> Evaluator<'a> { } } } - - Ok(()) } - async fn create_or_update_added_or_changed_resources(&mut self) -> anyhow::Result<()> { - let desired_resources = self.desired_graph.topological_order()?; - + async fn create_or_update_added_or_changed_resources( + &mut self, + desired_resources: Vec<&RbxResource>, + ) { for desired_resource in desired_resources.into_iter() { if let Some(previous_resource) = self.previous_graph.get(desired_resource.id()) { match desired_resource.next(&self.previous_graph, &self.next_graph) { @@ -100,8 +103,6 @@ impl<'a> Evaluator<'a> { } } } - - Ok(()) } }