From 9581a5d206a1bb0a049a8524449eae372c9856fe Mon Sep 17 00:00:00 2001 From: Tobias Berger Date: Tue, 15 Nov 2022 19:04:02 +0100 Subject: [PATCH] Add progress bar for world generation. Also remove a bunch of spammy logging --- Cargo.lock | 2 + Cargo.toml | 7 +- planet/Cargo.toml | 7 +- planet/src/lib.rs | 2 +- planet/src/world.rs | 113 ++++++++++++++++----- planet/src/world_manager.rs | 25 ++++- src/gui/mod.rs | 12 +-- src/gui/widget.rs | 10 +- src/gui/widgets/mod.rs | 2 +- src/gui/widgets/toolbar.rs | 8 +- src/gui/window.rs | 14 +-- src/gui/windows/mod.rs | 8 +- src/gui/windows/save_load.rs | 2 +- src/gui/windows/tile_info.rs | 2 +- src/gui/windows/world_overlay_selection.rs | 2 +- src/gui/windows/world_view_selection.rs | 2 +- src/main.rs | 41 ++++++-- src/planet_renderer.rs | 2 +- src/plugins/mod.rs | 4 +- src/plugins/world_plugins.rs | 2 +- src/resources/mod.rs | 43 ++++++-- 21 files changed, 221 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a68979..0f709e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2087,6 +2087,7 @@ name = "planet" version = "0.3.0" dependencies = [ "bevy", + "crossbeam-channel", "postcard", "rand", "serde", @@ -3087,6 +3088,7 @@ version = "0.3.0" dependencies = [ "bevy", "bevy_egui", + "crossbeam-channel", "futures-lite", "fxhash", "planet", diff --git a/Cargo.toml b/Cargo.toml index 14d550f..4ce8fcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,4 +54,9 @@ optional = true [dependencies.futures-lite] version = "1.12.0" -default-features = false \ No newline at end of file +default-features = false + +[dependencies.crossbeam-channel] +version = "0.5.6" +default-features = false +features = ["std"] \ No newline at end of file diff --git a/planet/Cargo.toml b/planet/Cargo.toml index 8954728..9b923d5 100644 --- a/planet/Cargo.toml +++ b/planet/Cargo.toml @@ -26,4 +26,9 @@ features = ["derive"] [dependencies.postcard] version = "1.0.2" default-features = false -features = ["use-std"] \ No newline at end of file +features = ["use-std"] + +[dependencies.crossbeam-channel] +version = "0.5.6" +default-features = false +features = ["std"] \ No newline at end of file diff --git a/planet/src/lib.rs b/planet/src/lib.rs index 358e2c3..653243d 100644 --- a/planet/src/lib.rs +++ b/planet/src/lib.rs @@ -4,7 +4,7 @@ pub mod biome; pub use biome::{BiomeStats, BiomeType}; pub mod world_manager; pub use world_manager::WorldManager; -pub(crate) mod macros; +pub mod macros; pub mod math_util; pub mod perlin; pub mod saving; diff --git a/planet/src/world.rs b/planet/src/world.rs index 8f06906..bdae608 100644 --- a/planet/src/world.rs +++ b/planet/src/world.rs @@ -17,6 +17,7 @@ use { prelude::Vec2, utils::{default, HashMap}, }, + crossbeam_channel::Sender, rand::{rngs::StdRng, Rng, SeedableRng}, serde::{Deserialize, Serialize}, std::{ @@ -169,18 +170,25 @@ impl World { } } - pub fn generate(&mut self) -> Result<(), WorldGenError> { - if let Err(err) = self.generate_altitude() { + pub fn generate( + &mut self, + progress_sender: &Sender<(f32, String)>, + ) -> Result<(), WorldGenError> { + send_progress(progress_sender, 0.0, "Generating altitude"); + if let Err(err) = self.generate_altitude(progress_sender) { return Err(WorldGenError::CartesianError(err)); } - if let Err(err) = self.generate_rainfall() { + send_progress(progress_sender, 0.0, "Generating rainfall"); + if let Err(err) = self.generate_rainfall(progress_sender) { return Err(WorldGenError::CartesianError(err)); } - if let Err(err) = self.generate_temperature() { + send_progress(progress_sender, 0.0, "Generating temperature"); + if let Err(err) = self.generate_temperature(progress_sender) { return Err(WorldGenError::CartesianError(err)); } - self.generate_biomes(); + send_progress(progress_sender, 0.0, "Generating biomes"); + self.generate_biomes(progress_sender); Ok(()) } @@ -192,8 +200,8 @@ impl World { let height = self.height as f32; for i in 0..World::NUM_CONTINENTS { - #[cfg(feature = "logging")] - info!("Continents {}/{}", i, World::NUM_CONTINENTS); + // #[cfg(feature = "logging")] + // info!("Continents: {}/{}", i, World::NUM_CONTINENTS); self.continent_offsets[i as usize].x = self .rng @@ -254,7 +262,10 @@ impl World { max_value } - fn generate_altitude(&mut self) -> Result<(), CartesianError> { + fn generate_altitude( + &mut self, + progress_sender: &Sender<(f32, String)>, + ) -> Result<(), CartesianError> { info!("Generating altitude"); self.generate_continents(); @@ -270,13 +281,20 @@ impl World { let offset_4 = World::random_offset_vector(&mut self.rng); let offset_5 = World::random_offset_vector(&mut self.rng); - for y in 0..self.terrain.len() { - #[cfg(feature = "logging")] - info!("Altitude: {}/{}", y, self.terrain.len()); - + let height = self.terrain.len(); + for y in 0..height { let alpha = (y as f32 / self.height as f32) * PI; - for x in 0..self.terrain[y].len() { + let width = self.terrain[y].len(); + let size = height * width; + for x in 0..width { + let index = y * width + x; + send_progress( + progress_sender, + index as f32 / size as f32, + format!("Generating topography: {index}/{size}"), + ); + let beta = (x as f32 / self.width as f32) * TAU; let value_1 = @@ -385,20 +403,27 @@ impl World { World::MIN_ALTITUDE + (raw_altitude * World::ALTITUDE_SPAN) } - fn generate_rainfall(&mut self) -> Result<(), CartesianError> { - #[cfg(feature = "logging")] + fn generate_rainfall( + &mut self, + progress_sender: &Sender<(f32, String)>, + ) -> Result<(), CartesianError> { info!("Generating rainfall"); const RADIUS: f32 = 2.0; let offset = World::random_offset_vector(&mut self.rng); let height = self.terrain.len(); for y in 0..height { - #[cfg(feature = "logging")] - info!("Rainfall: {}/{}", y, height); let alpha = (y as f32 / self.height as f32) * PI; let width = self.terrain[y].len(); + let size = width * height; for x in 0..width { + let index = y * width + x; + send_progress( + progress_sender, + index as f32 / size as f32, + format!("Generating rainfall: {index}/{size}"), + ); let beta = (x as f32 / self.width as f32) * TAU; let random_noise = @@ -459,17 +484,28 @@ impl World { ) } - fn generate_temperature(&mut self) -> Result<(), CartesianError> { - #[cfg(feature = "logging")] + fn generate_temperature( + &mut self, + progress_sender: &Sender<(f32, String)>, + ) -> Result<(), CartesianError> { info!("Generating temperature"); let offset = World::random_offset_vector(&mut self.rng); const RADIUS: f32 = 2.0; - for y in 0..self.terrain.len() { - #[cfg(feature = "logging")] - info!("Temperature: {}/{}", y, self.terrain.len()); + let height = self.terrain.len(); + for y in 0..height { let alpha = (y as f32 / self.height as f32) * PI; - for x in 0..self.terrain[y].len() { + + let width = self.terrain[y].len(); + let size = width * height; + for x in 0..width { + let index = y * width + x; + send_progress( + progress_sender, + index as f32 / size as f32, + format!("Generating temperature: {index}/{size}"), + ); + let beta = (x as f32 / self.width as f32) * TAU; let random_noise = @@ -496,6 +532,7 @@ impl World { } } + info!("Done generating temperature"); Ok(()) } @@ -507,12 +544,19 @@ impl World { ) } - fn generate_biomes(&mut self) { + fn generate_biomes(&mut self, progress_sender: &Sender<(f32, String)>) { info!("Generating biomes"); - for y in 0..self.terrain.len() { - #[cfg(feature = "logging")] - info!("Biomes: {}/{}", y, self.terrain.len()); - for x in 0..self.terrain[y].len() { + let height = self.terrain.len(); + for y in 0..height { + let width = self.terrain[y].len(); + let size = height * width; + for x in 0..width { + let index = y * width + x; + send_progress( + progress_sender, + index as f32 / size as f32, + format!("Generating biomes: {index}/{size}"), + ); let cell = &self.terrain[y][x]; let mut total_presence = 0.0; @@ -534,6 +578,7 @@ impl World { .collect(); } } + info!("Done generating biomes"); } fn biome_presence(&self, cell: &TerrainCell, biome: &BiomeStats) -> f32 { @@ -767,3 +812,15 @@ impl World { return false; } } + +fn send_progress>( + progress_sender: &Sender<(f32, String)>, + progress: f32, + progress_text: T, +) { + if let Err(_) = progress_sender.try_send((progress, progress_text.into())) { + // Quietly ignore the error, it's not critical, and logging is slow. + + // debug!("Failed to send world generation progress. {err}"); + } +} diff --git a/planet/src/world_manager.rs b/planet/src/world_manager.rs index a56a721..aea518b 100644 --- a/planet/src/world_manager.rs +++ b/planet/src/world_manager.rs @@ -6,6 +6,7 @@ use { tasks::{AsyncComputeTaskPool, Task}, utils::default, }, + crossbeam_channel::Sender, rand::random, std::{ error::Error, @@ -99,7 +100,6 @@ impl WorldManager { return Err(SaveError::MissingWorld); }; - let serialized = match postcard::to_stdvec(world) { Ok(serialized) => serialized, Err(err) => { @@ -124,7 +124,7 @@ impl WorldManager { if let Err(err) = file.read_to_end(&mut buf) { return Err(LoadError::MissingSave(err)); }; - + match postcard::from_bytes(buf.as_slice()) { Ok(world) => { self.world = Some(world); @@ -139,11 +139,15 @@ impl WorldManager { self.world.as_ref() } - pub fn set_world(&mut self, world: World) { + pub fn set_world(&mut self, world: World) { self.world = Some(world); } - pub fn new_world_async(&mut self, seed: Option) -> Task> { + pub fn new_world_async( + &mut self, + seed: Option, + progress_sender: Sender<(f32, String)>, + ) -> Task> { AsyncComputeTaskPool::get().spawn(async move { let seed = seed.unwrap_or_else(random); let mut new_world = World::async_new( @@ -151,7 +155,18 @@ impl WorldManager { WorldManager::NEW_WORLD_HEIGHT, seed, ); - match new_world.generate() { + if let Err(_) = + progress_sender.try_send((0.0, String::from("Generating new world..."))) + { + // Quietly ignore. It's not critical and logging is slow. + } + let result = new_world.generate(&progress_sender); + if let Err(_) = + progress_sender.try_send((1.0, String::from("Done generating world!"))) + { + // Quietly ignore. See above + } + match result { Ok(()) => Ok(new_world), Err(err) => Err(err), } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 73ba016..e687ff8 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,9 +1,9 @@ -pub(crate) mod widget; -pub(crate) use widget::*; -pub(crate) mod window; -pub(crate) use window::*; +pub mod widget; +pub use widget::*; +pub mod window; +pub use window::*; -pub(crate) mod widgets; -pub(crate) mod windows; +pub mod widgets; +pub mod windows; use crate::gui::{open_window, WidgetId, WidgetSystem}; diff --git a/src/gui/widget.rs b/src/gui/widget.rs index a4544db..1240b88 100644 --- a/src/gui/widget.rs +++ b/src/gui/widget.rs @@ -14,11 +14,11 @@ use { std::hash::Hasher, }; -pub(crate) trait WidgetSystem: SystemParam { +pub trait WidgetSystem: SystemParam { fn render(world: &mut World, state: &mut SystemState, ui: &mut Ui, id: WidgetId); } -pub(crate) fn widget(world: &mut World, ui: &mut Ui, id: WidgetId) { +pub fn widget(world: &mut World, ui: &mut Ui, id: WidgetId) { // We need to cache `SystemState` to allow for a system's locally tracked state if !world.contains_resource::>() { // Note, this message should only appear once! If you see it twice in the logs, @@ -51,10 +51,10 @@ struct StateInstances { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct WidgetId(pub(crate) u64); +pub struct WidgetId(pub u64); impl WidgetId { #[must_use] - pub(crate) fn new(name: &str) -> Self { + pub fn new(name: &str) -> Self { let bytes = name.as_bytes(); let mut hasher = FxHasher32::default(); hasher.write(bytes); @@ -62,7 +62,7 @@ impl WidgetId { } // #[must_use] - // pub(crate) fn with(&self, name: &str) -> Self { + // pub fn with(&self, name: &str) -> Self { // Self::new(&format!("{}{name}", self.0)) // } } diff --git a/src/gui/widgets/mod.rs b/src/gui/widgets/mod.rs index 65bcf96..6a9fc85 100644 --- a/src/gui/widgets/mod.rs +++ b/src/gui/widgets/mod.rs @@ -1,2 +1,2 @@ mod toolbar; -pub(crate) use toolbar::ToolbarWidget; +pub use toolbar::ToolbarWidget; diff --git a/src/gui/widgets/toolbar.rs b/src/gui/widgets/toolbar.rs index b1bd937..121ee73 100644 --- a/src/gui/widgets/toolbar.rs +++ b/src/gui/widgets/toolbar.rs @@ -7,7 +7,7 @@ use { WidgetSystem, }, macros::iterable_enum, - resources::{GenerateWorldTask, OpenedWindows}, + resources::{GenerateWorldProgressChannel, GenerateWorldTask, OpenedWindows}, }, bevy::{ ecs::{ @@ -34,11 +34,13 @@ impl ToolbarButton { world.resource_scope(|world, mut world_manager: Mut| { match self { ToolbarButton::GenerateWorld => { + let progress_sender = world.resource::().sender(); let generate_world_task = &mut world.resource_mut::(); if generate_world_task.0.is_some() { debug!("Already generating new world") } else { - generate_world_task.0 = Some(world_manager.new_world_async(None)) + generate_world_task.0 = + Some(world_manager.new_world_async(None, progress_sender)) } }, ToolbarButton::SaveLoad => { @@ -87,7 +89,7 @@ impl From<&ToolbarButton> for String { } #[derive(SystemParam)] -pub(crate) struct ToolbarWidget<'w, 's> { +pub struct ToolbarWidget<'w, 's> { #[system_param(ignore)] _phantom: PhantomData<(&'w (), &'s ())>, } diff --git a/src/gui/window.rs b/src/gui/window.rs index c885c8c..06d8038 100644 --- a/src/gui/window.rs +++ b/src/gui/window.rs @@ -16,13 +16,13 @@ use { std::hash::Hasher, }; -pub(crate) trait WindowSystem: SystemParam { +pub trait WindowSystem: SystemParam { fn draw_contents(world: &mut World, state: &mut SystemState, ui: &mut Ui); fn name() -> &'static str; fn resizable() -> bool; } -pub(crate) fn render_windows(world: &mut World, ctx: &Context) { +pub fn render_windows(world: &mut World, ctx: &Context) { // TODO: Windows are hard-coded here instead of being iterable, and allows // creating new windows that are never rendered. // Is that good enough? @@ -32,10 +32,10 @@ pub(crate) fn render_windows(world: &mut World, ctx: &Context) { window::(world, ctx); } -pub(crate) fn open_window(windows: &mut OpenedWindows) { +pub fn open_window(windows: &mut OpenedWindows) { windows.open(S::name().into()); } -pub(crate) fn close_window(windows: &mut OpenedWindows) { +pub fn close_window(windows: &mut OpenedWindows) { windows.close(&S::name().into()); } @@ -87,10 +87,10 @@ struct StateInstances { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct WindowId(pub(crate) u64); +pub struct WindowId(pub u64); impl WindowId { #[must_use] - pub(crate) fn new(name: &str) -> Self { + pub fn new(name: &str) -> Self { let bytes = name.as_bytes(); let mut hasher = FxHasher32::default(); hasher.write(bytes); @@ -98,7 +98,7 @@ impl WindowId { } // #[must_use] - // pub(crate) fn with(&self, name: &str) -> Self { + // pub fn with(&self, name: &str) -> Self { // Self::new(&format!("{}{name}", self.0)) // } } diff --git a/src/gui/windows/mod.rs b/src/gui/windows/mod.rs index f982098..0682f00 100644 --- a/src/gui/windows/mod.rs +++ b/src/gui/windows/mod.rs @@ -1,8 +1,8 @@ mod tile_info; -pub(crate) use tile_info::TileInfo; +pub use tile_info::TileInfo; mod world_view_selection; -pub(crate) use world_view_selection::WorldViewSelection; +pub use world_view_selection::WorldViewSelection; mod world_overlay_selection; -pub(crate) use world_overlay_selection::WorldOverlaySelection; +pub use world_overlay_selection::WorldOverlaySelection; mod save_load; -pub(crate) use save_load::SaveLoad; +pub use save_load::SaveLoad; diff --git a/src/gui/windows/save_load.rs b/src/gui/windows/save_load.rs index 7cae3ba..46d765b 100644 --- a/src/gui/windows/save_load.rs +++ b/src/gui/windows/save_load.rs @@ -14,7 +14,7 @@ use { }; #[derive(SystemParam)] -pub(crate) struct SaveLoad<'w, 's> { +pub struct SaveLoad<'w, 's> { pub file_name: Local<'s, String>, #[system_param(ignore)] _phantom: PhantomData<(&'w (), &'s ())>, diff --git a/src/gui/windows/tile_info.rs b/src/gui/windows/tile_info.rs index d50ee14..32a02aa 100644 --- a/src/gui/windows/tile_info.rs +++ b/src/gui/windows/tile_info.rs @@ -10,7 +10,7 @@ use { }; #[derive(SystemParam)] -pub(crate) struct TileInfo<'w, 's> { +pub struct TileInfo<'w, 's> { #[system_param(ignore)] _phantom: PhantomData<(&'w (), &'s ())>, } diff --git a/src/gui/windows/world_overlay_selection.rs b/src/gui/windows/world_overlay_selection.rs index 7e14ad6..aea0b8e 100644 --- a/src/gui/windows/world_overlay_selection.rs +++ b/src/gui/windows/world_overlay_selection.rs @@ -14,7 +14,7 @@ use { }; #[derive(SystemParam)] -pub(crate) struct WorldOverlaySelection<'w, 's> { +pub struct WorldOverlaySelection<'w, 's> { #[system_param(ignore)] _phantom: PhantomData<(&'w (), &'s ())>, } diff --git a/src/gui/windows/world_view_selection.rs b/src/gui/windows/world_view_selection.rs index 6ed9a60..a992e59 100644 --- a/src/gui/windows/world_view_selection.rs +++ b/src/gui/windows/world_view_selection.rs @@ -14,7 +14,7 @@ use { }; #[derive(SystemParam)] -pub(crate) struct WorldViewSelection<'w, 's> { +pub struct WorldViewSelection<'w, 's> { #[system_param(ignore)] _phantom: PhantomData<(&'w (), &'s ())>, } diff --git a/src/main.rs b/src/main.rs index 568f461..7c3879f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,23 +3,24 @@ use { crate::resources::GenerateWorldTask, futures_lite::future::{block_on, poll_once}, + resources::GenerateWorldProgressChannel, }; -pub(crate) mod components; +pub mod components; #[cfg(feature = "render")] -pub(crate) mod gui; -pub(crate) mod macros; +pub mod gui; +pub mod macros; #[cfg(feature = "render")] -pub(crate) mod planet_renderer; -pub(crate) mod plugins; -pub(crate) mod resources; +pub mod planet_renderer; +pub mod plugins; +pub mod resources; use {bevy::prelude::*, planet::WorldManager, plugins::WorldPlugins}; #[cfg(feature = "render")] use { bevy::render::camera::RenderTarget, bevy_egui::{ - egui::{FontData, FontDefinitions, FontFamily}, + egui::{FontData, FontDefinitions, FontFamily, ProgressBar}, EguiContext, }, gui::{render_windows, widget, widgets::ToolbarWidget, window::open_window, windows::TileInfo}, @@ -73,6 +74,9 @@ fn handle_generate_world_task( mut generate_world_task: ResMut, mut world_manager: ResMut, #[cfg(feature = "render")] mut should_redraw: ResMut, + #[cfg(feature = "render")] mut egui_ctx: ResMut<'_, EguiContext>, + #[cfg(feature = "render")] progress_channel: Res<'_, GenerateWorldProgressChannel>, + #[cfg(feature = "render")] mut progress: Local<(f32, String)>, ) { if let Some(task) = &mut generate_world_task.0 { if task.is_finished() { @@ -92,8 +96,24 @@ fn handle_generate_world_task( } } generate_world_task.0 = None; + #[cfg(feature = "render")] + { + *progress = (0.0, String::from("Generating world...")); + } } else { - debug!("Still generating world") + debug!("Still generating world"); + + #[cfg(feature = "render")] + { + if let Ok(new_progress) = progress_channel.receiver().try_recv() { + *progress = new_progress; + } + _ = bevy_egui::egui::TopBottomPanel::bottom("Generating World ProgressBar") + .default_height(8.0) + .show(egui_ctx.ctx_mut(), |ui| { + ui.add(ProgressBar::new(progress.0).text(progress.1.as_str())); + }); + } } } } @@ -293,9 +313,8 @@ fn main() -> Result<(), Box> { } app.insert_resource(WorldManager::new()) - .insert_resource(GenerateWorldTask( - /* Some(manager.new_world_async(Some(0))) */ None, - )) + .insert_resource(GenerateWorldProgressChannel::new()) + .insert_resource(GenerateWorldTask(None)) .add_system(handle_generate_world_task) .run(); diff --git a/src/planet_renderer.rs b/src/planet_renderer.rs index 2b41372..b212bfe 100644 --- a/src/planet_renderer.rs +++ b/src/planet_renderer.rs @@ -166,7 +166,7 @@ fn coastline_color(world: &World, cell: &TerrainCell) -> Color { COASTLINE_PALETTE[0] } } -pub(crate) trait WorldRenderer { +pub trait WorldRenderer { fn map_color_bytes(&self, render_settings: &WorldRenderSettings) -> Vec; fn generate_color(&self, cell: &TerrainCell, render_settings: &WorldRenderSettings) -> Color; } diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 6895c63..c79633d 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,2 +1,2 @@ -pub(crate) mod world_plugins; -pub(crate) use world_plugins::WorldPlugins; +pub mod world_plugins; +pub use world_plugins::WorldPlugins; diff --git a/src/plugins/world_plugins.rs b/src/plugins/world_plugins.rs index 1d880dc..59b8258 100644 --- a/src/plugins/world_plugins.rs +++ b/src/plugins/world_plugins.rs @@ -1,4 +1,4 @@ -pub(crate) struct WorldPlugins; +pub struct WorldPlugins; #[cfg(not(feature = "render"))] use bevy::app::ScheduleRunnerPlugin; diff --git a/src/resources/mod.rs b/src/resources/mod.rs index e1a8dc4..efa6d24 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -2,14 +2,15 @@ use {crate::gui::WindowId, bevy::utils::HashSet, std::fmt::Display}; use { bevy::{prelude::Resource, tasks::Task}, + crossbeam_channel::{bounded, Receiver, Sender}, planet::{World, WorldGenError}, }; #[cfg(feature = "render")] #[derive(Default, Debug, Resource)] -pub(crate) struct CursorMapPosition { - pub(crate) x: i32, - pub(crate) y: i32, +pub struct CursorMapPosition { + pub x: i32, + pub y: i32, } #[cfg(feature = "render")] impl Display for CursorMapPosition { @@ -20,27 +21,53 @@ impl Display for CursorMapPosition { #[cfg(feature = "render")] #[derive(Resource, Default)] -pub(crate) struct ShouldRedraw(pub(crate) bool); +pub struct ShouldRedraw(pub bool); #[cfg(feature = "render")] #[derive(Default, Resource)] -pub(crate) struct OpenedWindows(HashSet); +pub struct OpenedWindows(HashSet); #[cfg(feature = "render")] impl OpenedWindows { - pub(crate) fn open(&mut self, id: WindowId) { + pub fn open(&mut self, id: WindowId) { // Ignore opening already opened windows _ = self.0.insert(id); } - pub(crate) fn close(&mut self, id: &WindowId) { + pub fn close(&mut self, id: &WindowId) { // Ignore closing already closed windows _ = self.0.remove(id); } - pub(crate) fn is_open(&self, id: &WindowId) -> bool { + pub fn is_open(&self, id: &WindowId) -> bool { self.0.contains(id) } } +#[derive(Resource)] +pub struct GenerateWorldProgressChannel(Sender<(f32, String)>, Receiver<(f32, String)>); + +impl GenerateWorldProgressChannel { + pub fn new() -> Self { + bounded(1).into() + } + + pub fn sender(&self) -> Sender<(f32, String)> { + self.0.clone() + } + + pub fn receiver(&self) -> &Receiver<(f32, String)> { + &self.1 + } +} +impl Default for GenerateWorldProgressChannel { + fn default() -> Self { + Self::new() + } +} +impl From<(Sender<(f32, String)>, Receiver<(f32, String)>)> for GenerateWorldProgressChannel { + fn from(value: (Sender<(f32, String)>, Receiver<(f32, String)>)) -> Self { + Self(value.0, value.1) + } +} #[derive(Default, Resource)] pub struct GenerateWorldTask(pub Option>>);