Make world generation asynchronous; Update dependencies (Bevy 0.9, ayyy)

This commit is contained in:
Tobias Berger 2022-11-13 21:22:42 +01:00
parent 030f590d79
commit f65f29e6b0
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
17 changed files with 535 additions and 503 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
.fleet/run.json

614
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "worlds-history-sim-rs" name = "worlds-history-sim-rs"
version = "0.2.1" version = "0.3.0"
edition = "2021" edition = "2021"
resolver = "2" resolver = "2"
@ -35,7 +35,7 @@ path = "planet"
default-features = false default-features = false
[dependencies.bevy] [dependencies.bevy]
version = "0.8.1" version = "0.9.0"
default-features = false default-features = false
[dependencies.fxhash] [dependencies.fxhash]
@ -43,7 +43,7 @@ version = "0.2.1"
optional = true optional = true
[dependencies.bevy_egui] [dependencies.bevy_egui]
version = "0.16.1" version = "0.17"
optional = true optional = true
default-features = false default-features = false
features = ["manage_clipboard"] features = ["manage_clipboard"]
@ -51,3 +51,7 @@ features = ["manage_clipboard"]
[dependencies.tinyfiledialogs] [dependencies.tinyfiledialogs]
version = "3.9.1" version = "3.9.1"
optional = true optional = true
[dependencies.futures-lite]
version = "1.12.0"
default-features = false

View file

@ -1,6 +1,6 @@
[package] [package]
name = "planet" name = "planet"
version = "0.2.1" version = "0.3.0"
edition = "2021" edition = "2021"
[profile] [profile]
@ -15,7 +15,7 @@ default = ["logging", "render"]
version = "0.8.5" version = "0.8.5"
[dependencies.bevy] [dependencies.bevy]
version = "0.8" version = "0.9.0"
default-features = false default-features = false
[dependencies.serde] [dependencies.serde]
@ -24,4 +24,4 @@ default-features = false
features = ["derive"] features = ["derive"]
[dependencies.ron] [dependencies.ron]
version = "0.7.1" version = "0.8.0"

View file

