mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-12-25 05:08:21 -06:00
add trans rendering and crosshair
This commit is contained in:
parent
b95be7fb2a
commit
d57787059a
BIN
assets/blocks/water.png
Normal file
BIN
assets/blocks/water.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 341 B |
|
@ -22,6 +22,7 @@ pub enum BlockTexture {
|
|||
Cobblestone,
|
||||
Planks,
|
||||
WaterSolid,
|
||||
Water,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, EnumIter, TryFromPrimitive)]
|
||||
|
@ -136,7 +137,7 @@ impl Block {
|
|||
},
|
||||
Self::Water => BlockDescriptor {
|
||||
name: "water",
|
||||
render: RenderType::BinaryTransparency(CubeTexture::all(BlockTexture::WaterSolid)),
|
||||
render: RenderType::TransBlock(CubeTexture::all(BlockTexture::Water)),
|
||||
collision: CollisionType::None,
|
||||
raycast_collision: true,
|
||||
drops: None,
|
||||
|
@ -217,6 +218,7 @@ pub enum CollisionType {
|
|||
pub enum RenderType {
|
||||
None,
|
||||
SolidBlock(CubeTexture),
|
||||
TransBlock(CubeTexture),
|
||||
BinaryTransparency(CubeTexture),
|
||||
CrossShape(CrossTexture),
|
||||
}
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
precision lowp sampler2DArray;
|
||||
|
||||
in vec3 v_normal;
|
||||
in vec2 v_uv;
|
||||
flat in uint v_tex_index;
|
||||
out vec4 color;
|
||||
uniform sampler2DArray tex;
|
||||
|
||||
// vec4 alpha_drop(vec4 b, vec4 a) {
|
||||
// if ((a.w < 1.) || (b.w < 1.)) {
|
||||
// return vec4(b.xyz, 0.);
|
||||
// }
|
||||
// return a;
|
||||
// }
|
||||
|
||||
void main() {
|
||||
// base color from texture
|
||||
color = texture(tex, vec3(v_uv, v_tex_index));
|
||||
// discard transparent pixels
|
||||
if (color.w < 0.5) discard;
|
||||
//basic "lighting"
|
||||
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
|
||||
color *= vec4(vec3(light), 1.);
|
||||
//discard alpha
|
||||
color.w = 1.;
|
||||
}
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
precision lowp sampler2DArray;
|
||||
|
||||
in vec3 v_normal;
|
||||
in vec2 v_uv;
|
||||
flat in uint v_tex_index;
|
||||
out vec4 color;
|
||||
uniform sampler2DArray tex;
|
||||
uniform bool discard_alpha;
|
||||
|
||||
// vec4 alpha_drop(vec4 b, vec4 a) {
|
||||
// if ((a.w < 1.) || (b.w < 1.)) {
|
||||
// return vec4(b.xyz, 0.);
|
||||
// }
|
||||
// return a;
|
||||
// }
|
||||
|
||||
void main() {
|
||||
// base color from texture
|
||||
color = texture(tex, vec3(v_uv, v_tex_index));
|
||||
// discard transparent pixels
|
||||
if (color.w < (discard_alpha ? 0.01 : 0.5)) discard;
|
||||
//basic "lighting"
|
||||
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
|
||||
color *= vec4(vec3(light), 1.);
|
||||
//discard alpha
|
||||
if (discard_alpha) color.w = 1.;
|
||||
}
|
||||
|
|
|
@ -15,12 +15,13 @@ use std::time::Instant;
|
|||
|
||||
pub(crate) use kubi_shared::transform;
|
||||
|
||||
mod ui {
|
||||
pub(crate) mod loading_screen;
|
||||
pub(crate) mod connecting_screen;
|
||||
pub(crate) mod chat_ui;
|
||||
}
|
||||
pub(crate) use ui::{loading_screen, connecting_screen, chat_ui};
|
||||
mod ui;
|
||||
pub(crate) use ui::{
|
||||
loading_screen,
|
||||
connecting_screen,
|
||||
chat_ui,
|
||||
crosshair_ui,
|
||||
};
|
||||
|
||||
pub(crate) mod rendering;
|
||||
pub(crate) mod world;
|
||||
|
@ -91,6 +92,7 @@ use filesystem::AssetManager;
|
|||
use client_physics::{init_client_physics, update_client_physics_late};
|
||||
use chat_ui::render_chat;
|
||||
use chat::init_chat_manager;
|
||||
use crosshair_ui::{init_crosshair_image, draw_crosshair};
|
||||
|
||||
/// stuff required to init the renderer and other basic systems
|
||||
fn pre_startup() -> Workload {
|
||||
|
@ -116,6 +118,7 @@ fn startup() -> Workload {
|
|||
init_delta_time,
|
||||
init_client_physics,
|
||||
init_chat_manager,
|
||||
init_crosshair_image,
|
||||
).into_sequential_workload()
|
||||
}
|
||||
|
||||
|
@ -151,6 +154,7 @@ fn update() -> Workload {
|
|||
update_block_placement,
|
||||
apply_queued_blocks,
|
||||
render_chat,
|
||||
draw_crosshair,
|
||||
).into_sequential_workload().run_if(is_ingame),
|
||||
update_networking_late.run_if(is_multiplayer),
|
||||
compute_cameras,
|
||||
|
|
|
@ -34,6 +34,7 @@ impl AssetPaths for BlockTexture {
|
|||
Self::Cobblestone => "cobblestone.png",
|
||||
Self::Planks => "planks.png",
|
||||
Self::WaterSolid => "solid_water.png",
|
||||
Self::Water => "water.png",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ pub fn draw_world(
|
|||
settings: UniqueView<GameSettings>
|
||||
) {
|
||||
let camera = camera.iter().next().expect("No cameras in the scene");
|
||||
let draw_parameters = DrawParameters {
|
||||
let mut draw_parameters = DrawParameters {
|
||||
depth: Depth {
|
||||
test: DepthTest::IfLess,
|
||||
write: true,
|
||||
|
@ -75,6 +75,8 @@ pub fn draw_world(
|
|||
let view = camera.view_matrix.to_cols_array_2d();
|
||||
let perspective = camera.perspective_matrix.to_cols_array_2d();
|
||||
|
||||
let mut enqueue_trans = Vec::new();
|
||||
|
||||
for (&position, chunk) in &chunks.chunks {
|
||||
if let Some(key) = chunk.mesh_index {
|
||||
let mesh = meshes.get(key).expect("Mesh index pointing to nothing");
|
||||
|
@ -107,8 +109,31 @@ pub fn draw_world(
|
|||
},
|
||||
&draw_parameters
|
||||
).unwrap();
|
||||
|
||||
if mesh.trans_index_buffer.len() > 0 {
|
||||
enqueue_trans.push((chunk, mesh));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_parameters.blend = Blend::alpha_blending();
|
||||
draw_parameters.backface_culling = BackfaceCullingMode::CullingDisabled;
|
||||
|
||||
for (chunk, mesh) in enqueue_trans {
|
||||
let world_position = chunk.position.as_vec3() * CHUNK_SIZE as f32;
|
||||
target.0.draw(
|
||||
&mesh.trans_vertex_buffer,
|
||||
&mesh.trans_index_buffer,
|
||||
&program.0,
|
||||
&uniform! {
|
||||
position_offset: world_position.to_array(),
|
||||
view: view,
|
||||
perspective: perspective,
|
||||
tex: texture_sampler,
|
||||
},
|
||||
&draw_parameters
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_current_chunk_border(
|
||||
|
|
4
kubi/src/ui.rs
Normal file
4
kubi/src/ui.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub(crate) mod loading_screen;
|
||||
pub(crate) mod connecting_screen;
|
||||
pub(crate) mod chat_ui;
|
||||
pub(crate) mod crosshair_ui;
|
60
kubi/src/ui/crosshair_ui.rs
Normal file
60
kubi/src/ui/crosshair_ui.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use std::f32::consts::PI;
|
||||
|
||||
use glam::uvec2;
|
||||
use hui::{
|
||||
draw::{ImageHandle, TextureFormat},
|
||||
element::{container::Container, image::Image, transformer::ElementTransformExt, UiElementExt},
|
||||
layout::Alignment,
|
||||
size
|
||||
};
|
||||
use shipyard::{AllStoragesViewMut, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
|
||||
use crate::{hui_integration::UiState, player::MainPlayer, rendering::WindowSize, world::raycast::LookingAtBlock};
|
||||
|
||||
const CROSSHAIR_SIZE: usize = 9;
|
||||
const CROSSHAIR: &[u8] = &[
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct CrosshairImage(ImageHandle);
|
||||
|
||||
pub fn init_crosshair_image(storages: AllStoragesViewMut) {
|
||||
let mut ui = storages.borrow::<NonSendSync<UniqueViewMut<UiState>>>().unwrap();
|
||||
let image = ui.hui.add_image(TextureFormat::Grayscale, CROSSHAIR, CROSSHAIR_SIZE);
|
||||
storages.add_unique(CrosshairImage(image));
|
||||
}
|
||||
|
||||
pub fn draw_crosshair(
|
||||
mut ui: NonSendSync<UniqueViewMut<UiState>>,
|
||||
crosshair: UniqueView<CrosshairImage>,
|
||||
size: UniqueView<WindowSize>,
|
||||
player: View<MainPlayer>,
|
||||
raycast: View<LookingAtBlock>,
|
||||
) {
|
||||
let mut cursor_active = false;
|
||||
if let Some((_, raycast)) = (&player, &raycast).iter().next() {
|
||||
cursor_active = raycast.0.is_some();
|
||||
}
|
||||
|
||||
Container::default()
|
||||
.with_size(size!(100%))
|
||||
.with_align(Alignment::Center)
|
||||
.with_children(|ui| {
|
||||
Image::new(crosshair.0)
|
||||
.with_color((1., 1., 1., 0.5))
|
||||
.with_size(size!(18, 18))
|
||||
.transform()
|
||||
.scale(glam::Vec2::splat(if cursor_active { 1. } else { 0.66 }))
|
||||
.rotate(if cursor_active { 0. } else { PI / 4. })
|
||||
.add_child(ui);
|
||||
})
|
||||
.add_root(&mut ui.hui, uvec2(size.0.x & !1, size.0.y & !1).as_vec2());
|
||||
}
|
|
@ -1,69 +1,71 @@
|
|||
use glam::IVec3;
|
||||
use glium::{VertexBuffer, IndexBuffer};
|
||||
use crate::rendering::world::ChunkVertex;
|
||||
|
||||
pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData};
|
||||
|
||||
pub struct ChunkData {
|
||||
pub blocks: BlockData,
|
||||
//pub has_renderable_blocks: bool,
|
||||
}
|
||||
impl ChunkData {
|
||||
// pub fn update_metadata(&mut self) {
|
||||
// todo!()
|
||||
// }
|
||||
}
|
||||
|
||||
pub struct ChunkMesh {
|
||||
pub vertex_buffer: VertexBuffer<ChunkVertex>,
|
||||
pub index_buffer: IndexBuffer<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum CurrentChunkState {
|
||||
#[default]
|
||||
Nothing,
|
||||
Loading,
|
||||
Loaded,
|
||||
CalculatingMesh,
|
||||
Rendered,
|
||||
RecalculatingMesh,
|
||||
Unloading,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum DesiredChunkState {
|
||||
#[default]
|
||||
Nothing,
|
||||
Loaded,
|
||||
Rendered,
|
||||
ToUnload,
|
||||
}
|
||||
impl DesiredChunkState {
|
||||
pub fn matches_current(self, current: CurrentChunkState) -> bool {
|
||||
(matches!(self, DesiredChunkState::Nothing) && matches!(current, CurrentChunkState::Nothing)) ||
|
||||
(matches!(self, DesiredChunkState::Loaded) && matches!(current, CurrentChunkState::Loaded)) ||
|
||||
(matches!(self, DesiredChunkState::Rendered) && matches!(current, CurrentChunkState::Rendered))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub position: IVec3,
|
||||
pub block_data: Option<ChunkData>,
|
||||
pub mesh_index: Option<usize>,
|
||||
pub current_state: CurrentChunkState,
|
||||
pub desired_state: DesiredChunkState,
|
||||
pub mesh_dirty: bool,
|
||||
}
|
||||
impl Chunk {
|
||||
pub fn new(position: IVec3) -> Self {
|
||||
Self {
|
||||
position,
|
||||
block_data: None,
|
||||
mesh_index: None,
|
||||
current_state: Default::default(),
|
||||
desired_state: Default::default(),
|
||||
mesh_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
use glam::IVec3;
|
||||
use glium::{VertexBuffer, IndexBuffer};
|
||||
use crate::rendering::world::ChunkVertex;
|
||||
|
||||
pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData};
|
||||
|
||||
pub struct ChunkData {
|
||||
pub blocks: BlockData,
|
||||
//pub has_renderable_blocks: bool,
|
||||
}
|
||||
impl ChunkData {
|
||||
// pub fn update_metadata(&mut self) {
|
||||
// todo!()
|
||||
// }
|
||||
}
|
||||
|
||||
pub struct ChunkMesh {
|
||||
pub vertex_buffer: VertexBuffer<ChunkVertex>,
|
||||
pub index_buffer: IndexBuffer<u32>,
|
||||
pub trans_vertex_buffer: VertexBuffer<ChunkVertex>,
|
||||
pub trans_index_buffer: IndexBuffer<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum CurrentChunkState {
|
||||
#[default]
|
||||
Nothing,
|
||||
Loading,
|
||||
Loaded,
|
||||
CalculatingMesh,
|
||||
Rendered,
|
||||
RecalculatingMesh,
|
||||
Unloading,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum DesiredChunkState {
|
||||
#[default]
|
||||
Nothing,
|
||||
Loaded,
|
||||
Rendered,
|
||||
ToUnload,
|
||||
}
|
||||
impl DesiredChunkState {
|
||||
pub fn matches_current(self, current: CurrentChunkState) -> bool {
|
||||
(matches!(self, DesiredChunkState::Nothing) && matches!(current, CurrentChunkState::Nothing)) ||
|
||||
(matches!(self, DesiredChunkState::Loaded) && matches!(current, CurrentChunkState::Loaded)) ||
|
||||
(matches!(self, DesiredChunkState::Rendered) && matches!(current, CurrentChunkState::Rendered))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub position: IVec3,
|
||||
pub block_data: Option<ChunkData>,
|
||||
pub mesh_index: Option<usize>,
|
||||
pub current_state: CurrentChunkState,
|
||||
pub desired_state: DesiredChunkState,
|
||||
pub mesh_dirty: bool,
|
||||
}
|
||||
impl Chunk {
|
||||
pub fn new(position: IVec3) -> Self {
|
||||
Self {
|
||||
position,
|
||||
block_data: None,
|
||||
mesh_index: None,
|
||||
current_state: Default::default(),
|
||||
desired_state: Default::default(),
|
||||
mesh_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,262 +1,267 @@
|
|||
use glam::{IVec3, ivec3};
|
||||
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
|
||||
use kubi_shared::networking::messages::ClientToServerMessage;
|
||||
use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track};
|
||||
use uflow::SendMode;
|
||||
use crate::{
|
||||
player::MainPlayer,
|
||||
transform::Transform,
|
||||
settings::GameSettings,
|
||||
rendering::Renderer,
|
||||
state::GameState,
|
||||
networking::UdpClient,
|
||||
};
|
||||
use super::{
|
||||
ChunkStorage, ChunkMeshStorage,
|
||||
chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData},
|
||||
tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask},
|
||||
queue::BlockUpdateQueue
|
||||
};
|
||||
|
||||
const MAX_CHUNK_OPS_INGAME: usize = 6;
|
||||
const MAX_CHUNK_OPS: usize = 32;
|
||||
|
||||
pub fn update_loaded_world_around_player() -> Workload {
|
||||
(
|
||||
update_chunks_if_player_moved,
|
||||
unload_downgrade_chunks,
|
||||
start_required_tasks,
|
||||
process_completed_tasks,
|
||||
).into_sequential_workload()
|
||||
}
|
||||
|
||||
pub fn update_chunks_if_player_moved(
|
||||
v_settings: UniqueView<GameSettings>,
|
||||
v_local_player: View<MainPlayer>,
|
||||
v_transform: View<Transform, track::All>,
|
||||
mut vm_world: UniqueViewMut<ChunkStorage>,
|
||||
) {
|
||||
//Check if the player actually moved
|
||||
//TODO fix this also triggers on rotation, only activate when the player crosses the chunk border
|
||||
let Some((_, transform)) = (&v_local_player, v_transform.inserted_or_modified()).iter().next() else {
|
||||
return
|
||||
};
|
||||
|
||||
//Read game settings
|
||||
let load_distance = (v_settings.render_distance + 1) as i32;
|
||||
|
||||
//If it did, get it's position and current chunk
|
||||
let player_position = transform.0.to_scale_rotation_translation().2;
|
||||
let player_position_ivec3 = player_position.as_ivec3();
|
||||
let player_at_chunk = ivec3(
|
||||
player_position_ivec3.x.div_euclid(CHUNK_SIZE as i32),
|
||||
player_position_ivec3.y.div_euclid(CHUNK_SIZE as i32),
|
||||
player_position_ivec3.z.div_euclid(CHUNK_SIZE as i32),
|
||||
);
|
||||
|
||||
//Then, mark *ALL* chunks with ToUnload
|
||||
for (_, chunk) in &mut vm_world.chunks {
|
||||
chunk.desired_state = DesiredChunkState::ToUnload;
|
||||
}
|
||||
|
||||
//Then mark chunks that are near to the player
|
||||
for x in -load_distance..=load_distance {
|
||||
for y in -load_distance..=load_distance {
|
||||
for z in -load_distance..=load_distance {
|
||||
let chunk_pos_offset = ivec3(x, y, z);
|
||||
let chunk_pos = player_at_chunk + chunk_pos_offset;
|
||||
let is_border = {
|
||||
chunk_pos_offset.x.abs() == load_distance ||
|
||||
chunk_pos_offset.y.abs() == load_distance ||
|
||||
chunk_pos_offset.z.abs() == load_distance
|
||||
};
|
||||
//If chunk doesn't exist create it
|
||||
let chunk = match vm_world.chunks.get_mut(&chunk_pos) {
|
||||
Some(chunk) => chunk,
|
||||
None => {
|
||||
let chunk = Chunk::new(chunk_pos);
|
||||
vm_world.chunks.insert_unique_unchecked(chunk_pos, chunk);
|
||||
vm_world.chunks.get_mut(&chunk_pos).unwrap()
|
||||
}
|
||||
};
|
||||
let desired = match is_border {
|
||||
true => DesiredChunkState::Loaded,
|
||||
false => DesiredChunkState::Rendered,
|
||||
};
|
||||
chunk.desired_state = desired;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unload_downgrade_chunks(
|
||||
mut vm_world: UniqueViewMut<ChunkStorage>,
|
||||
mut vm_meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>
|
||||
) {
|
||||
if !vm_world.is_modified() {
|
||||
return
|
||||
}
|
||||
//TODO refactor this
|
||||
//TODO unsubscibe if in multiplayer
|
||||
vm_world.chunks.retain(|_, chunk| {
|
||||
if chunk.desired_state == DesiredChunkState::ToUnload {
|
||||
if let Some(mesh_index) = chunk.mesh_index {
|
||||
vm_meshes.remove(mesh_index).unwrap();
|
||||
}
|
||||
false
|
||||
} else {
|
||||
match chunk.desired_state {
|
||||
DesiredChunkState::Loaded if matches!(chunk.current_state, CurrentChunkState::Rendered | CurrentChunkState::CalculatingMesh | CurrentChunkState::RecalculatingMesh) => {
|
||||
if let Some(mesh_index) = chunk.mesh_index {
|
||||
vm_meshes.remove(mesh_index).unwrap();
|
||||
}
|
||||
chunk.mesh_index = None;
|
||||
chunk.current_state = CurrentChunkState::Loaded;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn start_required_tasks(
|
||||
task_manager: UniqueView<ChunkTaskManager>,
|
||||
mut udp_client: Option<UniqueViewMut<UdpClient>>,
|
||||
mut world: UniqueViewMut<ChunkStorage>,
|
||||
) {
|
||||
if !world.is_modified() {
|
||||
return
|
||||
}
|
||||
//HACK: cant iterate over chunks.keys() or chunk directly!
|
||||
let hashmap_keys: Vec<IVec3> = 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
|
||||
if let Some(client) = &mut udp_client {
|
||||
client.0.send(
|
||||
postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest {
|
||||
chunk: position,
|
||||
}).unwrap().into_boxed_slice(),
|
||||
0,
|
||||
SendMode::Reliable
|
||||
);
|
||||
} else {
|
||||
task_manager.spawn_task(ChunkTask::LoadChunk {
|
||||
seed: 0xbeef_face_dead_cafe,
|
||||
position
|
||||
});
|
||||
}
|
||||
//Update chunk state
|
||||
let chunk = world.chunks.get_mut(&position).unwrap();
|
||||
chunk.current_state = CurrentChunkState::Loading;
|
||||
// ===========
|
||||
//log::trace!("Started loading chunk {position}");
|
||||
},
|
||||
DesiredChunkState::Rendered if (chunk.current_state == CurrentChunkState::Loaded || chunk.mesh_dirty) => {
|
||||
//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();
|
||||
if chunk.mesh_dirty {
|
||||
chunk.current_state = CurrentChunkState::RecalculatingMesh;
|
||||
} else {
|
||||
chunk.current_state = CurrentChunkState::CalculatingMesh;
|
||||
}
|
||||
chunk.mesh_dirty = false;
|
||||
// ===========
|
||||
//log::trace!("Started generating mesh for chunk {position}");
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_completed_tasks(
|
||||
task_manager: UniqueView<ChunkTaskManager>,
|
||||
mut world: UniqueViewMut<ChunkStorage>,
|
||||
mut meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>,
|
||||
renderer: NonSendSync<UniqueView<Renderer>>,
|
||||
state: UniqueView<GameState>,
|
||||
mut queue: UniqueViewMut<BlockUpdateQueue>,
|
||||
) {
|
||||
let mut ops: usize = 0;
|
||||
while let Some(res) = task_manager.receive() {
|
||||
match res {
|
||||
ChunkTaskResponse::LoadedChunk { position, chunk_data, mut queued } => {
|
||||
//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: {:?}", chunk.desired_state);
|
||||
return
|
||||
}
|
||||
|
||||
//set the block data
|
||||
chunk.block_data = Some(ChunkData {
|
||||
blocks: chunk_data
|
||||
});
|
||||
|
||||
//update chunk state
|
||||
chunk.current_state = CurrentChunkState::Loaded;
|
||||
|
||||
//push queued blocks
|
||||
queue.0.append(&mut queued);
|
||||
drop(queued); //`queued` is empty after `append`
|
||||
|
||||
//increase ops counter
|
||||
ops += 1;
|
||||
},
|
||||
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: {:?}", chunk.desired_state);
|
||||
return
|
||||
}
|
||||
|
||||
//apply the mesh
|
||||
let vertex_buffer = VertexBuffer::immutable(&renderer.display, &vertices).unwrap();
|
||||
let index_buffer = IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &indexes).unwrap();
|
||||
let mesh = ChunkMesh {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
};
|
||||
if let Some(index) = chunk.mesh_index {
|
||||
meshes.update(index, mesh).expect("Mesh update failed");
|
||||
} else {
|
||||
let mesh_index = meshes.insert(mesh);
|
||||
chunk.mesh_index = Some(mesh_index);
|
||||
}
|
||||
|
||||
//update chunk state
|
||||
chunk.current_state = CurrentChunkState::Rendered;
|
||||
|
||||
//increase ops counter
|
||||
ops += 1;
|
||||
}
|
||||
}
|
||||
if ops >= match *state {
|
||||
GameState::InGame => MAX_CHUNK_OPS_INGAME,
|
||||
_ => MAX_CHUNK_OPS,
|
||||
} { break }
|
||||
}
|
||||
}
|
||||
use glam::{IVec3, ivec3};
|
||||
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
|
||||
use kubi_shared::networking::messages::ClientToServerMessage;
|
||||
use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track};
|
||||
use uflow::SendMode;
|
||||
use crate::{
|
||||
player::MainPlayer,
|
||||
transform::Transform,
|
||||
settings::GameSettings,
|
||||
rendering::Renderer,
|
||||
state::GameState,
|
||||
networking::UdpClient,
|
||||
};
|
||||
use super::{
|
||||
ChunkStorage, ChunkMeshStorage,
|
||||
chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData},
|
||||
tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask},
|
||||
queue::BlockUpdateQueue
|
||||
};
|
||||
|
||||
const MAX_CHUNK_OPS_INGAME: usize = 6;
|
||||
const MAX_CHUNK_OPS: usize = 32;
|
||||
|
||||
pub fn update_loaded_world_around_player() -> Workload {
|
||||
(
|
||||
update_chunks_if_player_moved,
|
||||
unload_downgrade_chunks,
|
||||
start_required_tasks,
|
||||
process_completed_tasks,
|
||||
).into_sequential_workload()
|
||||
}
|
||||
|
||||
pub fn update_chunks_if_player_moved(
|
||||
v_settings: UniqueView<GameSettings>,
|
||||
v_local_player: View<MainPlayer>,
|
||||
v_transform: View<Transform, track::All>,
|
||||
mut vm_world: UniqueViewMut<ChunkStorage>,
|
||||
) {
|
||||
//Check if the player actually moved
|
||||
//TODO fix this also triggers on rotation, only activate when the player crosses the chunk border
|
||||
let Some((_, transform)) = (&v_local_player, v_transform.inserted_or_modified()).iter().next() else {
|
||||
return
|
||||
};
|
||||
|
||||
//Read game settings
|
||||
let load_distance = (v_settings.render_distance + 1) as i32;
|
||||
|
||||
//If it did, get it's position and current chunk
|
||||
let player_position = transform.0.to_scale_rotation_translation().2;
|
||||
let player_position_ivec3 = player_position.as_ivec3();
|
||||
let player_at_chunk = ivec3(
|
||||
player_position_ivec3.x.div_euclid(CHUNK_SIZE as i32),
|
||||
player_position_ivec3.y.div_euclid(CHUNK_SIZE as i32),
|
||||
player_position_ivec3.z.div_euclid(CHUNK_SIZE as i32),
|
||||
);
|
||||
|
||||
//Then, mark *ALL* chunks with ToUnload
|
||||
for (_, chunk) in &mut vm_world.chunks {
|
||||
chunk.desired_state = DesiredChunkState::ToUnload;
|
||||
}
|
||||
|
||||
//Then mark chunks that are near to the player
|
||||
for x in -load_distance..=load_distance {
|
||||
for y in -load_distance..=load_distance {
|
||||
for z in -load_distance..=load_distance {
|
||||
let chunk_pos_offset = ivec3(x, y, z);
|
||||
let chunk_pos = player_at_chunk + chunk_pos_offset;
|
||||
let is_border = {
|
||||
chunk_pos_offset.x.abs() == load_distance ||
|
||||
chunk_pos_offset.y.abs() == load_distance ||
|
||||
chunk_pos_offset.z.abs() == load_distance
|
||||
};
|
||||
//If chunk doesn't exist create it
|
||||
let chunk = match vm_world.chunks.get_mut(&chunk_pos) {
|
||||
Some(chunk) => chunk,
|
||||
None => {
|
||||
let chunk = Chunk::new(chunk_pos);
|
||||
vm_world.chunks.insert_unique_unchecked(chunk_pos, chunk);
|
||||
vm_world.chunks.get_mut(&chunk_pos).unwrap()
|
||||
}
|
||||
};
|
||||
let desired = match is_border {
|
||||
true => DesiredChunkState::Loaded,
|
||||
false => DesiredChunkState::Rendered,
|
||||
};
|
||||
chunk.desired_state = desired;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unload_downgrade_chunks(
|
||||
mut vm_world: UniqueViewMut<ChunkStorage>,
|
||||
mut vm_meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>
|
||||
) {
|
||||
if !vm_world.is_modified() {
|
||||
return
|
||||
}
|
||||
//TODO refactor this
|
||||
//TODO unsubscibe if in multiplayer
|
||||
vm_world.chunks.retain(|_, chunk| {
|
||||
if chunk.desired_state == DesiredChunkState::ToUnload {
|
||||
if let Some(mesh_index) = chunk.mesh_index {
|
||||
vm_meshes.remove(mesh_index).unwrap();
|
||||
}
|
||||
false
|
||||
} else {
|
||||
match chunk.desired_state {
|
||||
DesiredChunkState::Loaded if matches!(chunk.current_state, CurrentChunkState::Rendered | CurrentChunkState::CalculatingMesh | CurrentChunkState::RecalculatingMesh) => {
|
||||
if let Some(mesh_index) = chunk.mesh_index {
|
||||
vm_meshes.remove(mesh_index).unwrap();
|
||||
}
|
||||
chunk.mesh_index = None;
|
||||
chunk.current_state = CurrentChunkState::Loaded;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn start_required_tasks(
|
||||
task_manager: UniqueView<ChunkTaskManager>,
|
||||
mut udp_client: Option<UniqueViewMut<UdpClient>>,
|
||||
mut world: UniqueViewMut<ChunkStorage>,
|
||||
) {
|
||||
if !world.is_modified() {
|
||||
return
|
||||
}
|
||||
//HACK: cant iterate over chunks.keys() or chunk directly!
|
||||
let hashmap_keys: Vec<IVec3> = 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
|
||||
if let Some(client) = &mut udp_client {
|
||||
client.0.send(
|
||||
postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest {
|
||||
chunk: position,
|
||||
}).unwrap().into_boxed_slice(),
|
||||
0,
|
||||
SendMode::Reliable
|
||||
);
|
||||
} else {
|
||||
task_manager.spawn_task(ChunkTask::LoadChunk {
|
||||
seed: 0xbeef_face_dead_cafe,
|
||||
position
|
||||
});
|
||||
}
|
||||
//Update chunk state
|
||||
let chunk = world.chunks.get_mut(&position).unwrap();
|
||||
chunk.current_state = CurrentChunkState::Loading;
|
||||
// ===========
|
||||
//log::trace!("Started loading chunk {position}");
|
||||
},
|
||||
DesiredChunkState::Rendered if (chunk.current_state == CurrentChunkState::Loaded || chunk.mesh_dirty) => {
|
||||
//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();
|
||||
if chunk.mesh_dirty {
|
||||
chunk.current_state = CurrentChunkState::RecalculatingMesh;
|
||||
} else {
|
||||
chunk.current_state = CurrentChunkState::CalculatingMesh;
|
||||
}
|
||||
chunk.mesh_dirty = false;
|
||||
// ===========
|
||||
//log::trace!("Started generating mesh for chunk {position}");
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_completed_tasks(
|
||||
task_manager: UniqueView<ChunkTaskManager>,
|
||||
mut world: UniqueViewMut<ChunkStorage>,
|
||||
mut meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>,
|
||||
renderer: NonSendSync<UniqueView<Renderer>>,
|
||||
state: UniqueView<GameState>,
|
||||
mut queue: UniqueViewMut<BlockUpdateQueue>,
|
||||
) {
|
||||
let mut ops: usize = 0;
|
||||
while let Some(res) = task_manager.receive() {
|
||||
match res {
|
||||
ChunkTaskResponse::LoadedChunk { position, chunk_data, mut queued } => {
|
||||
//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: {:?}", chunk.desired_state);
|
||||
return
|
||||
}
|
||||
|
||||
//set the block data
|
||||
chunk.block_data = Some(ChunkData {
|
||||
blocks: chunk_data
|
||||
});
|
||||
|
||||
//update chunk state
|
||||
chunk.current_state = CurrentChunkState::Loaded;
|
||||
|
||||
//push queued blocks
|
||||
queue.0.append(&mut queued);
|
||||
drop(queued); //`queued` is empty after `append`
|
||||
|
||||
//increase ops counter
|
||||
ops += 1;
|
||||
},
|
||||
ChunkTaskResponse::GeneratedMesh {
|
||||
position,
|
||||
vertices, indices,
|
||||
trans_vertices, trans_indices,
|
||||
} => {
|
||||
//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: {:?}", chunk.desired_state);
|
||||
return
|
||||
}
|
||||
|
||||
//apply the mesh
|
||||
//TODO: Skip if mesh is empty? (i.e. set to None)
|
||||
let mesh = ChunkMesh {
|
||||
vertex_buffer: VertexBuffer::immutable(&renderer.display, &vertices).unwrap(),
|
||||
index_buffer: IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &indices).unwrap(),
|
||||
trans_vertex_buffer: VertexBuffer::immutable(&renderer.display, &trans_vertices).unwrap(),
|
||||
trans_index_buffer: IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &trans_indices).unwrap(),
|
||||
};
|
||||
if let Some(index) = chunk.mesh_index {
|
||||
meshes.update(index, mesh).expect("Mesh update failed");
|
||||
} else {
|
||||
let mesh_index = meshes.insert(mesh);
|
||||
chunk.mesh_index = Some(mesh_index);
|
||||
}
|
||||
|
||||
//update chunk state
|
||||
chunk.current_state = CurrentChunkState::Rendered;
|
||||
|
||||
//increase ops counter
|
||||
ops += 1;
|
||||
}
|
||||
}
|
||||
if ops >= match *state {
|
||||
GameState::InGame => MAX_CHUNK_OPS_INGAME,
|
||||
_ => MAX_CHUNK_OPS,
|
||||
} { break }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use glam::{IVec3, ivec3};
|
||||
use strum::IntoEnumIterator;
|
||||
use kubi_shared::block::{Block, RenderType};
|
||||
use kubi_shared::block::{Block, BlockTexture, RenderType};
|
||||
use crate::world::chunk::CHUNK_SIZE;
|
||||
use crate::rendering::world::ChunkVertex;
|
||||
|
||||
|
@ -10,7 +10,10 @@ mod builder;
|
|||
use data::MeshGenData;
|
||||
use builder::{MeshBuilder, CubeFace, DiagonalFace};
|
||||
|
||||
pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
||||
pub fn generate_mesh(data: MeshGenData) -> (
|
||||
(Vec<ChunkVertex>, Vec<u32>),
|
||||
(Vec<ChunkVertex>, Vec<u32>),
|
||||
) {
|
||||
let get_block = |pos: IVec3| -> Block {
|
||||
if pos.x < 0 {
|
||||
data.block_data_neg_x[(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize]
|
||||
|
@ -30,6 +33,7 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|||
};
|
||||
|
||||
let mut builder = MeshBuilder::new();
|
||||
let mut trans_builder = MeshBuilder::new();
|
||||
|
||||
for x in 0..CHUNK_SIZE as i32 {
|
||||
for y in 0..CHUNK_SIZE as i32 {
|
||||
|
@ -39,7 +43,9 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|||
let descriptor = block.descriptor();
|
||||
match descriptor.render {
|
||||
RenderType::None => continue,
|
||||
RenderType::SolidBlock(textures) | RenderType::BinaryTransparency(textures) => {
|
||||
RenderType::SolidBlock(textures) |
|
||||
RenderType::BinaryTransparency(textures) |
|
||||
RenderType::TransBlock(textures) => {
|
||||
for face in CubeFace::iter() {
|
||||
let facing_direction = face.normal();
|
||||
let facing_coord = coord + facing_direction;
|
||||
|
@ -47,10 +53,12 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|||
let facing_descriptor = facing_block.descriptor();
|
||||
let face_obstructed = match descriptor.render {
|
||||
RenderType::SolidBlock(_) => matches!(facing_descriptor.render, RenderType::SolidBlock(_)),
|
||||
RenderType::BinaryTransparency(_) => {
|
||||
RenderType::BinaryTransparency(_) |
|
||||
RenderType::TransBlock(_) => {
|
||||
match facing_descriptor.render {
|
||||
RenderType::SolidBlock(_) => true,
|
||||
RenderType::BinaryTransparency(_) => block == facing_block,
|
||||
RenderType::BinaryTransparency(_) |
|
||||
RenderType::TransBlock(_) => block == facing_block,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
|
@ -65,7 +73,11 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|||
CubeFace::Back => textures.back,
|
||||
CubeFace::Bottom => textures.bottom,
|
||||
};
|
||||
builder.add_face(face, coord, face_texture as u8);
|
||||
let cur_builder = match descriptor.render {
|
||||
RenderType::TransBlock(_) => &mut trans_builder,
|
||||
_ => &mut builder,
|
||||
};
|
||||
cur_builder.add_face(face, coord, face_texture as u8);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -88,5 +100,5 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|||
}
|
||||
}
|
||||
|
||||
builder.finish()
|
||||
(builder.finish(), trans_builder.finish())
|
||||
}
|
||||
|
|
|
@ -1,71 +1,80 @@
|
|||
use flume::{Sender, Receiver};
|
||||
use glam::IVec3;
|
||||
use kubi_shared::queue::QueuedBlock;
|
||||
use shipyard::Unique;
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
use super::{
|
||||
chunk::BlockData,
|
||||
mesh::{generate_mesh, data::MeshGenData},
|
||||
worldgen::generate_world,
|
||||
};
|
||||
use crate::rendering::world::ChunkVertex;
|
||||
|
||||
pub enum ChunkTask {
|
||||
LoadChunk {
|
||||
seed: u64,
|
||||
position: IVec3
|
||||
},
|
||||
GenerateMesh {
|
||||
position: IVec3,
|
||||
data: MeshGenData
|
||||
}
|
||||
}
|
||||
pub enum ChunkTaskResponse {
|
||||
LoadedChunk {
|
||||
position: IVec3,
|
||||
chunk_data: BlockData,
|
||||
queued: Vec<QueuedBlock>
|
||||
},
|
||||
GeneratedMesh {
|
||||
position: IVec3,
|
||||
vertices: Vec<ChunkVertex>,
|
||||
indexes: Vec<u32>
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct ChunkTaskManager {
|
||||
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
|
||||
pool: ThreadPool,
|
||||
}
|
||||
impl ChunkTaskManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
channel: flume::unbounded::<ChunkTaskResponse>(), //maybe put a bound or even bound(0)?
|
||||
pool: ThreadPoolBuilder::new().num_threads(4).build().unwrap()
|
||||
}
|
||||
}
|
||||
pub fn add_sussy_response(&self, response: ChunkTaskResponse) {
|
||||
// this WILL get stuck if the channel is bounded
|
||||
// don't make the channel bounded ever
|
||||
self.channel.0.send(response).unwrap()
|
||||
}
|
||||
pub fn spawn_task(&self, task: ChunkTask) {
|
||||
let sender = self.channel.0.clone();
|
||||
self.pool.spawn(move || {
|
||||
let _ = sender.send(match task {
|
||||
ChunkTask::GenerateMesh { position, data } => {
|
||||
let (vertices, indexes) = generate_mesh(data);
|
||||
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes }
|
||||
},
|
||||
ChunkTask::LoadChunk { position, seed } => {
|
||||
let (chunk_data, queued) = generate_world(position, seed);
|
||||
ChunkTaskResponse::LoadedChunk { position, chunk_data, queued }
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
pub fn receive(&self) -> Option<ChunkTaskResponse> {
|
||||
self.channel.1.try_recv().ok()
|
||||
}
|
||||
}
|
||||
use flume::{Sender, Receiver};
|
||||
use glam::IVec3;
|
||||
use kubi_shared::queue::QueuedBlock;
|
||||
use shipyard::Unique;
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
use super::{
|
||||
chunk::BlockData,
|
||||
mesh::{generate_mesh, data::MeshGenData},
|
||||
worldgen::generate_world,
|
||||
};
|
||||
use crate::rendering::world::ChunkVertex;
|
||||
|
||||
pub enum ChunkTask {
|
||||
LoadChunk {
|
||||
seed: u64,
|
||||
position: IVec3
|
||||
},
|
||||
GenerateMesh {
|
||||
position: IVec3,
|
||||
data: MeshGenData
|
||||
}
|
||||
}
|
||||
pub enum ChunkTaskResponse {
|
||||
LoadedChunk {
|
||||
position: IVec3,
|
||||
chunk_data: BlockData,
|
||||
queued: Vec<QueuedBlock>
|
||||
},
|
||||
GeneratedMesh {
|
||||
position: IVec3,
|
||||
vertices: Vec<ChunkVertex>,
|
||||
indices: Vec<u32>,
|
||||
trans_vertices: Vec<ChunkVertex>,
|
||||
trans_indices: Vec<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct ChunkTaskManager {
|
||||
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
|
||||
pool: ThreadPool,
|
||||
}
|
||||
impl ChunkTaskManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
channel: flume::unbounded::<ChunkTaskResponse>(), //maybe put a bound or even bound(0)?
|
||||
pool: ThreadPoolBuilder::new().num_threads(4).build().unwrap()
|
||||
}
|
||||
}
|
||||
pub fn add_sussy_response(&self, response: ChunkTaskResponse) {
|
||||
// this WILL get stuck if the channel is bounded
|
||||
// don't make the channel bounded ever
|
||||
self.channel.0.send(response).unwrap()
|
||||
}
|
||||
pub fn spawn_task(&self, task: ChunkTask) {
|
||||
let sender = self.channel.0.clone();
|
||||
self.pool.spawn(move || {
|
||||
let _ = sender.send(match task {
|
||||
ChunkTask::GenerateMesh { position, data } => {
|
||||
let (
|
||||
(vertices, indices),
|
||||
(trans_vertices, trans_indices),
|
||||
) = generate_mesh(data);
|
||||
ChunkTaskResponse::GeneratedMesh {
|
||||
position,
|
||||
vertices, indices,
|
||||
trans_vertices, trans_indices,
|
||||
}
|
||||
},
|
||||
ChunkTask::LoadChunk { position, seed } => {
|
||||
let (chunk_data, queued) = generate_world(position, seed);
|
||||
ChunkTaskResponse::LoadedChunk { position, chunk_data, queued }
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
pub fn receive(&self) -> Option<ChunkTaskResponse> {
|
||||
self.channel.1.try_recv().ok()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue