From 1a071e44a995a7c4ae5ba91b62cf9a3f247e2105 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Sat, 31 May 2025 22:38:38 +0200 Subject: [PATCH] MaterialManager: with O(n) complexity --- src/core/render/material_manager.rs | 281 +++++++++++++++------------- 1 file changed, 155 insertions(+), 126 deletions(-) diff --git a/src/core/render/material_manager.rs b/src/core/render/material_manager.rs index dd8f657..e522ebf 100644 --- a/src/core/render/material_manager.rs +++ b/src/core/render/material_manager.rs @@ -1,14 +1,10 @@ use std::{ any::TypeId, - collections::HashMap, error::Error, - sync::{Arc, RwLock}, + sync::{Arc, RwLock, RwLockReadGuard}, }; -use vulkano::{ - device::Device, format::Format, memory::allocator::StandardMemoryAllocator, - pipeline::Pipeline as VulkanoPipeline, -}; +use vulkano::{device::Device, format::Format, memory::allocator::StandardMemoryAllocator}; pub trait Pipeline { fn load( @@ -32,23 +28,31 @@ pub trait Material { ) -> Result<(), Box>; } +pub enum MaterialState { + Loading, + Loaded, +} + +pub enum MaterialError { + PipelineNotFound, +} + pub struct MaterialManager { device: Arc, memory_allocator: Arc, swapchain_format: Format, depth_format: Format, - // cached pipelines by Pipeline ID - loading_pipelines: Arc>>>>, - loaded_pipelines: Arc>>>>, + pipelines_id: Arc>>, + pipelines_state: Arc>>>>, + pipelines: Arc>>>>, - // cached materials by Material ID - loading_materials: Arc>>>>, - loaded_materials: Arc>>>>, - material_pipeline_map: Arc>>, - - // Store all materials marked as renderable when pipeline and material are loaded - renderable_materials: Arc>>>>, + materials_id: Arc>>, + materials_pipeline_id: Arc>>, + materials_pipeline: Arc>>>>, + materials_pipeline_state: Arc>>>>, + materials_state: Arc>>>>, + materials: Arc>>>>, } impl MaterialManager { @@ -63,37 +67,75 @@ impl MaterialManager { memory_allocator, swapchain_format, depth_format, - loading_pipelines: Arc::new(RwLock::new(HashMap::new())), - loaded_pipelines: Arc::new(RwLock::new(HashMap::new())), - loading_materials: Arc::new(RwLock::new(HashMap::new())), - loaded_materials: Arc::new(RwLock::new(HashMap::new())), - material_pipeline_map: Arc::new(RwLock::new(HashMap::new())), - renderable_materials: Arc::new(RwLock::new(HashMap::new())), + pipelines_id: Arc::new(RwLock::new(Vec::new())), + pipelines_state: Arc::new(RwLock::new(Vec::new())), + pipelines: Arc::new(RwLock::new(Vec::new())), + materials_id: Arc::new(RwLock::new(Vec::new())), + materials_pipeline_id: Arc::new(RwLock::new(Vec::new())), + materials_pipeline: Arc::new(RwLock::new(Vec::new())), + materials_pipeline_state: Arc::new(RwLock::new(Vec::new())), + materials_state: Arc::new(RwLock::new(Vec::new())), + materials: Arc::new(RwLock::new(Vec::new())), } } pub fn add_pipeline(&self) { let type_id = TypeId::of::

(); - self.loading_pipelines - .write() - .expect("Failed to get write lock to loading_pipelines") - .insert(type_id, Arc::new(RwLock::new(P::default()))); + let pipeline = Arc::new(RwLock::new(P::default())); + + let mut pipelines_id = self.pipelines_id.write().unwrap(); + let mut pipelines_state = self.pipelines_state.write().unwrap(); + let mut pipelines = self.pipelines.write().unwrap(); + + pipelines_id.push(type_id); + pipelines_state.push(Arc::new(RwLock::new(MaterialState::Loading))); + pipelines.push(pipeline.clone()); } - pub fn add_material(&self) { - let type_id = TypeId::of::(); + pub fn add_material(&self) -> Result<(), MaterialError> { let pipeline_id = M::pipeline_type_id(); + + let pipeline_result = { + let pipelines_id = self.pipelines_id.read().unwrap(); + let pipelines_state = self.pipelines_state.read().unwrap(); + let pipelines = self.pipelines.read().unwrap(); + + pipelines_id + .iter() + .zip(pipelines.iter()) + .zip(pipelines_state.iter()) + .find(|((id, _), _)| *id == &pipeline_id) + .map(|((_, pipeline), state)| (pipeline.clone(), state.clone())) + }; + + let (pipeline, pipeline_state) = match pipeline_result { + Some(pipeline) => pipeline, + None => { + tracing::error!( + "Pipeline with id {pipeline_id:?} not found, please add it before adding a material" + ); + return Err(MaterialError::PipelineNotFound); + } + }; + + let type_id = TypeId::of::(); let material = Arc::new(RwLock::new(M::default())); - self.loading_materials - .write() - .expect("Failed to get write lock to loading_materials") - .insert(type_id, material.clone()); + let mut materials_id = self.materials_id.write().unwrap(); + let mut materials_pipeline_id = self.materials_pipeline_id.write().unwrap(); + let mut materials_pipeline = self.materials_pipeline.write().unwrap(); + let mut materials_pipeline_state = self.materials_pipeline_state.write().unwrap(); + let mut materials_state = self.materials_state.write().unwrap(); + let mut materials = self.materials.write().unwrap(); - self.material_pipeline_map - .write() - .expect("Failed to get write lock to material_pipeline_map") - .insert(type_id, pipeline_id); + materials_id.push(type_id); + materials_pipeline_id.push(pipeline_id); + materials_pipeline.push(pipeline.clone()); + materials_pipeline_state.push(pipeline_state.clone()); + materials_state.push(Arc::new(RwLock::new(MaterialState::Loading))); + materials.push(material.clone()); + + Ok(()) } pub fn update_swapchain_format(&mut self, swapchain_format: Format) { @@ -105,114 +147,101 @@ impl MaterialManager { self.mark_all_pipelines_as_loading(); } + fn mark_all_pipelines_as_loading(&self) { + let pipelines_state = self.pipelines_state.write().unwrap(); + + for state in pipelines_state.iter() { + let mut state = state.write().unwrap(); + *state = MaterialState::Loading; + } + } + fn load_pipelines(&self) { - let mut loaded_pipelines = HashMap::>>::new(); + let pipelines_state = self.pipelines_state.read().unwrap(); + let pipelines = self.pipelines.read().unwrap(); - { - let loading_pipelines = self.loading_pipelines.read().unwrap(); + let iter = pipelines_state + .iter() + .zip(pipelines.iter()) + .filter(|(state, _)| { + let state = state.read().unwrap(); + matches!(*state, MaterialState::Loading) + }); - for (type_id, pipeline) in loading_pipelines.iter() { - let result = { - let mut pipeline = pipeline.write().unwrap(); - pipeline.load( - &self.device, - &self.memory_allocator, - self.swapchain_format, - self.depth_format, - ) - }; - match result { - Ok(_) => { - loaded_pipelines.insert(type_id.clone(), pipeline.clone()); - } - Err(e) => { - tracing::error!("Failed to load pipeline: {e}"); - } + for (state, pipeline) in iter { + let mut pipeline = pipeline.write().unwrap(); + let result = pipeline.load( + &self.device, + &self.memory_allocator, + self.swapchain_format, + self.depth_format, + ); + + match result { + Ok(_) => { + let mut state = state.write().unwrap(); + *state = MaterialState::Loaded; + } + Err(e) => { + tracing::error!("Failed to load pipeline: {e}"); } } } - - let loaded_pipeline_keys = loaded_pipelines.keys().collect::>(); - - { - let mut loading_pipelines = self.loading_pipelines.write().unwrap(); - let loaded_materials = self.loaded_materials.read().unwrap(); - let mut renderable_materials = self.renderable_materials.write().unwrap(); - - for type_id in loaded_pipeline_keys { - loading_pipelines.remove(type_id); - - if loaded_materials.contains_key(type_id) { - renderable_materials.insert( - type_id.clone(), - loaded_materials.get(type_id).unwrap().clone(), - ); - } - } - } - - self.loaded_pipelines - .write() - .expect("Failed to get write lock to loaded_pipelines") - .extend(loaded_pipelines); } fn load_materials(&self) { - let mut loaded_materials = HashMap::>>::new(); + let materials_state = self.materials_state.read().unwrap(); + let materials = self.materials.read().unwrap(); - { - let loading_materials = self.loading_materials.read().unwrap(); + let iter = materials_state + .iter() + .zip(materials.iter()) + .filter(|(state, _)| { + let state = state.read().unwrap(); + matches!(*state, MaterialState::Loading) + }); - for (type_id, material) in loading_materials.iter() { - let result = { - let mut material = material.write().unwrap(); - material.load(&self.device, &self.memory_allocator) - }; + for (state, material) in iter { + let mut material = material.write().unwrap(); + let result = material.load(&self.device, &self.memory_allocator); - match result { - Ok(_) => { - loaded_materials.insert(type_id.clone(), material.clone()); - } - Err(e) => { - tracing::error!("Failed to load material: {e}"); - } + match result { + Ok(_) => { + let mut state = state.write().unwrap(); + *state = MaterialState::Loaded; + } + Err(e) => { + tracing::error!("Failed to load material: {e}"); } } } - - { - let mut loading_materials = self.loading_materials.write().unwrap(); - let loaded_pipelines = self.loaded_pipelines.read().unwrap(); - let material_pipeline_map = self.material_pipeline_map.read().unwrap(); - let mut renderable_materials = self.renderable_materials.write().unwrap(); - - for (type_id, material) in loaded_materials.iter() { - loading_materials.remove(type_id); - - let pipeline_id = material_pipeline_map.get(type_id).unwrap().clone(); - - if loaded_pipelines.contains_key(&pipeline_id) { - renderable_materials.insert(type_id.clone(), material.clone()); - } - } - } - - self.loaded_materials - .write() - .expect("Failed to get write lock to loaded_materials") - .extend(loaded_materials); } - fn mark_all_pipelines_as_loading(&self) { - let mut loading_pipelines = self.loading_pipelines.write().unwrap(); - let mut loaded_pipelines = self.loaded_pipelines.write().unwrap(); - let mut renderable_materials = self.renderable_materials.write().unwrap(); + fn render_materials(&self, f: F) + where + F: Fn(RwLockReadGuard<'_, dyn Material>, RwLockReadGuard<'_, dyn Pipeline>), + { + let materials = self.materials.read().unwrap(); + let materials_state = self.materials_state.read().unwrap(); + let materials_pipeline = self.materials_pipeline.read().unwrap(); + let materials_pipeline_state = self.materials_pipeline_state.read().unwrap(); - for (type_id, pipeline) in loaded_pipelines.iter() { - loading_pipelines.insert(type_id.clone(), pipeline.clone()); - } + materials + .iter() + .zip(materials_state.iter()) + .zip(materials_pipeline.iter()) + .zip(materials_pipeline_state.iter()) + .filter(|(((_, material_state), _), pipeline_state)| { + let material_state = material_state.read().unwrap(); + let pipeline_state = pipeline_state.read().unwrap(); + matches!(*material_state, MaterialState::Loaded) + && matches!(*pipeline_state, MaterialState::Loaded) + }) + .for_each(|(((material, _), pipeline), _)| { + let material = material.read().unwrap(); + let pipeline = pipeline.read().unwrap(); - loaded_pipelines.clear(); - renderable_materials.clear(); // Mark all materials as loading + f(material, pipeline); + }); } }