@ -1,4 +1,3 @@
// TODO: Logging doesn't seem to work here? Figure out why and fix
use { use {
crate::{ crate::{
math_util::{ math_util::{
@ -149,6 +148,27 @@ impl World {
} }
} }
pub fn async_new(width: u32, height: u32, seed: u32) -> World {
World {
width,
height,
seed,
terrain: vec![
vec![TerrainCell::default(); width.try_into().unwrap()];
height.try_into().unwrap()
],
continent_offsets: [default(); World::NUM_CONTINENTS as usize],
continent_sizes: [default(); World::NUM_CONTINENTS as usize],
max_altitude: World::MIN_ALTITUDE,
min_altitude: World::MAX_ALTITUDE,
max_rainfall: World::MIN_RAINFALL,
min_rainfall: World::MAX_RAINFALL,
max_temperature: World::MIN_TEMPERATURE,
min_temperature: World::MAX_TEMPERATURE,
rng: StdRng::seed_from_u64(seed as u64),
}
}
pub fn generate(&mut self) -> Result<(), WorldGenError> { pub fn generate(&mut self) -> Result<(), WorldGenError> {
if let Err(err) = self.generate_altitude() { if let Err(err) = self.generate_altitude() {
return Err(WorldGenError::CartesianError(err)); return Err(WorldGenError::CartesianError(err));

View file

@ -1,6 +1,11 @@
use { use {
crate::{World, WorldGenError}, crate::{World, WorldGenError},
bevy::{log::warn, utils::default}, bevy::{
log::warn,
prelude::Resource,
tasks::{AsyncComputeTaskPool, Task},
utils::default,
},
rand::random, rand::random,
std::{ std::{
error::Error, error::Error,
@ -14,7 +19,7 @@ use {
#[derive(Debug)] #[derive(Debug)]
pub enum LoadError { pub enum LoadError {
MissingSave(io::Error), MissingSave(io::Error),
InvalidSave(ron::Error), InvalidSave(ron::error::SpannedError),
} }
impl Error for LoadError { impl Error for LoadError {
fn source(&self) -> Option<&(dyn Error + 'static)> { fn source(&self) -> Option<&(dyn Error + 'static)> {
@ -74,12 +79,15 @@ impl Display for SaveError {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Resource)]
pub struct WorldManager { pub struct WorldManager {
world: Option<World>, world: Option<World>,
} }
impl WorldManager { impl WorldManager {
const NEW_WORLD_HEIGHT: u32 = 200;
const NEW_WORLD_WIDTH: u32 = 400;
#[must_use] #[must_use]
pub fn new() -> WorldManager { pub fn new() -> WorldManager {
default() default()
@ -160,11 +168,34 @@ impl WorldManager {
self.get_world().unwrap() self.get_world().unwrap()
} }
pub fn set_world(&mut self, world: World) {
self.world = Some(world);
}
pub fn new_world(&mut self, seed: Option<u32>) -> Result<&World, WorldGenError> { pub fn new_world(&mut self, seed: Option<u32>) -> Result<&World, WorldGenError> {
let seed = seed.unwrap_or_else(random); let seed = seed.unwrap_or_else(random);
let mut new_world = World::new(400, 200, seed); let mut new_world = World::new(
WorldManager::NEW_WORLD_WIDTH,
WorldManager::NEW_WORLD_HEIGHT,
seed,
);
new_world.generate()?; new_world.generate()?;
self.world = Some(new_world); self.world = Some(new_world);
Ok(self.get_world().unwrap()) Ok(self.get_world().unwrap())
} }
pub fn new_world_async(&mut self, seed: Option<u32>) -> Task<Result<World, WorldGenError>> {
AsyncComputeTaskPool::get().spawn(async move {
let seed = seed.unwrap_or_else(random);
let mut new_world = World::async_new(
WorldManager::NEW_WORLD_WIDTH,
WorldManager::NEW_WORLD_HEIGHT,
seed,
);
match new_world.generate() {
Ok(()) => Ok(new_world),
Err(err) => Err(err),
}
})
}
} }

View file

@ -1,2 +1 @@
#[cfg(feature = "render")]
pub(crate) mod panning;

View file

@ -1,12 +0,0 @@
use bevy::ecs::component::Component;
#[derive(Component)]
pub(crate) struct Pan2d {
pub(crate) enabled: bool,
}
impl Pan2d {
#[must_use]
pub(crate) const fn new() -> Pan2d {
Pan2d { enabled: true }
}
}

View file

@ -6,6 +6,7 @@ use {
world::World, world::World,
}, },
log::debug, log::debug,
prelude::*,
utils::HashMap, utils::HashMap,
}, },
bevy_egui::egui::Ui, bevy_egui::egui::Ui,
@ -44,8 +45,8 @@ pub(crate) fn widget<S: 'static + WidgetSystem>(world: &mut World, ui: &mut Ui,
/// A UI widget may have multiple instances. We need to ensure the local state /// A UI widget may have multiple instances. We need to ensure the local state
/// of these instances is not shared. This hashmap allows us to dynamically /// of these instances is not shared. This hashmap allows us to dynamically
/// store instance states. /// store instance states.
#[derive(Default)] #[derive(Default, Resource)]
struct StateInstances<T: WidgetSystem> { struct StateInstances<T: WidgetSystem + 'static> {
instances: HashMap<WidgetId, SystemState<T>>, instances: HashMap<WidgetId, SystemState<T>>,
} }

View file

@ -7,7 +7,7 @@ use {
WidgetSystem, WidgetSystem,
}, },
macros::iterable_enum, macros::iterable_enum,
resources::{OpenedWindows, ShouldRedraw}, resources::{GenerateWorldTask, OpenedWindows},
}, },
bevy::{ bevy::{
ecs::{ ecs::{
@ -34,10 +34,11 @@ impl ToolbarButton {
world.resource_scope(|world, mut world_manager: Mut<'_, WorldManager>| { world.resource_scope(|world, mut world_manager: Mut<'_, WorldManager>| {
match self { match self {
ToolbarButton::GenerateWorld => { ToolbarButton::GenerateWorld => {
if let Err(err) = world_manager.new_world(None) { let generate_world_task = &mut world.resource_mut::<GenerateWorldTask>();
eprintln!("Failed to generate world: {}", err); if generate_world_task.0.is_some() {
debug!("Already generating new world")
} else { } else {
world.resource_mut::<ShouldRedraw>().0 = true; generate_world_task.0 = Some(world_manager.new_world_async(None))
} }
}, },
ToolbarButton::SaveLoad => { ToolbarButton::SaveLoad => {

View file

@ -8,6 +8,7 @@ use {
world::World, world::World,
}, },
log::debug, log::debug,
prelude::Resource,
utils::HashMap, utils::HashMap,
}, },
bevy_egui::egui::{Context, Ui, Window}, bevy_egui::egui::{Context, Ui, Window},
@ -80,8 +81,8 @@ fn window<S: 'static + WindowSystem>(world: &mut World, ctx: &Context) {
}); });
} }
#[derive(Default)] #[derive(Default, Resource)]
struct StateInstances<T: WindowSystem> { struct StateInstances<T: WindowSystem + 'static> {
instances: HashMap<WindowId, SystemState<T>>, instances: HashMap<WindowId, SystemState<T>>,
} }

View file

@ -1,5 +1,10 @@
#![cfg_attr(not(feature = "logging"), windows_subsystem = "windows")] #![cfg_attr(not(feature = "logging"), windows_subsystem = "windows")]
use {
crate::resources::GenerateWorldTask,
futures_lite::future::{block_on, poll_once},
};
pub(crate) mod components; pub(crate) mod components;
#[cfg(feature = "render")] #[cfg(feature = "render")]
pub(crate) mod gui; pub(crate) mod gui;
@ -7,52 +12,16 @@ pub(crate) mod macros;
#[cfg(feature = "render")] #[cfg(feature = "render")]
pub(crate) mod planet_renderer; pub(crate) mod planet_renderer;
pub(crate) mod plugins; pub(crate) mod plugins;
#[cfg(feature = "render")]
pub(crate) mod resources; pub(crate) mod resources;
use { use {bevy::prelude::*, planet::WorldManager, plugins::WorldPlugins};
bevy::{
app::App,
log::LogSettings,
utils::{default, tracing::Level},
},
planet::WorldManager,
plugins::WorldPlugins,
};
#[cfg(feature = "render")] #[cfg(feature = "render")]
use { use {
bevy::{ bevy::render::camera::RenderTarget,
asset::Assets,
core_pipeline::core_2d::{Camera2d, Camera2dBundle},
ecs::{
change_detection::{Mut, ResMut},
query::With,
system::{Commands, IntoExclusiveSystem, Query, Res},
world::World,
},
input::{keyboard::KeyCode, Input},
prelude::Vec2,
render::{
camera::{Camera, RenderTarget},
render_resource::{
Extent3d,
TextureDescriptor,
TextureDimension,
TextureFormat,
TextureUsages,
},
texture::{Image, ImageSettings},
},
sprite::{Sprite, SpriteBundle},
transform::components::GlobalTransform,
window::{WindowDescriptor, Windows},
winit::WinitSettings,
},
bevy_egui::{ bevy_egui::{
egui::{FontData, FontDefinitions, FontFamily}, egui::{FontData, FontDefinitions, FontFamily},
EguiContext, EguiContext,
}, },
components::panning::Pan2d,
gui::{render_windows, widget, widgets::ToolbarWidget, window::open_window, windows::TileInfo}, gui::{render_windows, widget, widgets::ToolbarWidget, window::open_window, windows::TileInfo},
planet_renderer::{WorldRenderSettings, WorldRenderer}, planet_renderer::{WorldRenderSettings, WorldRenderer},
resources::{CursorMapPosition, OpenedWindows, ShouldRedraw}, resources::{CursorMapPosition, OpenedWindows, ShouldRedraw},
@ -98,16 +67,45 @@ fn update_cursor_map_position(
} }
} }
fn handle_generate_world_task(
mut generate_world_task: ResMut<'_, GenerateWorldTask>,
mut world_manager: ResMut<'_, WorldManager>,
#[cfg(feature = "render")] mut should_redraw: ResMut<'_, ShouldRedraw>,
) {
if let Some(task) = &mut generate_world_task.0 {
if task.is_finished() {
debug!("Done");
if let Some(result) = block_on(poll_once(task)) {
match result {
Ok(world) => {
world_manager.set_world(world);
#[cfg(feature = "render")]
{
should_redraw.0 = true;
}
},
Err(err) => error!("{err:#?}"),
}
}
generate_world_task.0 = None;
} else {
debug!("Working")
}
}
}
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn generate_graphics( fn generate_graphics(
mut commands: Commands<'_, '_>, mut commands: Commands<'_, '_>,
world_manager: ResMut<'_, WorldManager>, world_manager: ResMut<'_, WorldManager>,
mut images: ResMut<'_, Assets<Image>>, images: ResMut<'_, Assets<Image>>,
mut egui_context: ResMut<'_, EguiContext>, egui_context: ResMut<'_, EguiContext>,
mut render_settings: ResMut<'_, WorldRenderSettings>, render_settings: ResMut<'_, WorldRenderSettings>,
) { ) {
// Add Julia-Mono font to egui // Add Julia-Mono font to egui
{ {
let egui_context = egui_context.into_inner();
let ctx = egui_context.ctx_mut(); let ctx = egui_context.ctx_mut();
let mut fonts = FontDefinitions::default(); let mut fonts = FontDefinitions::default();
const FONT_NAME: &str = "Julia-Mono"; const FONT_NAME: &str = "Julia-Mono";
@ -143,6 +141,14 @@ fn generate_graphics(
}; };
// Set up 2D map mode // Set up 2D map mode
{ {
use bevy::render::render_resource::{
TextureDescriptor,
TextureDimension,
TextureFormat,
TextureUsages,
};
let images = images.into_inner();
let mut render_settings = render_settings.into_inner();
let map_image_handle = images.add(Image { let map_image_handle = images.add(Image {
data: vec![], data: vec![],
texture_descriptor: TextureDescriptor { texture_descriptor: TextureDescriptor {
@ -156,14 +162,13 @@ fn generate_graphics(
}, },
..default() ..default()
}); });
render_settings.map_image_handle_id = Some(map_image_handle.id); render_settings.map_image_handle_id = Some(map_image_handle.id());
_ = commands _ = commands.spawn(Camera2dBundle::default());
.spawn_bundle(Camera2dBundle::default())
.insert(Pan2d::new());
// TODO: Switch to egui // TODO: Switch to egui
_ = commands.spawn_bundle(SpriteBundle { _ = commands.spawn(SpriteBundle {
texture: images.get_handle(map_image_handle.id), texture: images
.get_handle(unsafe { render_settings.map_image_handle_id.unwrap_unchecked() }),
sprite: Sprite { sprite: Sprite {
custom_size: Some(custom_sprite_size), custom_size: Some(custom_sprite_size),
..default() ..default()
@ -234,7 +239,7 @@ fn redraw_map(
let map_image = images let map_image = images
.get_mut(&map_image_handle) .get_mut(&map_image_handle)
.expect("Map image handle pointing to non-existing image"); .expect("Map image handle pointing to non-existing image");
map_image.resize(Extent3d { map_image.resize(bevy::render::render_resource::Extent3d {
width: world_manager.world().width, width: world_manager.world().width,
height: world_manager.world().height, height: world_manager.world().height,
depth_or_array_layers: 1, depth_or_array_layers: 1,
@ -246,48 +251,49 @@ fn redraw_map(
} }
#[cfg(feature = "render")] #[cfg(feature = "render")]
const WORLD_SCALE: i32 = 4; const WORLD_SCALE: i32 = 2;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut app = App::new(); let mut app = App::new();
let mut manager = WorldManager::new(); let mut manager = WorldManager::new();
#[cfg(feature = "render")] #[cfg(feature = "render")]
{ {
let world = manager.new_world(None)?; use bevy::winit::WinitSettings;
let world = manager.new_world(Some(0))?;
_ = app _ = app
.insert_resource(WinitSettings::game()) .insert_resource(WinitSettings::game())
// Use nearest-neighbor rendering for cripsier pixels .insert_resource(CursorMapPosition::default())
.insert_resource(ImageSettings::default_nearest()) .insert_resource(OpenedWindows::default())
.insert_resource(WindowDescriptor { .insert_resource(WorldRenderSettings::default())
.insert_resource(ShouldRedraw::default())
.insert_resource(GenerateWorldTask::default())
.add_startup_system(generate_graphics)
.add_system(update_gui)
.add_system(update_cursor_map_position)
.add_system(open_tile_info)
.add_system(redraw_map);
app.add_plugins(WorldPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: (WORLD_SCALE * world.width as i32) as f32, width: (WORLD_SCALE * world.width as i32) as f32,
height: (WORLD_SCALE * world.height as i32) as f32, height: (WORLD_SCALE * world.height as i32) as f32,
title: String::from("World-RS"), title: String::from("World-RS"),
resizable: true, resizable: true,
..default() ..default()
}) },
.insert_resource(CursorMapPosition::default()) ..default()
.insert_resource(OpenedWindows::default()) }));
.insert_resource(WorldRenderSettings::default())
.insert_resource(ShouldRedraw::default())
.add_startup_system(generate_graphics)
.add_system(update_gui.exclusive_system())
.add_system(update_cursor_map_position)
.add_system(open_tile_info)
.add_system(redraw_map);
} }
#[cfg(not(feature = "render"))] #[cfg(not(feature = "render"))]
{ {
_ = manager.new_world()? _ = manager.new_world(Some(0))?;
app.add_plugins(WorldPlugins);
} }
_ = app.insert_resource(LogSettings { app.add_system(handle_generate_world_task)
#[cfg(feature = "logging")] .insert_resource(manager)
level: Level::DEBUG, .run();
#[cfg(not(feature = "logging"))]
level: Level::WARN,
..default()
});
app.add_plugins(WorldPlugins).insert_resource(manager).run();
Ok(()) Ok(())
} }

View file

@ -1,6 +1,10 @@
use { use {
crate::macros::iterable_enum_stringify, crate::macros::iterable_enum_stringify,
bevy::{asset::HandleId, prelude::Color, utils::HashSet}, bevy::{
asset::HandleId,
prelude::{Color, Resource},
utils::HashSet,
},
planet::{BiomeStats, TerrainCell, World, WorldManager}, planet::{BiomeStats, TerrainCell, World, WorldManager},
}; };
@ -15,7 +19,7 @@ iterable_enum_stringify!(WorldOverlay {
}); });
#[cfg(feature = "render")] #[cfg(feature = "render")]
#[derive(Debug, Default)] #[derive(Debug, Default, Resource)]
pub struct WorldRenderSettings { pub struct WorldRenderSettings {
pub map_image_handle_id: Option<HandleId>, pub map_image_handle_id: Option<HandleId>,

View file

@ -1,4 +1,2 @@
#[cfg(feature = "render")]
pub(crate) mod panning_plugin;
pub(crate) mod world_plugins; pub(crate) mod world_plugins;
pub(crate) use world_plugins::WorldPlugins; pub(crate) use world_plugins::WorldPlugins;

View file

@ -1,47 +0,0 @@
use {
crate::components::panning::Pan2d,
bevy::{
app::{App, Plugin},
ecs::{
event::EventReader,
query::With,
system::{Query, Res},
},
input::{
mouse::{MouseButton, MouseMotion},
Input,
},
sprite::Sprite,
transform::components::Transform,
},
};
#[derive(Default)]
pub(crate) struct PanningPlugin;
impl Plugin for PanningPlugin {
fn build(&self, app: &mut App) {
_ = app.add_system(panning_system_2d);
}
}
fn panning_system_2d(
mut query: Query<'_, '_, (&mut Transform, &Pan2d), With<Sprite>>,
mut mouse_motion_events: EventReader<'_, '_, MouseMotion>,
input_mouse: Res<'_, Input<MouseButton>>,
) {
if !input_mouse.pressed(MouseButton::Left) {
return;
}
let mut horizontal = 0.0;
for movement in mouse_motion_events.iter() {
horizontal += movement.delta.x;
}
query.for_each_mut(|(mut transform, pan)| {
if pan.enabled {
transform.translation.x += horizontal;
}
});
}

View file

@ -1,25 +1,21 @@
pub(crate) struct WorldPlugins; pub(crate) struct WorldPlugins;
#[cfg(not(feature = "render"))]
use bevy::app::ScheduleRunnerPlugin;
#[cfg(all(feature = "logging"))]
use bevy::diagnostic::{DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
use bevy::{ use bevy::{
app::{PluginGroup, PluginGroupBuilder}, app::{PluginGroup, PluginGroupBuilder},
core::CorePlugin, core::CorePlugin,
diagnostic::{DiagnosticsPlugin, LogDiagnosticsPlugin}, log::{Level, LogPlugin},
log::LogPlugin, prelude::*,
time::TimePlugin, time::TimePlugin,
}; };
impl PluginGroup for WorldPlugins {
fn build(&mut self, group: &mut PluginGroupBuilder) {
_ = group.add(LogPlugin).add(CorePlugin).add(TimePlugin);
#[cfg(feature = "render")] #[cfg(feature = "render")]
{
use { use {
crate::plugins::panning_plugin::PanningPlugin,
bevy::{ bevy::{
asset::AssetPlugin, asset::AssetPlugin,
core_pipeline::CorePipelinePlugin, core_pipeline::CorePipelinePlugin,
hierarchy::HierarchyPlugin,
input::InputPlugin, input::InputPlugin,
render::RenderPlugin, render::RenderPlugin,
sprite::SpritePlugin, sprite::SpritePlugin,
@ -32,34 +28,48 @@ impl PluginGroup for WorldPlugins {
bevy_egui::EguiPlugin, bevy_egui::EguiPlugin,
}; };
_ = group impl PluginGroup for WorldPlugins {
fn build(self) -> PluginGroupBuilder {
let mut group_builder = PluginGroupBuilder::start::<Self>()
.add(LogPlugin {
#[cfg(feature = "logging")]
level: Level::DEBUG,
#[cfg(not(feature = "logging"))]
level: Level::WARN,
..default()
})
.add(CorePlugin::default()) // sets compute pool config
.add(TimePlugin);
#[cfg(feature = "render")]
{
group_builder = group_builder
.add(TransformPlugin) .add(TransformPlugin)
// hierarchy
.add(InputPlugin) .add(InputPlugin)
.add(WindowPlugin) .add(WindowPlugin::default())
.add(AssetPlugin) .add(AssetPlugin::default())
.add(HierarchyPlugin)
.add(WinitPlugin)
.add(RenderPlugin) .add(RenderPlugin)
.add(ImagePlugin::default_nearest())
.add(WinitPlugin)
.add(CorePipelinePlugin) .add(CorePipelinePlugin)
.add(SpritePlugin) .add(SpritePlugin)
.add(TextPlugin) .add(TextPlugin)
.add(UiPlugin) .add(UiPlugin)
.add(PanningPlugin)
.add(EguiPlugin); .add(EguiPlugin);
} }
#[cfg(not(feature = "render"))] #[cfg(not(feature = "render"))]
{ {
use bevy::app::ScheduleRunnerPlugin; group_builder = group_builder.add(ScheduleRunnerPlugin);
_ = group.add(ScheduleRunnerPlugin);
} }
_ = group.add(DiagnosticsPlugin); #[cfg(feature = "logging")]
#[cfg(all(feature = "logging"))]
{ {
use bevy::diagnostic::FrameTimeDiagnosticsPlugin; group_builder = group_builder
_ = group.add(FrameTimeDiagnosticsPlugin); .add(DiagnosticsPlugin)
.add(FrameTimeDiagnosticsPlugin)
.add(LogDiagnosticsPlugin::default());
} }
_ = group.add(LogDiagnosticsPlugin::default());
group_builder
} }
} }

View file

@ -1,26 +1,38 @@
#[cfg(feature = "render")]
use {crate::gui::WindowId, bevy::utils::HashSet, std::fmt::Display}; use {crate::gui::WindowId, bevy::utils::HashSet, std::fmt::Display};
use {
bevy::{prelude::Resource, tasks::Task},
planet::{World, WorldGenError},
};
#[derive(Default, Debug)] #[cfg(feature = "render")]
#[derive(Default, Debug, Resource)]
pub(crate) struct CursorMapPosition { pub(crate) struct CursorMapPosition {
pub(crate) x: i32, pub(crate) x: i32,
pub(crate) y: i32, pub(crate) y: i32,
} }
#[cfg(feature = "render")]
impl Display for CursorMapPosition { impl Display for CursorMapPosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y)) f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
} }
} }
#[cfg(feature = "render")]
#[derive(Resource)]
pub(crate) struct ShouldRedraw(pub(crate) bool); pub(crate) struct ShouldRedraw(pub(crate) bool);
#[cfg(feature = "render")]
impl Default for ShouldRedraw { impl Default for ShouldRedraw {
fn default() -> Self { fn default() -> Self {
Self(true) Self(true)
} }
} }
#[derive(Default)] #[cfg(feature = "render")]
#[derive(Default, Resource)]
pub(crate) struct OpenedWindows(HashSet<WindowId>); pub(crate) struct OpenedWindows(HashSet<WindowId>);
#[cfg(feature = "render")]
impl OpenedWindows { impl OpenedWindows {
pub(crate) fn open(&mut self, id: WindowId) { pub(crate) fn open(&mut self, id: WindowId) {
// Ignore opening already opened windows // Ignore opening already opened windows
@ -36,3 +48,6 @@ impl OpenedWindows {
self.0.contains(id) self.0.contains(id)
} }
} }
#[derive(Default, Resource)]
pub struct GenerateWorldTask(pub Option<Task<Result<World, WorldGenError>>>);