diff --git a/cosmos_client/src/rendering/lod_renderer.rs b/cosmos_client/src/rendering/lod_renderer.rs index 7a1c8ff02..24f8f88d3 100644 --- a/cosmos_client/src/rendering/lod_renderer.rs +++ b/cosmos_client/src/rendering/lod_renderer.rs @@ -455,7 +455,7 @@ fn vec_eq(v1: Vec3, v2: Vec3) -> bool { (v1.x - v2.x).abs() < EPSILON && (v1.y - v2.y).abs() < EPSILON && (v1.z - v2.z).abs() < EPSILON } -fn poll_generating_lods( +fn poll_rendering_lods( mut commands: Commands, structure_lod_meshes_query: Query<&LodMeshes>, mut meshes: ResMut>, @@ -654,7 +654,7 @@ pub(super) fn register(app: &mut App) { ( monitor_lods_needs_rendered_system, trigger_lod_render, - poll_generating_lods, + poll_rendering_lods, hide_lod, ) .chain() diff --git a/cosmos_client/src/rendering/structure_renderer.rs b/cosmos_client/src/rendering/structure_renderer.rs index 20f2a9abf..0871d0329 100644 --- a/cosmos_client/src/rendering/structure_renderer.rs +++ b/cosmos_client/src/rendering/structure_renderer.rs @@ -374,10 +374,6 @@ fn monitor_needs_rendered_system( return; }; - // 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 - for (entity, ce, _) in chunks_need_rendered .iter() .map(|(x, y, transform)| (x, y, transform.translation().distance_squared(local_transform.translation()))) @@ -392,6 +388,10 @@ fn monitor_needs_rendered_system( let coords = ce.chunk_location; + // I assure you officer, cloning 7 chunks to render 1 is very necessary + // + // please someone fix this when they feel inspired + let Some(chunk) = structure.chunk_from_chunk_coordinates(coords).cloned() else { continue; }; @@ -405,6 +405,8 @@ fn monitor_needs_rendered_system( let back = structure.chunk_from_chunk_coordinates_unbound(unbound.back()).cloned(); let front = structure.chunk_from_chunk_coordinates_unbound(unbound.front()).cloned(); + // "gee, you sure have a way with the borrow checker" + let atlas = atlas.clone(); let materials = materials.clone(); let blocks = blocks.clone(); diff --git a/cosmos_core/src/utils/mod.rs b/cosmos_core/src/utils/mod.rs index fe6529123..181054232 100644 --- a/cosmos_core/src/utils/mod.rs +++ b/cosmos_core/src/utils/mod.rs @@ -2,6 +2,5 @@ pub mod array_utils; pub mod quat_math; -pub mod resource_wrapper; pub mod smooth_clamp; pub mod timer; diff --git a/cosmos_core/src/utils/resource_wrapper.rs b/cosmos_core/src/utils/resource_wrapper.rs deleted file mode 100644 index d3a371c9e..000000000 --- a/cosmos_core/src/utils/resource_wrapper.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Used to get around the derive Resource requirement for resources - -use bevy::prelude::{Deref, Resource}; - -/// Used to get around the derive Resource requirement for resources -#[derive(Resource, Deref)] -pub struct ResourceWrapper(pub T); diff --git a/cosmos_server/src/init/init_world.rs b/cosmos_server/src/init/init_world.rs index 4514d0f34..e2e2e26ab 100644 --- a/cosmos_server/src/init/init_world.rs +++ b/cosmos_server/src/init/init_world.rs @@ -1,9 +1,13 @@ //! Handles the initialization of the server world -use std::{fs, num::Wrapping}; +use std::{ + fs, + num::Wrapping, + sync::{Arc, RwLock, RwLockReadGuard}, +}; use bevy::prelude::*; -use cosmos_core::{netty::cosmos_encoder, utils::resource_wrapper::ResourceWrapper}; +use cosmos_core::netty::cosmos_encoder; use serde::{Deserialize, Serialize}; #[derive(Debug, Resource, Deref, Serialize, Deserialize, Clone, Copy)] @@ -35,6 +39,25 @@ impl ServerSeed { } } +#[derive(Resource, Debug, Clone, Deref, DerefMut)] +/// A pre-seeded structure to create noise values. Uses simplex noise as the backend +/// +/// This cannot be sent across threads - use ReadOnlyNoise to send use across threads. +pub struct Noise(noise::OpenSimplex); + +#[derive(Resource, Debug, Clone)] +/// A thread-safe pre-seeded structure to create noise values. Uses simplex noise as the backend +/// +/// To use across threads, just clone this and call the `inner` method to get the Noise struct this encapsulates +pub struct ReadOnlyNoise(Arc>); + +impl ReadOnlyNoise { + /// Returns the `Noise` instance this encapsulates + pub fn inner(&self) -> RwLockReadGuard { + self.0.read().expect("Failed to read") + } +} + pub(super) fn register(app: &mut App) { let server_seed = if let Ok(seed) = fs::read("./world/seed.dat") { cosmos_encoder::deserialize::(&seed).expect("Unable to understand './world/seed.dat' seed file. Is it corrupted?") @@ -47,6 +70,8 @@ pub(super) fn register(app: &mut App) { seed }; - app.insert_resource(ResourceWrapper(noise::OpenSimplex::new(server_seed.as_u32()))) - .insert_resource(server_seed); + let noise = Noise(noise::OpenSimplex::new(server_seed.as_u32())); + let read_noise = ReadOnlyNoise(Arc::new(RwLock::new(noise.clone()))); + + app.insert_resource(noise).insert_resource(read_noise).insert_resource(server_seed); } diff --git a/cosmos_server/src/structure/asteroid/generator.rs b/cosmos_server/src/structure/asteroid/generator.rs index 701614663..8080c771a 100644 --- a/cosmos_server/src/structure/asteroid/generator.rs +++ b/cosmos_server/src/structure/asteroid/generator.rs @@ -16,12 +16,11 @@ use cosmos_core::{ structure_iterator::ChunkIteratorResult, ChunkInitEvent, Structure, }, - utils::resource_wrapper::ResourceWrapper, }; use futures_lite::future; use noise::NoiseFn; -use crate::state::GameState; +use crate::{init::init_world::Noise, state::GameState}; #[derive(Component)] struct AsyncStructureGeneration { @@ -72,7 +71,7 @@ fn notify_when_done_generating( fn start_generating_asteroid( query: Query<(Entity, &Structure, &Location), With>, - noise: Res>, + noise: Res, blocks: Res>, mut commands: Commands, ) { diff --git a/cosmos_server/src/structure/planet/biosphere/biosphere_generation.rs b/cosmos_server/src/structure/planet/biosphere/biosphere_generation.rs index b2f30eed4..0d951b808 100644 --- a/cosmos_server/src/structure/planet/biosphere/biosphere_generation.rs +++ b/cosmos_server/src/structure/planet/biosphere/biosphere_generation.rs @@ -3,7 +3,7 @@ use std::{marker::PhantomData, mem::swap}; use bevy::{ - prelude::{Component, Entity, Event, EventReader, EventWriter, Query, Res, ResMut, Resource, With}, + prelude::{App, Commands, Component, DespawnRecursiveExt, Entity, Event, EventReader, EventWriter, Query, Res, ResMut, Resource, With}, tasks::AsyncComputeTaskPool, }; use cosmos_core::{ @@ -18,13 +18,16 @@ use cosmos_core::{ planet::{ChunkFaces, Planet}, Structure, }, - utils::{array_utils::flatten_2d, resource_wrapper::ResourceWrapper}, + utils::array_utils::flatten_2d, }; use futures_lite::future; use noise::NoiseFn; use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; -use crate::structure::planet::lods::generate_lods::{GeneratingLod, PlayerGeneratingLod}; +use crate::{ + init::init_world::{Noise, ReadOnlyNoise}, + structure::planet::lods::generate_lods::{GeneratingLod, GeneratingLods, LodNeedsGeneratedForPlayer}, +}; use super::{GeneratingChunk, GeneratingChunks, TGenerateChunkEvent}; @@ -1045,7 +1048,7 @@ fn recurse, ) { match generating_lod { @@ -1093,31 +1096,49 @@ fn recurse( - mut query: Query<&mut PlayerGeneratingLod>, +pub(crate) fn start_generating_lods( + query: Query<(Entity, &LodNeedsGeneratedForPlayer)>, is_biosphere: Query<(&Structure, &Location), With>, - noise_generator: Res>, + noise_generator: Res, block_ranges: Res>, + mut currently_generating: ResMut, + mut commands: Commands, ) { - query.par_iter_mut().for_each_mut(|mut generating_lod| { + for (entity, generating_lod) in query.iter() { + commands.entity(entity).despawn_recursive(); + let Ok((structure, location)) = is_biosphere.get(generating_lod.structure_entity) else { return; }; + let task_pool = AsyncComputeTaskPool::get(); + let structure_coords = location.absolute_coords_f64(); let dimensions = structure.block_dimensions().x; - recurse::( - &mut generating_lod.generating_lod, - (structure_coords.x, structure_coords.y, structure_coords.z), - BlockCoordinate::new(0, 0, 0), - dimensions, - dimensions / CHUNK_DIMENSIONS, - &noise_generator, - &block_ranges, - ); - }); + let mut generating_lod = generating_lod.clone(); + let noise_generator = noise_generator.clone(); + let block_ranges = block_ranges.clone(); + + let task = task_pool.spawn(async move { + let noise = noise_generator.inner(); + + recurse::( + &mut generating_lod.generating_lod, + (structure_coords.x, structure_coords.y, structure_coords.z), + BlockCoordinate::new(0, 0, 0), + dimensions, + dimensions / CHUNK_DIMENSIONS, + &noise, + &block_ranges, + ); + + generating_lod + }); + + currently_generating.push(task); + } } /// Calls generate_face_chunk, generate_edge_chunk, and generate_corner_chunk to generate the chunks of a planet. @@ -1125,7 +1146,7 @@ pub fn generate_planet, mut generating: ResMut>, mut events: EventReader, - noise_generator: Res>, + noise_generator: Res, block_ranges: Res>, ) { let chunks = events @@ -1168,9 +1189,11 @@ pub fn generate_planet(); +} diff --git a/cosmos_server/src/structure/planet/biosphere/grass_biosphere.rs b/cosmos_server/src/structure/planet/biosphere/grass_biosphere.rs index 070c07350..e6171f103 100644 --- a/cosmos_server/src/structure/planet/biosphere/grass_biosphere.rs +++ b/cosmos_server/src/structure/planet/biosphere/grass_biosphere.rs @@ -14,11 +14,10 @@ use cosmos_core::{ planet::Planet, rotate, ChunkInitEvent, Structure, }, - utils::resource_wrapper::ResourceWrapper, }; use noise::NoiseFn; -use crate::GameState; +use crate::{init::init_world::Noise, GameState}; use super::{ biosphere_generation::{BlockLayers, DefaultBiosphereGenerationStrategy, GenerateChunkFeaturesEvent}, @@ -139,7 +138,7 @@ fn redwood_tree( location: &Location, block_event_writer: &mut EventWriter, blocks: &Registry, - noise_generator: &ResourceWrapper, + noise_generator: &Noise, ) { let log = blocks.from_id("cosmos:redwood_log").unwrap(); let leaf = blocks.from_id("cosmos:redwood_leaf").unwrap(); @@ -439,7 +438,7 @@ fn trees( location: &Location, block_event_writer: &mut EventWriter, blocks: &Registry, - noise_generator: &ResourceWrapper, + noise_generator: &Noise, ) { let Structure::Dynamic(planet) = structure else { panic!("A planet must be dynamic!"); @@ -565,7 +564,7 @@ pub fn generate_chunk_features( mut block_event_writer: EventWriter, mut structure_query: Query<(&mut Structure, &Location)>, blocks: Res>, - noise_generator: Res>, + noise_generator: Res, ) { for ev in event_reader.iter() { if let Ok((mut structure, location)) = structure_query.get_mut(ev.structure_entity) { diff --git a/cosmos_server/src/structure/planet/biosphere/mod.rs b/cosmos_server/src/structure/planet/biosphere/mod.rs index 1d2e73e0f..f989a6a00 100644 --- a/cosmos_server/src/structure/planet/biosphere/mod.rs +++ b/cosmos_server/src/structure/planet/biosphere/mod.rs @@ -32,7 +32,7 @@ use crate::{ }; use self::biosphere_generation::{ - generate_lods, generate_planet, notify_when_done_generating_terrain, BiosphereGenerationStrategy, GenerateChunkFeaturesEvent, + generate_planet, notify_when_done_generating_terrain, start_generating_lods, BiosphereGenerationStrategy, GenerateChunkFeaturesEvent, }; use super::generation::planet_generator::check_needs_generated_system; @@ -152,7 +152,7 @@ pub fn register_biosphere< ( generate_planet::, notify_when_done_generating_terrain::, - generate_lods::, + start_generating_lods::, check_needs_generated_system::, ) .run_if(in_state(GameState::Playing)), @@ -244,4 +244,5 @@ pub(super) fn register(app: &mut App) { grass_biosphere::register(app); molten_biosphere::register(app); ice_biosphere::register(app); + biosphere_generation::register(app); } diff --git a/cosmos_server/src/structure/planet/lods/generate_lods.rs b/cosmos_server/src/structure/planet/lods/generate_lods.rs index 30a30d5e8..f8dffb3e0 100644 --- a/cosmos_server/src/structure/planet/lods/generate_lods.rs +++ b/cosmos_server/src/structure/planet/lods/generate_lods.rs @@ -1,6 +1,11 @@ -use bevy::prelude::{ - in_state, warn, App, BuildChildren, Children, Commands, Component, DespawnRecursiveExt, Entity, GlobalTransform, IntoSystemConfigs, - Quat, Query, Res, Update, With, +use std::mem::swap; + +use bevy::{ + prelude::{ + in_state, warn, App, BuildChildren, Children, Commands, Component, Deref, DerefMut, Entity, GlobalTransform, IntoSystemConfigs, + Quat, Query, Res, ResMut, Resource, Update, With, + }, + tasks::Task, }; use cosmos_core::{ block::Block, @@ -15,6 +20,7 @@ use cosmos_core::{ Structure, }, }; +use futures_lite::future; use crate::state::GameState; @@ -27,13 +33,17 @@ enum LodRequest { Multi(Box<[LodRequest; 8]>), } -#[derive(Debug, Component)] -pub struct PlayerGeneratingLod { +#[derive(Debug, Component, Clone)] +pub struct LodNeedsGeneratedForPlayer { pub structure_entity: Entity, pub generating_lod: GeneratingLod, pub player_entity: Entity, + pub current_lod: Option, } +#[derive(Debug, Resource, Deref, DerefMut, Default)] +pub(crate) struct GeneratingLods(pub Vec>); + #[derive(Debug, Clone)] /// Represents a reduced-detail version of a planet undergoing generation pub enum GeneratingLod { @@ -68,7 +78,7 @@ struct LodGenerationRequest { request: LodRequest, structure_entity: Entity, player_entity: Entity, - // task: Task, + current_lod: Option, } fn check_done(generating_lod: &GeneratingLod) -> bool { @@ -108,42 +118,48 @@ fn check_done_generating( mut commands: Commands, children_query: Query<&Children>, mut lod_query: Query<(Entity, &mut PlayerLod)>, - query: Query<(Entity, &PlayerGeneratingLod)>, + mut generating_lods: ResMut, ) { - for (entity, player_generating_lod) in query.iter() { - if check_done(&player_generating_lod.generating_lod) { - commands.entity(entity).despawn_recursive(); - - let current_lod = children_query - .get(player_generating_lod.structure_entity) - .map(|children| { - children - .iter() - .flat_map(|&child_entity| lod_query.get(child_entity)) - .find(|&(_, player_lod)| player_lod.player == player_generating_lod.player_entity) - .map(|(entity, _)| entity) - }) - .unwrap_or(None) - .map(|e| lod_query.get_mut(e).map(|(_, player_lod)| player_lod)); - - let lod_delta = recursively_create_lod_delta(player_generating_lod.generating_lod.clone()); - - let cloned_delta = lod_delta.clone(); - - if let Some(Ok(mut current_lod)) = current_lod { - cloned_delta.apply_changes(&mut current_lod.lod); - current_lod.deltas.push(lod_delta); - } else { - commands.get_entity(player_generating_lod.structure_entity).map(|mut ecmds| { - ecmds.with_children(|cmds| { - cmds.spawn(PlayerLod { - lod: cloned_delta.create_lod(), - deltas: vec![lod_delta], - player: player_generating_lod.player_entity, + let mut todo = Vec::with_capacity(generating_lods.capacity()); + + swap(&mut todo, &mut generating_lods.0); + + for mut task in todo { + if let Some(generated_lod) = future::block_on(future::poll_once(&mut task)) { + if check_done(&generated_lod.generating_lod) { + let current_lod = children_query + .get(generated_lod.structure_entity) + .map(|children| { + children + .iter() + .flat_map(|&child_entity| lod_query.get(child_entity)) + .find(|&(_, player_lod)| player_lod.player == generated_lod.player_entity) + .map(|(entity, _)| entity) + }) + .unwrap_or(None) + .map(|e| lod_query.get_mut(e).map(|(_, player_lod)| player_lod)); + + let lod_delta = recursively_create_lod_delta(generated_lod.generating_lod); + + let cloned_delta = lod_delta.clone(); + + if let Some(Ok(mut current_lod)) = current_lod { + cloned_delta.apply_changes(&mut current_lod.lod); + current_lod.deltas.push(lod_delta); + } else { + commands.get_entity(generated_lod.structure_entity).map(|mut ecmds| { + ecmds.with_children(|cmds| { + cmds.spawn(PlayerLod { + lod: cloned_delta.create_lod(), + deltas: vec![lod_delta], + player: generated_lod.player_entity, + }); }); }); - }); + } } + } else { + generating_lods.push(task); } } } @@ -229,7 +245,7 @@ fn create_generating_lod( } } -fn poll_generating( +fn start_generating_lods( mut commands: Commands, blocks: Res>, structure_query: Query<&Structure>, @@ -247,13 +263,15 @@ fn poll_generating( (BlockCoordinate::new(0, 0, 0), structure.block_dimensions()), ); - commands.spawn(PlayerGeneratingLod { - structure_entity: lod_request.structure_entity, - generating_lod, - player_entity: lod_request.player_entity, - }); - - commands.entity(entity).despawn_recursive(); + commands + .entity(entity) + .remove::() + .insert(LodNeedsGeneratedForPlayer { + structure_entity: lod_request.structure_entity, + generating_lod, + player_entity: lod_request.player_entity, + current_lod: lod_request.current_lod.clone(), + }); } } @@ -377,7 +395,7 @@ fn create_lod_request( fn generate_player_lods( mut commands: Commands, any_generation_requests: Query<(), With>, - generating_lods: Query<&PlayerGeneratingLod>, + generating_lods: Query<&LodNeedsGeneratedForPlayer>, players: Query<(Entity, &Location), With>, structures: Query<(Entity, &Structure, &Location, &GlobalTransform, Option<&Children>), With>, current_lods: Query<&PlayerLod>, @@ -439,6 +457,7 @@ fn generate_player_lods( player_entity, structure_entity: structure_ent, request, + current_lod: current_lod.cloned(), }) .id(); commands.entity(structure_ent).add_child(request_entity); @@ -451,7 +470,7 @@ pub(super) fn register(app: &mut App) { Update, ( generate_player_lods.run_if(in_state(GameState::Playing)), - (poll_generating, check_done_generating).run_if(in_state(GameState::Playing)), + (start_generating_lods, check_done_generating).run_if(in_state(GameState::Playing)), ), ); }