From 6af3f0e308bc6b4299145e72c6da07e2fb1413ff Mon Sep 17 00:00:00 2001 From: AnthonyTornetta Date: Thu, 7 Sep 2023 15:11:59 -0400 Subject: [PATCH] Getting lods to be parallel --- Cargo.lock | 1 + cosmos_client/Cargo.toml | 5 +- cosmos_client/src/asset/asset_loading.rs | 33 ++- cosmos_client/src/block/lighting.rs | 2 +- cosmos_client/src/materials/mod.rs | 1 + cosmos_client/src/rendering/lod_renderer.rs | 270 +++++++++++------- cosmos_client/src/rendering/mod.rs | 9 +- .../src/structure/planet/biosphere.rs | 2 +- cosmos_client/src/structure/planet/lod.rs | 5 + cosmos_core/src/block/hardness/mod.rs | 2 +- cosmos_core/src/item/mod.rs | 1 + cosmos_core/src/physics/block_colliders.rs | 8 +- cosmos_core/src/physics/structure_physics.rs | 7 + cosmos_core/src/registry/identifiable.rs | 2 +- cosmos_core/src/registry/many_to_one.rs | 49 +++- cosmos_core/src/registry/mod.rs | 47 ++- 16 files changed, 315 insertions(+), 129 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8033685af..28e77de6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1500,6 +1500,7 @@ dependencies = [ "bigdecimal", "bincode", "cosmos_core", + "futures-lite", "image", "local-ip-address", "rayon", diff --git a/cosmos_client/Cargo.toml b/cosmos_client/Cargo.toml index 75dadd867..336b68352 100644 --- a/cosmos_client/Cargo.toml +++ b/cosmos_client/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cosmos_core = { version = "0.0.4", path = "../cosmos_core", features = [ "client" ] } +cosmos_core = { version = "0.0.4", path = "../cosmos_core", features = [ + "client", +] } bevy = { workspace = true } bevy_renet = { workspace = true } @@ -15,6 +17,7 @@ bincode = { workspace = true } local-ip-address = { workspace = true } bevy_rapier3d = { workspace = true } bigdecimal = { workspace = true } +futures-lite = { workspace = true } serde_json = { workspace = true } image = { workspace = true } diff --git a/cosmos_client/src/asset/asset_loading.rs b/cosmos_client/src/asset/asset_loading.rs index 486b26322..655a09d5f 100644 --- a/cosmos_client/src/asset/asset_loading.rs +++ b/cosmos_client/src/asset/asset_loading.rs @@ -2,7 +2,10 @@ //! //! This also combines the textures into one big atlas. -use std::fs; +use std::{ + fs, + sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}, +}; use bevy::{ prelude::*, @@ -38,7 +41,7 @@ struct AssetsLoadingID(usize); #[derive(Resource, Debug, Default)] struct AssetsLoading(Vec); -#[derive(Resource, Reflect, Debug)] +#[derive(Resource, Reflect, Debug, Clone)] /// This stores the texture atlas for all blocks in the game. /// /// Eventually this will be redone to allow for multiple atlases, but for now this works fine. @@ -56,6 +59,19 @@ pub struct MainAtlas { padding: u32, } +#[derive(Resource, Debug, Clone)] +/// This stores the texture atlas for all blocks in the game. +/// +/// Eventually this will be redone to allow for multiple atlases, but for now this works fine. +pub struct ReadOnlyMainAtlas(Arc>); + +impl ReadOnlyMainAtlas { + /// Locks the atlas for use on this thread + pub fn atlas<'a>(&'a self) -> RwLockReadGuard<'a, MainAtlas> { + self.0.as_ref().read().expect("Failed to lock atlas") + } +} + impl MainAtlas { #[inline] /// Returns the UV coordinates for the texture atlas given the block's index @@ -256,12 +272,15 @@ fn check_assets_ready( let texture = atlas.texture.clone(); - commands.insert_resource(MainAtlas { + let main_atlas = MainAtlas { material: material_handle, unlit_material: unlit_material_handle, atlas, padding: PADDING, - }); + }; + + commands.insert_resource(main_atlas.clone()); + commands.insert_resource(ReadOnlyMainAtlas(Arc::new(RwLock::new(main_atlas)))); let illuminated_material_handle = materials.add(StandardMaterial { base_color_texture: Some(texture), @@ -294,7 +313,7 @@ fn check_assets_ready( } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Contains information that links the block faces to their texture indices. /// /// This could also link non-face imformation to their texture indices. @@ -312,7 +331,7 @@ impl BlockTextureIndicies { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Links blocks to their correspoding atlas index. pub struct BlockTextureIndex { indices: BlockTextureIndicies, @@ -363,7 +382,7 @@ struct ReadBlockInfo { model: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Every block will have information about how to render it -- even air pub struct BlockRenderingInfo { /// This maps textures ids to the various parts of its model. diff --git a/cosmos_client/src/block/lighting.rs b/cosmos_client/src/block/lighting.rs index f67aa5f04..4f9e812e3 100644 --- a/cosmos_client/src/block/lighting.rs +++ b/cosmos_client/src/block/lighting.rs @@ -27,7 +27,7 @@ pub struct BlockLightProperties { pub shadows_disabled: bool, } -#[derive(Debug, Reflect, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Reflect, Default, Serialize, Deserialize)] /// This links up a block to its block light properties pub struct BlockLighting { /// The properties this block has diff --git a/cosmos_client/src/materials/mod.rs b/cosmos_client/src/materials/mod.rs index 78633f9c6..7f3c8a7c4 100644 --- a/cosmos_client/src/materials/mod.rs +++ b/cosmos_client/src/materials/mod.rs @@ -11,6 +11,7 @@ use crate::{ state::game_state::GameState, }; +#[derive(Debug, Clone)] /// An identifiable `StandardMaterial` pub struct CosmosMaterial { /// The handle to the bevy `StandardMaterial` diff --git a/cosmos_client/src/rendering/lod_renderer.rs b/cosmos_client/src/rendering/lod_renderer.rs index af39d7ea3..ac793e150 100644 --- a/cosmos_client/src/rendering/lod_renderer.rs +++ b/cosmos_client/src/rendering/lod_renderer.rs @@ -1,9 +1,20 @@ -use std::{f32::consts::PI, sync::Mutex}; +use std::{f32::consts::PI, mem::swap, sync::Mutex}; + +use bevy::{ + prelude::*, + render::primitives::Aabb, + tasks::{AsyncComputeTaskPool, Task}, + utils::HashMap, +}; +use futures_lite::future; -use bevy::{prelude::*, render::primitives::Aabb, utils::HashMap}; use cosmos_core::{ block::{Block, BlockFace}, - registry::{identifiable::Identifiable, many_to_one::ManyToOneRegistry, Registry}, + registry::{ + identifiable::Identifiable, + many_to_one::{ManyToOneRegistry, ReadOnlyManyToOneRegistry}, + ReadOnlyRegistry, Registry, + }, structure::{ block_storage::BlockStorer, chunk::{CHUNK_DIMENSIONS, CHUNK_DIMENSIONSF}, @@ -17,12 +28,12 @@ use cosmos_core::{ use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; use crate::{ - asset::asset_loading::{BlockTextureIndex, MainAtlas}, + asset::asset_loading::{BlockTextureIndex, MainAtlas, ReadOnlyMainAtlas}, materials::CosmosMaterial, state::game_state::GameState, }; -use super::{BlockMeshRegistry, CosmosMeshBuilder, MeshBuilder, MeshInformation}; +use super::{BlockMeshRegistry, CosmosMeshBuilder, MeshBuilder, MeshInformation, ReadOnlyBlockMeshRegistry}; #[derive(Debug)] struct MeshMaterial { @@ -405,7 +416,7 @@ fn find_non_dirty(lod: &Lod, offset: Vec3, to_process: &mut Vec, scale: f3 Lod::None => {} Lod::Children(children) => { children.iter().enumerate().for_each(|(i, c)| { - let s4 = scale / 4.0; + let s4: f32 = scale / 4.0; let offset = match i { 0 => offset + Vec3::new(-s4, -s4, -s4), @@ -430,125 +441,184 @@ fn find_non_dirty(lod: &Lod, offset: Vec3, to_process: &mut Vec, scale: f3 }; } -/// Performance hot spot -fn monitor_lods_needs_rendered_system( +#[derive(Debug)] +struct RenderingLod(Task<(Vec, HashMap>)>); + +fn poll_generating_lods( mut commands: Commands, - atlas: Res, - mut meshes: ResMut>, - blocks: Res>, - materials: Res>, - meshes_registry: Res, chunk_meshes_query: Query<&LodMeshes>, - block_textures: Res>, + mut meshes: ResMut>, transform_query: Query<&Transform>, - - mut lods_needed: Query<(Entity, &mut Lod, &Structure), Changed>, + mut rendering_lods: ResMut, ) { - // by making the Vec an Option I can take ownership of it later, which I cannot do with - // just a plain Mutex. - // https://stackoverflow.com/questions/30573188/cannot-move-data-out-of-a-mutex - let to_process: Mutex>> = Mutex::new(Some(Vec::new())); - let to_keep: Mutex>>> = Mutex::new(Some(HashMap::new())); + let mut todo = Vec::with_capacity(rendering_lods.0.capacity()); - let mut todo = Vec::from_iter(lods_needed.iter_mut()); + swap(&mut rendering_lods.0, &mut todo); - // Render lods in parallel - todo.par_iter_mut().for_each(|(entity, lod, structure)| { - let mut non_dirty = vec![]; - find_non_dirty(lod, Vec3::ZERO, &mut non_dirty, structure.block_dimensions().x as f32); - - to_keep - .lock() - .expect("failed to lock mutex") - .as_mut() - .unwrap() - .insert(*entity, non_dirty); - - recursively_process_lod( - lod.as_mut(), - Vec3::ZERO, - &to_process, - *entity, - &atlas, - &blocks, - &materials, - &meshes_registry, - &block_textures, - structure.chunk_dimensions().x as f32, - ); - }); - - let to_process_chunks = to_process.lock().unwrap().take().unwrap(); - - let mut ent_meshes = HashMap::new(); - for (entity, chunk_mesh, offset) in to_process_chunks { - if !ent_meshes.contains_key(&entity) { - ent_meshes.insert(entity, vec![]); - } - ent_meshes.get_mut(&entity).expect("Just added").push((chunk_mesh, offset)); - } + for (entity, mut rendering_lod) in todo { + if let Some((to_keep_locations, ent_meshes)) = future::block_on(future::poll_once(&mut rendering_lod.0)) { + let mut structure_meshes_component = LodMeshes::default(); + let mut entities_to_add = Vec::new(); - for (entity, lod_meshes) in ent_meshes { - let old_mesh_entities = chunk_meshes_query.get(entity).map(|x| x.0.clone()).unwrap_or_default(); + for (entity, lod_meshes) in ent_meshes { + let old_mesh_entities = chunk_meshes_query.get(entity).map(|x| x.0.clone()).unwrap_or_default(); - let to_keep_locations = to_keep.lock().unwrap().take().unwrap_or_default(); + for (lod_mesh, offset) in lod_meshes { + for mesh_material in lod_mesh.mesh_materials { + let mesh = meshes.add(mesh_material.mesh); - let to_keep_locations = to_keep_locations.get(&entity); + let s = (CHUNK_DIMENSIONS / 2) as f32 * lod_mesh.scale; - let mut entities_to_add = Vec::new(); + let ent = commands + .spawn(( + PbrBundle { + mesh, + material: mesh_material.material, + transform: Transform::from_translation(offset), + ..Default::default() + }, + // Remove this once https://github.com/bevyengine/bevy/issues/4294 is done (bevy 0.12 released) + Aabb::from_min_max(Vec3::new(-s, -s, -s), Vec3::new(s, s, s)), + )) + .id(); - let mut structure_meshes_component = LodMeshes::default(); + entities_to_add.push(ent); - for (lod_mesh, offset) in lod_meshes { - for mesh_material in lod_mesh.mesh_materials { - let mesh = meshes.add(mesh_material.mesh); + structure_meshes_component.0.push(ent); + } + } - let s = (CHUNK_DIMENSIONS / 2) as f32 * lod_mesh.scale; + println!("{to_keep_locations:?}"); - let ent = commands - .spawn(( - PbrBundle { - mesh, - material: mesh_material.material, - transform: Transform::from_translation(offset), - ..Default::default() - }, - // Remove this once https://github.com/bevyengine/bevy/issues/4294 is done (bevy 0.12 released) - Aabb::from_min_max(Vec3::new(-s, -s, -s), Vec3::new(s, s, s)), - )) - .id(); + // Any dirty entities are useless now, so kill them + for mesh_entity in old_mesh_entities { + let is_clean = transform_query + .get(mesh_entity) + .map(|transform| to_keep_locations.contains(&transform.translation)) + .unwrap_or(false); + if is_clean { + structure_meshes_component.0.push(mesh_entity); + } else { + // commands.entity(mesh_entity).log_components(); + commands.entity(mesh_entity).despawn_recursive(); + } + } + } - entities_to_add.push(ent); + let mut entity_commands = commands.entity(entity); - structure_meshes_component.0.push(ent); + for ent in entities_to_add { + entity_commands.add_child(ent); } + + entity_commands + // .insert(meshes.add(chunk_mesh.mesh)) + .insert(structure_meshes_component); + } else { + rendering_lods.0.push((entity, rendering_lod)) } + } +} - // Any dirty entities are useless now, so kill them - for mesh_entity in old_mesh_entities { - let is_clean = transform_query - .get(mesh_entity) - .map(|transform| to_keep_locations.map(|x| x.contains(&transform.translation)).unwrap_or(false)) - .unwrap_or(false); - if is_clean { - structure_meshes_component.0.push(mesh_entity); - } else { - commands.entity(mesh_entity).despawn_recursive(); - } +fn make_clean(lod: &mut Lod) { + match lod { + Lod::None => {} + Lod::Children(children) => { + children.iter_mut().for_each(make_clean); + } + Lod::Single(_, dirty) => { + *dirty = false; } + } +} - let mut entity_commands = commands.entity(entity); +/// Performance hot spot +fn monitor_lods_needs_rendered_system( + atlas: Res, + blocks: Res>, + materials: Res>, + meshes_registry: Res, + block_textures: Res>, + mut lods_needed: Query<(Entity, &mut Lod, &Structure), Changed>, + mut rendering_lods: ResMut, +) { + let thread_pool = AsyncComputeTaskPool::get(); + for (entity, mut lod, structure) in lods_needed.iter_mut() { + // for (entity, _, _, _) in todo.iter() { + // if let Some((idx, _)) = rendering_lods.iter().enumerate().find(|(_, r_lod)| r_lod.0 == *entity) { + // // Tasks are auto-cancelled when they are dropped + // rendering_lods.swap_remove(idx); + // } + // } - for ent in entities_to_add { - entity_commands.add_child(ent); - } + let mut non_dirty = vec![]; + find_non_dirty(&lod, Vec3::ZERO, &mut non_dirty, structure.block_dimensions().x as f32); - entity_commands - // .insert(meshes.add(chunk_mesh.mesh)) - .insert(structure_meshes_component); + let blocks = blocks.clone(); + let block_textures = block_textures.clone(); + let materials = materials.clone(); + let meshes_registry = meshes_registry.clone(); + let atlas = atlas.clone(); + + let chunk_dimensions = structure.chunk_dimensions().x; + + let mut cloned_lod = lod.clone(); + + make_clean(&mut lod); + + let task = thread_pool.spawn(async move { + // by making the Vec an Option I can take ownership of it later, which I cannot do with + // just a plain Mutex. + // https://stackoverflow.com/questions/30573188/cannot-move-data-out-of-a-mutex + let to_process: Mutex>> = Mutex::new(Some(Vec::new())); + + let blocks = blocks.registry(); + let block_textures = block_textures.registry(); + let materials = materials.registry(); + let meshes_registry = meshes_registry.registry(); + let atlas = atlas.atlas(); + + recursively_process_lod( + &mut cloned_lod, + Vec3::ZERO, + &to_process, + entity, + &atlas, + &blocks, + &materials, + &meshes_registry, + &block_textures, + chunk_dimensions as f32, + ); + + let to_process_chunks = to_process.lock().unwrap().take().unwrap(); + + let mut ent_meshes = HashMap::new(); + for (entity, chunk_mesh, offset) in to_process_chunks { + if !ent_meshes.contains_key(&entity) { + ent_meshes.insert(entity, vec![]); + } + ent_meshes.get_mut(&entity).expect("Just added").push((chunk_mesh, offset)); + } + + (non_dirty, ent_meshes) + }); + + rendering_lods.push((entity, RenderingLod(task))); } } +#[derive(Resource, Debug, Default, Deref, DerefMut)] +struct RenderingLods(Vec<(Entity, RenderingLod)>); + +fn count_entities(query: Query) { + println!("# ents: {}", query.iter().len()); +} + pub(super) fn register(app: &mut App) { - app.add_systems(Update, (monitor_lods_needs_rendered_system).run_if(in_state(GameState::Playing))); + app.add_systems( + Update, + (monitor_lods_needs_rendered_system, poll_generating_lods, count_entities).run_if(in_state(GameState::Playing)), + ) + .insert_resource(RenderingLods::default()); } diff --git a/cosmos_client/src/rendering/mod.rs b/cosmos_client/src/rendering/mod.rs index adec2cf33..e94f63ce0 100644 --- a/cosmos_client/src/rendering/mod.rs +++ b/cosmos_client/src/rendering/mod.rs @@ -10,7 +10,7 @@ use cosmos_core::{ block::{Block, BlockFace}, registry::{ identifiable::Identifiable, - many_to_one::{self, ManyToOneRegistry}, + many_to_one::{self, ManyToOneRegistry, ReadOnlyManyToOneRegistry}, Registry, }, }; @@ -114,7 +114,7 @@ impl MeshBuilder for CosmosMeshBuilder { } } -#[derive(Debug)] +#[derive(Debug, Clone)] enum MeshType { /// The mesh is broken up into its 6 faces, which can all be stitched together to create the full mesh /// @@ -124,7 +124,7 @@ enum MeshType { AllFacesMesh(Box), } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Stores all the mesh information for a block pub struct BlockMeshInformation { mesh_info: MeshType, @@ -562,6 +562,9 @@ fn register_block_meshes( /// This is a `ManyToOneRegistry` mapping Blocks to `BlockMeshInformation`. pub type BlockMeshRegistry = ManyToOneRegistry; +/// This is a `ReadOnlyManyToOneRegistry` mapping Blocks to `BlockMeshInformation`. +pub type ReadOnlyBlockMeshRegistry = ReadOnlyManyToOneRegistry; + pub(super) fn register(app: &mut App) { many_to_one::create_many_to_one_registry::(app); structure_renderer::register(app); diff --git a/cosmos_client/src/structure/planet/biosphere.rs b/cosmos_client/src/structure/planet/biosphere.rs index 2a9bbc7b6..e55cc395a 100644 --- a/cosmos_client/src/structure/planet/biosphere.rs +++ b/cosmos_client/src/structure/planet/biosphere.rs @@ -3,7 +3,7 @@ use bevy::prelude::{App, Color, ResMut, Startup}; use cosmos_core::registry::{self, identifiable::Identifiable, Registry}; -#[derive(Debug)] +#[derive(Debug, Clone)] /// Represents the overall color of a biosphere pub struct BiosphereColor { color: Color, diff --git a/cosmos_client/src/structure/planet/lod.rs b/cosmos_client/src/structure/planet/lod.rs index 5f9be9015..6cb3fad80 100644 --- a/cosmos_client/src/structure/planet/lod.rs +++ b/cosmos_client/src/structure/planet/lod.rs @@ -3,6 +3,7 @@ use bevy_renet::renet::RenetClient; use cosmos_core::{ netty::{cosmos_encoder, NettyChannelServer}, structure::lod::{Lod, LodDelta, LodNetworkMessage}, + utils::timer::UtilsTimer, }; use crate::{netty::mapping::NetworkMapping, state::game_state::GameState}; @@ -27,6 +28,8 @@ fn listen_for_new_lods( if let Some(mut ecmds) = commands.get_entity(structure_entity) { let cur_lod = lod_query.get_mut(structure_entity); + let timer = UtilsTimer::start(); + let delta_lod = cosmos_encoder::deserialize::(&lod.serialized_lod).expect("Unable to deserialize lod"); if let Ok(mut cur_lod) = cur_lod { @@ -40,6 +43,8 @@ fn listen_for_new_lods( } ecmds.insert(created); } + + timer.log_duration("Apply LOD changes:"); } } } diff --git a/cosmos_core/src/block/hardness/mod.rs b/cosmos_core/src/block/hardness/mod.rs index 0c97ec649..2a8d8cc1b 100644 --- a/cosmos_core/src/block/hardness/mod.rs +++ b/cosmos_core/src/block/hardness/mod.rs @@ -6,7 +6,7 @@ use crate::registry::{self, identifiable::Identifiable, Registry}; use super::Block; -#[derive(Debug)] +#[derive(Debug, Clone)] /// Used to represent how much damage a block can take before it breaks pub struct BlockHardness { id: u16, diff --git a/cosmos_core/src/item/mod.rs b/cosmos_core/src/item/mod.rs index f68ebc54c..9722d171e 100644 --- a/cosmos_core/src/item/mod.rs +++ b/cosmos_core/src/item/mod.rs @@ -6,6 +6,7 @@ use bevy::prelude::App; use crate::registry::identifiable::Identifiable; +#[derive(Debug, Clone)] /// An item represents something that can be stored in inventories. pub struct Item { unlocalized_name: String, diff --git a/cosmos_core/src/physics/block_colliders.rs b/cosmos_core/src/physics/block_colliders.rs index 3f2f3b9c6..2b9b3b63d 100644 --- a/cosmos_core/src/physics/block_colliders.rs +++ b/cosmos_core/src/physics/block_colliders.rs @@ -8,7 +8,7 @@ use crate::{ registry::{create_registry, identifiable::Identifiable, Registry}, }; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] /// How the collider interacts with the world pub enum BlockColliderMode { /// This type of collider will be physically interact with other colliders @@ -17,7 +17,7 @@ pub enum BlockColliderMode { SensorCollider, } -#[derive(Debug)] +#[derive(Debug, Clone)] /// A custom collider a block may have /// /// Note that this should not go outside the bounds of the block, or breaking/placing will not work when you are targetting this collider. @@ -30,7 +30,7 @@ pub struct CustomCollider { pub mode: BlockColliderMode, } -#[derive(Debug)] +#[derive(Debug, Clone)] /// The type of collider a block has pub enum BlockColliderType { /// Takes an entire block @@ -41,7 +41,7 @@ pub enum BlockColliderType { Empty, } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Determines how a block interacts with its physics environment pub struct BlockCollider { /// What type of collider this is diff --git a/cosmos_core/src/physics/structure_physics.rs b/cosmos_core/src/physics/structure_physics.rs index 1f488e535..73100697b 100644 --- a/cosmos_core/src/physics/structure_physics.rs +++ b/cosmos_core/src/physics/structure_physics.rs @@ -11,6 +11,7 @@ use crate::structure::chunk::{Chunk, ChunkUnloadEvent, CHUNK_DIMENSIONS}; use crate::structure::coordinates::{ChunkBlockCoordinate, ChunkCoordinate, CoordinateType}; use crate::structure::events::ChunkSetEvent; use crate::structure::Structure; +use crate::utils::timer::UtilsTimer; use bevy::prelude::{ Added, App, BuildChildren, Commands, Component, DespawnRecursiveExt, Entity, Event, EventReader, EventWriter, IntoSystemConfigs, Query, Res, Transform, Update, @@ -303,9 +304,15 @@ fn listen_for_new_physics_event( transform_query: Query<&Transform>, mut physics_components_query: Query<&mut ChunkPhysicsParts>, ) { + if event_reader.is_empty() { + return; + } + + let timer = UtilsTimer::start(); let mut to_process = event_reader.iter().collect::>(); to_process.dedup(); + timer.log_duration("Dedup"); // clean up old collider entities for ev in to_process.iter() { diff --git a/cosmos_core/src/registry/identifiable.rs b/cosmos_core/src/registry/identifiable.rs index 52e34b9a0..395389f8b 100644 --- a/cosmos_core/src/registry/identifiable.rs +++ b/cosmos_core/src/registry/identifiable.rs @@ -5,7 +5,7 @@ //! some dummy value like 0. The unlocalized name has to be set by you. /// Represents something that has an internally used numeric id & a globally used unlocalized name. -pub trait Identifiable: Send + Sync { +pub trait Identifiable: Send + Sync + Clone + 'static { /// Returns the internally used id /// /// Make sure the value this returns is the same as the value set by set_numeric_id. diff --git a/cosmos_core/src/registry/many_to_one.rs b/cosmos_core/src/registry/many_to_one.rs index a4711aeba..530f73452 100644 --- a/cosmos_core/src/registry/many_to_one.rs +++ b/cosmos_core/src/registry/many_to_one.rs @@ -4,8 +4,9 @@ //! [`create_many_to_one_registry`] use std::marker::PhantomData; +use std::sync::{Arc, RwLock, RwLockReadGuard}; -use bevy::prelude::{App, Resource}; +use bevy::prelude::{resource_exists_and_changed, App, IntoSystemConfigs, Res, ResMut, Resource, Update}; use bevy::utils::hashbrown::hash_map::Values; use bevy::utils::HashMap; @@ -13,8 +14,8 @@ use super::identifiable::Identifiable; use super::AddLinkError; /// Represents a many to one link -#[derive(Resource, Default, Debug)] -pub struct ManyToOneRegistry { +#[derive(Resource, Default, Debug, Clone)] +pub struct ManyToOneRegistry { values: HashMap, name_to_value_pointer: HashMap, @@ -26,7 +27,7 @@ pub struct ManyToOneRegistry, } -impl ManyToOneRegistry { +impl ManyToOneRegistry { /// Initializes a ManyToOne relationship. /// /// You should use [`create_many_to_one_registry`] instead, unless you don't want this @@ -92,7 +93,43 @@ impl ManyToOneRegi } } +/// This is synced with its corresponding ManyToOneRegistry every frame when it's changed. +/// +/// This is slower than a normal registry, but is usable between threads. +/// +/// Any updates made to this will be overwritten whenever the ManyToOneRegistry changes, so don't change this +/// and expect anything to happen to the normal registry. +#[derive(Resource, Debug, Clone)] +pub struct ReadOnlyManyToOneRegistry(Arc>>); + +impl ReadOnlyManyToOneRegistry { + /// Initializes a Registry. + /// + /// You should use [`create_registry`] instead, unless you don't want this + /// added as a bevy resource. + pub fn new() -> Self { + Self(Arc::new(RwLock::new(ManyToOneRegistry::new()))) + } + + /// Takes a lock of the registry this encapsulates + pub fn registry<'a>(&'a self) -> RwLockReadGuard<'a, ManyToOneRegistry> { + self.0.as_ref().read().expect("Failed to lock registry") + } +} + +fn apply_changes( + registry: Res>, + mut mutex_registry: ResMut>, +) { + mutex_registry.0 = Arc::new(RwLock::new(registry.clone())); +} + /// Initializes & adds the resource to bevy that can then be used in systems via `Res>` -pub fn create_many_to_one_registry(app: &mut App) { - app.insert_resource(ManyToOneRegistry::::new()); +pub fn create_many_to_one_registry(app: &mut App) { + app.insert_resource(ManyToOneRegistry::::new()) + .insert_resource(ReadOnlyManyToOneRegistry::::new()) + .add_systems( + Update, + apply_changes::.run_if(resource_exists_and_changed::>()), + ); } diff --git a/cosmos_core/src/registry/mod.rs b/cosmos_core/src/registry/mod.rs index d49285f29..70a0dc265 100644 --- a/cosmos_core/src/registry/mod.rs +++ b/cosmos_core/src/registry/mod.rs @@ -4,10 +4,11 @@ pub mod identifiable; pub mod many_to_one; pub mod one_to_one; -use bevy::prelude::{App, Resource}; +use bevy::prelude::{resource_exists_and_changed, App, IntoSystemConfigs, Res, ResMut, Resource, Update}; use bevy::utils::HashMap; use std::fmt; use std::slice::Iter; +use std::sync::{Arc, RwLock, RwLockReadGuard}; use self::identifiable::Identifiable; @@ -36,7 +37,7 @@ impl fmt::Display for AddLinkError { impl std::error::Error for AddLinkError {} /// Represents a bunch of values that are identifiable by their unlocalized name + numeric ids. -#[derive(Resource, Debug)] +#[derive(Resource, Debug, Clone)] pub struct Registry { contents: Vec, unlocalized_name_to_id: HashMap, @@ -104,7 +105,45 @@ impl Registry { } } +/// Represents a bunch of values that are identifiable by their unlocalized name + numeric ids. +/// +/// This is synced with its corresponding Registry every frame when it's changed. +/// +/// This is slower than a normal registry, but is usable between threads. +/// +/// Any updates made to this will be overwritten whenever the Registry changes, so don't change this +/// and expect anything to happen to the normal registry. +#[derive(Resource, Debug, Clone)] +pub struct ReadOnlyRegistry(Arc>>); + +impl Default for ReadOnlyRegistry { + fn default() -> Self { + Self(Arc::new(RwLock::new(Registry::default()))) + } +} + +impl ReadOnlyRegistry { + /// Initializes a Registry. + /// + /// You should use [`create_registry`] instead, unless you don't want this + /// added as a bevy resource. + pub fn new() -> Self { + Self(Arc::new(RwLock::new(Registry::new()))) + } + + /// Takes a lock of the registry this encapsulates + pub fn registry<'a>(&'a self) -> RwLockReadGuard<'a, Registry> { + self.0.as_ref().read().expect("Failed to lock registry") + } +} + +fn apply_changes(registry: Res>, mut mutex_registry: ResMut>) { + mutex_registry.0 = Arc::new(RwLock::new(registry.clone())); +} + /// Initializes & adds the registry to bevy that can then be used in systems via `Res>` -pub fn create_registry(app: &mut App) { - app.insert_resource(Registry::::new()); +pub fn create_registry(app: &mut App) { + app.insert_resource(Registry::::new()) + .insert_resource(ReadOnlyRegistry::::new()) + .add_systems(Update, apply_changes::.run_if(resource_exists_and_changed::>())); }