diff --git a/src/main.rs b/src/main.rs index e474530..d272559 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,11 +20,10 @@ pub(crate) mod player; pub(crate) mod prefabs; pub(crate) mod transform; pub(crate) mod settings; -pub(crate) mod state; pub(crate) mod camera; use rendering::{Renderer, RenderTarget, BackgroundColor, clear_background}; -use world::{ChunkStorage, ChunkMeshStorage, loading::update_loaded_world_around_player, render::draw_world}; +use world::{loading::update_loaded_world_around_player, render::draw_world, init_world}; use player::spawn_player; use prefabs::load_prefabs; use settings::GameSettings; @@ -62,12 +61,11 @@ fn main() { //Add systems and uniques, Init and load things world.add_unique_non_send_sync(Renderer::init(&event_loop)); - world.add_unique_non_send_sync(ChunkMeshStorage::new()); - world.add_unique(ChunkStorage::new()); world.add_unique(BackgroundColor(vec3(0.5, 0.5, 1.))); world.add_unique(DeltaTime(Duration::default())); world.add_unique(GameSettings::default()); load_prefabs(&world); + init_world(&world); //Register workloads world.add_workload(startup); diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index 6e1f9a7..0000000 --- a/src/state.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub enum GameState { - MainMenu, - Connecting, - LoadingWorld, - InGame -} diff --git a/src/world.rs b/src/world.rs index 349dc79..a4281e3 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,6 +1,6 @@ use nohash_hasher::BuildNoHashHasher; -use shipyard::Unique; -use glam::{IVec3, ivec3}; +use shipyard::{Unique, World}; +use glam::IVec3; use hashbrown::HashMap; use anyhow::{Result, Context}; @@ -14,6 +14,7 @@ pub mod neighbors; pub mod worldgen; use chunk::{Chunk, ChunkMesh}; +use tasks::ChunkTaskManager; //TODO separate world struct for render data // because this is not send-sync @@ -64,3 +65,9 @@ impl ChunkMeshStorage { self.meshes.get(&key) } } + +pub fn init_world(world: &World) { + world.add_unique_non_send_sync(ChunkMeshStorage::new()); + world.add_unique(ChunkStorage::new()); + world.add_unique(ChunkTaskManager::new()); +} diff --git a/src/world/chunk.rs b/src/world/chunk.rs index cb4a134..20e91e5 100644 --- a/src/world/chunk.rs +++ b/src/world/chunk.rs @@ -8,12 +8,12 @@ pub type BlockData = Box<[[[Block; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]>; pub struct ChunkData { pub blocks: BlockData, - pub has_renderable_blocks: bool, + //pub has_renderable_blocks: bool, } impl ChunkData { - pub fn update_metadata(&mut self) { - todo!() - } + // pub fn update_metadata(&mut self) { + // todo!() + // } } pub struct ChunkMesh { @@ -22,24 +22,33 @@ pub struct ChunkMesh { pub index_buffer: IndexBuffer, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum ChunkState { - ToUnload, //desired only +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum CurrentChunkState { #[default] Nothing, - Loading, //current only + Loading, Loaded, - Meshing, //current only + CalculatingMesh, Rendered, - RecalculatingMesh //current only + RecalculatingMesh, + Unloading, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum DesiredChunkState { + #[default] + Nothing, + Loaded, + Rendered, + ToUnload, } pub struct Chunk { pub position: IVec3, pub block_data: Option, pub mesh_index: Option, - pub current_state: ChunkState, - pub desired_state: ChunkState, + pub current_state: CurrentChunkState, + pub desired_state: DesiredChunkState, } impl Chunk { pub fn new(position: IVec3) -> Self { diff --git a/src/world/loading.rs b/src/world/loading.rs index dab53df..c2302e3 100644 --- a/src/world/loading.rs +++ b/src/world/loading.rs @@ -1,12 +1,24 @@ -use glam::ivec3; -use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync}; -use crate::{player::LocalPlayer, transform::Transform, settings::GameSettings}; -use super::{ChunkStorage, chunk::{Chunk, ChunkState, CHUNK_SIZE}, ChunkMeshStorage}; +use glam::{IVec3, ivec3}; +use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType}; +use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, Nothing}; +use crate::{ + player::LocalPlayer, + transform::Transform, + settings::GameSettings, + rendering::Renderer +}; +use super::{ + ChunkStorage, ChunkMeshStorage, + chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData}, + tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask}, +}; pub fn update_loaded_world_around_player() -> Workload { ( update_chunks_if_player_moved, - unload_marked_chunks + unload_marked_chunks, + start_required_tasks, + process_completed_tasks, ).into_workload() } @@ -31,7 +43,7 @@ pub fn update_chunks_if_player_moved( //Then, mark *ALL* chunks with ToUnload for (_, chunk) in &mut vm_world.chunks { - chunk.desired_state = ChunkState::ToUnload; + chunk.desired_state = DesiredChunkState::ToUnload; } //Then mark chunks that are near to the player @@ -55,8 +67,8 @@ pub fn update_chunks_if_player_moved( } }; let desired = match is_border { - true => ChunkState::Loaded, - false => ChunkState::Rendered, + true => DesiredChunkState::Loaded, + false => DesiredChunkState::Rendered, }; chunk.desired_state = desired; } @@ -72,7 +84,7 @@ fn unload_marked_chunks( return } vm_world.chunks.retain(|_, chunk| { - if chunk.desired_state == ChunkState::ToUnload { + if chunk.desired_state == DesiredChunkState::ToUnload { if let Some(mesh_index) = chunk.mesh_index { vm_meshes.remove(mesh_index).unwrap(); } @@ -83,8 +95,99 @@ fn unload_marked_chunks( }) } -fn process_tasks( - +fn start_required_tasks( + task_manager: UniqueView, + mut world: UniqueViewMut, ) { - + //HACK: cant iterate over chunks.keys() or chunk directly! + let hashmap_keys: Vec = world.chunks.keys().copied().collect(); + for position in hashmap_keys { + let chunk = world.chunks.get(&position).unwrap(); + match chunk.desired_state { + DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => { + //start load task + task_manager.spawn_task(ChunkTask::LoadChunk { + seed: 0xdead_cafe, + position + }); + //Update chunk state + let chunk = world.chunks.get_mut(&position).unwrap(); + chunk.current_state = CurrentChunkState::Loading; + }, + DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Loaded => { + //get needed data + let Some(neighbors) = world.neighbors_all(position) else { + continue + }; + let Some(data) = neighbors.mesh_data() else { + continue + }; + //spawn task + task_manager.spawn_task(ChunkTask::GenerateMesh { data, position }); + //Update chunk state + let chunk = world.chunks.get_mut(&position).unwrap(); + chunk.current_state = CurrentChunkState::CalculatingMesh; + } + _ => () + } + } +} + +fn process_completed_tasks( + task_manager: UniqueView, + mut world: UniqueViewMut, + mut meshes: NonSendSync>, + renderer: NonSendSync> +) { + while let Some(res) = task_manager.receive() { + match res { + ChunkTaskResponse::LoadedChunk { position, chunk_data } => { + //check if chunk exists + let Some(chunk) = world.chunks.get_mut(&position) else { + log::warn!("blocks data discarded: chunk doesn't exist"); + return + }; + + //check if chunk still wants it + if matches!(chunk.desired_state, DesiredChunkState::Loaded | DesiredChunkState::Rendered) { + log::warn!("block data discarded: state undesirable"); + return + } + + //set the block data + chunk.block_data = Some(ChunkData { + blocks: chunk_data + }); + + //update chunk state + chunk.current_state = CurrentChunkState::Loaded; + }, + ChunkTaskResponse::GeneratedMesh { position, vertices, indexes } => { + //check if chunk exists + let Some(chunk) = world.chunks.get_mut(&position) else { + log::warn!("mesh discarded: chunk doesn't exist"); + return + }; + + //check if chunk still wants it + if chunk.desired_state != DesiredChunkState::Rendered { + log::warn!("mesh discarded: state undesirable"); + return + } + + //apply the mesh + let vertex_buffer = VertexBuffer::new(&renderer.display, &vertices).unwrap(); + let index_buffer = IndexBuffer::new(&renderer.display, PrimitiveType::TrianglesList, &indexes).unwrap(); + let mesh_index = meshes.insert(ChunkMesh { + is_dirty: false, + vertex_buffer, + index_buffer, + }); + chunk.mesh_index = Some(mesh_index); + + //update chunk state + chunk.current_state = CurrentChunkState::Rendered; + } + } + } } diff --git a/src/world/mesh.rs b/src/world/mesh.rs index ae5a6cd..6fdcecd 100644 --- a/src/world/mesh.rs +++ b/src/world/mesh.rs @@ -1,2 +1,43 @@ +use strum::{EnumIter, IntoEnumIterator}; +use glam::{Vec3A, vec3a}; + pub mod data; use data::MeshGenData; + +#[repr(usize)] +#[derive(Clone, Copy, Debug, EnumIter)] +pub enum CubeFace { + Top = 0, + Front = 1, + Left = 2, + Right = 3, + Back = 4, + Bottom = 5, +} +const CUBE_FACE_VERTICES: [[Vec3A; 4]; 6] = [ + [vec3a(0., 1., 0.), vec3a(0., 1., 1.), vec3a(1., 1., 0.), vec3a(1., 1., 1.)], + [vec3a(0., 0., 0.), vec3a(0., 1., 0.), vec3a(1., 0., 0.), vec3a(1., 1., 0.)], + [vec3a(0., 0., 1.), vec3a(0., 1., 1.), vec3a(0., 0., 0.), vec3a(0., 1., 0.)], + [vec3a(1., 0., 0.), vec3a(1., 1., 0.), vec3a(1., 0., 1.), vec3a(1., 1., 1.)], + [vec3a(1., 0., 1.), vec3a(1., 1., 1.), vec3a(0., 0., 1.), vec3a(0., 1., 1.)], + [vec3a(0., 0., 1.), vec3a(0., 0., 0.), vec3a(1., 0., 1.), vec3a(1., 0., 0.)], +]; +const CUBE_FACE_NORMALS: [Vec3A; 6] = [ //this is likely incorrect for a right handed system + vec3a(0., 1., 0.), + vec3a(0., 0., 1.), + vec3a(-1.,0., 0.), + vec3a(1., 0., 0.), + vec3a(0., 0., -1.), + vec3a(0., -1.,0.) +]; +const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3]; +const UV_COORDS: [[f32; 2]; 4] = [ + [0., 0.], + [0., 1.], + [1., 0.], + [1., 1.], +]; + +pub fn generate_mesh(data: MeshGenData) { + +} diff --git a/src/world/tasks.rs b/src/world/tasks.rs index e2f1263..2047d80 100644 --- a/src/world/tasks.rs +++ b/src/world/tasks.rs @@ -1,5 +1,6 @@ use flume::{Sender, Receiver}; use glam::IVec3; +use shipyard::Unique; use super::{ chunk::BlockData, render::ChunkVertex, @@ -29,19 +30,20 @@ pub enum ChunkTaskResponse { }, } +#[derive(Unique)] pub struct ChunkTaskManager { channel: (Sender, Receiver), } impl ChunkTaskManager { pub fn new() -> Self { Self { - channel: flume::bounded::(0), + channel: flume::unbounded::(), //maybe put a bound or even bound(0)? } } pub fn spawn_task(&self, task: ChunkTask) { let sender = self.channel.0.clone(); rayon::spawn(move || { - sender.send(match task { + let _ = sender.send(match task { ChunkTask::GenerateMesh { position, data } => { todo!() }, @@ -52,4 +54,7 @@ impl ChunkTaskManager { }); }); } + pub fn receive(&self) -> Option { + self.channel.1.try_recv().ok() + } }