Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7be2443
wip
CuddlyBunion341 Mar 13, 2025
5218b0b
Add perf data?
CuddlyBunion341 Mar 13, 2025
09905bf
Attempt improve performance (crashes instantly)
CuddlyBunion341 Mar 14, 2025
71b5354
Add async std
CuddlyBunion341 Mar 14, 2025
2191fc6
Revert "Add async std"
CuddlyBunion341 Mar 14, 2025
060f285
Revert "Attempt improve performance (crashes instantly)"
CuddlyBunion341 Mar 14, 2025
dfba6e8
Revert "Add perf data?"
CuddlyBunion341 Mar 14, 2025
c969a3c
Revert "wip"
CuddlyBunion341 Mar 14, 2025
088f1ee
wip
CuddlyBunion341 Mar 15, 2025
972004d
Implement async!
CuddlyBunion341 Mar 16, 2025
818290b
Refactor mesh generation, fix performance
CuddlyBunion341 Mar 16, 2025
36c81b8
Fix linter
CuddlyBunion341 Mar 16, 2025
6d5499a
Refactor remove unused code
CuddlyBunion341 Mar 16, 2025
6c40e67
Refactor
CuddlyBunion341 Mar 16, 2025
09af598
Refactor rename
CuddlyBunion341 Mar 16, 2025
ea89014
Use ChatGPT to DRYify mesher task with macro
CuddlyBunion341 Mar 16, 2025
83ddaa3
Refactor, fix linter
CuddlyBunion341 Mar 16, 2025
3280693
Refactor
CuddlyBunion341 Mar 16, 2025
8c87304
Improve dispose
CuddlyBunion341 Mar 16, 2025
5adc654
Update src/client/terrain/systems.rs
CuddlyBunion341 Mar 16, 2025
9541f0a
Update src/client/terrain/systems.rs
CuddlyBunion341 Mar 16, 2025
2940e1d
Join chunk meshing
CuddlyBunion341 Mar 17, 2025
68ab49e
Refactor?
CuddlyBunion341 Mar 17, 2025
90e272b
Refactor systems
CuddlyBunion341 Mar 18, 2025
f9e5cbd
Fix linter
CuddlyBunion341 Mar 18, 2025
7085d21
Refactor rename
CuddlyBunion341 Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/client/terrain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ impl Plugin for TerrainPlugin {
app.insert_resource(ChunkManager::new());
app.insert_resource(util::TextureManager::new());
app.insert_resource(resources::RenderMaterials::new());
app.insert_resource(resources::MesherTasks::new());
app.add_event::<terrain_events::BlockUpdateEvent>();
app.add_event::<terrain_events::ChunkMeshUpdateEvent>();
app.add_event::<terrain_events::WorldRegenerateEvent>();
Expand All @@ -36,6 +37,7 @@ impl Plugin for TerrainPlugin {
Update,
terrain_systems::handle_terrain_regeneration_events_system,
);
app.add_systems(Update, terrain_systems::handle_chunk_tasks_system);
}
}
}
33 changes: 33 additions & 0 deletions src/client/terrain/resources.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bevy::tasks::Task;

use crate::prelude::*;

#[derive(Resource)]
Expand All @@ -9,6 +11,37 @@ impl SpawnAreaLoaded {
}
}

pub enum MeshType {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder:

A logical Chunk of terrain has two geometrical representations:
A solid one, for the cubes like stone and dirt and a transparent one for the grass at the moment, perhaps glass and other stuff in the future.

Solid,
Transparent,
}

pub struct MeshTask(pub Task<Option<Mesh>>);
pub struct FutureChunkMesh {
pub position: Vec3,
pub mesh_task: MeshTask,
pub mesh_type: MeshType,
}

#[derive(Resource)]
pub struct MesherTasks {
pub task_list: Vec<FutureChunkMesh>,
}

impl Default for MesherTasks {
fn default() -> Self {
Self::new()
}
}

impl MesherTasks {
pub fn new() -> Self {
MesherTasks {
task_list: Vec::new(),
}
}
}

#[derive(Resource)]
pub struct RenderMaterials {
pub transparent_material: Option<Handle<StandardMaterial>>,
Expand Down
172 changes: 93 additions & 79 deletions src/client/terrain/systems.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use terrain_resources::RenderMaterials;
use terrain_util::create_cross_mesh_for_chunk;
use bevy::tasks::{futures_lite::future, AsyncComputeTaskPool};
use terrain_resources::{FutureChunkMesh, MeshTask, MeshType, MesherTasks, RenderMaterials};

use crate::prelude::*;

Expand Down Expand Up @@ -46,7 +46,7 @@ pub fn generate_world_system(
mut client: ResMut<RenetClient>,
mut chunk_manager: ResMut<ChunkManager>,
) {
let render_distance = Vec3::new(4.0, 4.0, 4.0);
let render_distance = Vec3::new(8.0, 4.0, 8.0);

info!("Sending chunk requests for chunks");

Expand All @@ -71,12 +71,11 @@ pub fn generate_world_system(

pub fn handle_chunk_mesh_update_events_system(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
chunk_manager: ResMut<ChunkManager>,
mut chunk_mesh_update_events: EventReader<terrain_events::ChunkMeshUpdateEvent>,
mut mesh_query: Query<(Entity, &terrain_components::ChunkMesh)>,
texture_manager: ResMut<terrain_util::TextureManager>,
materials: Res<RenderMaterials>,
mut tasks: ResMut<MesherTasks>,
) {
for event in chunk_mesh_update_events.read() {
info!(
Expand All @@ -91,20 +90,16 @@ pub fn handle_chunk_mesh_update_events_system(
commands.entity(entity).despawn();
}
}
add_chunk_objects(
&mut commands,
&mut meshes,
chunk,
&texture_manager,
&materials,
);
add_cross_objects(
&mut commands,
chunk,
&materials,
&texture_manager,
&mut meshes,
);
tasks.task_list.push(FutureChunkMesh {
position: chunk.position,
mesh_task: create_cube_mesher_task(chunk, &texture_manager),
mesh_type: terrain_resources::MeshType::Solid,
});
tasks.task_list.push(FutureChunkMesh {
position: chunk.position,
mesh_task: create_cross_mesher_task(chunk, &texture_manager),
mesh_type: terrain_resources::MeshType::Transparent,
});
}
None => {
println!("No chunk found");
Expand All @@ -113,73 +108,92 @@ pub fn handle_chunk_mesh_update_events_system(
}
}

fn add_chunk_objects(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
fn create_cube_mesher_task(
chunk: &Chunk,
texture_manager: &terrain_util::TextureManager,
materials: &RenderMaterials,
) {
if let Some(mesh) = terrain_util::create_chunk_mesh(chunk, texture_manager) {
let material = materials
.chunk_material
.clone()
.expect("Chunk material is loaded");

let meshes: &mut Mut<Assets<Mesh>> = &mut ResMut::reborrow(meshes);
commands.spawn((
Mesh3d(meshes.add(mesh)),
Transform::from_xyz(
chunk.position.x * CHUNK_SIZE as f32,
chunk.position.y * CHUNK_SIZE as f32,
chunk.position.z * CHUNK_SIZE as f32,
),
MeshMaterial3d(material),
player_components::Raycastable,
terrain_components::ChunkMesh {
key: [
chunk.position.x as i32,
chunk.position.y as i32,
chunk.position.z as i32,
],
},
Name::from("Transparent Chunk Mesh"),
));
}
) -> MeshTask {
let task_pool = AsyncComputeTaskPool::get();

let chunk = *chunk;
let texture_manager = texture_manager.clone();

let task =
task_pool.spawn(async move { terrain_util::create_chunk_mesh(&chunk, &texture_manager) });

MeshTask(task)
}

fn add_cross_objects(
commands: &mut Commands,
fn create_cross_mesher_task(
chunk: &Chunk,
materials: &RenderMaterials,
texture_manager: &terrain_util::TextureManager,
meshes: &mut ResMut<Assets<Mesh>>,
) -> MeshTask {
let task_pool = AsyncComputeTaskPool::get();

let chunk = *chunk;
let texture_manager = texture_manager.clone();

let task = task_pool
.spawn(async move { terrain_util::create_cross_mesh_for_chunk(&chunk, &texture_manager) });

MeshTask(task)
}

pub fn handle_chunk_tasks_system(
mut commands: Commands,
materials: Res<RenderMaterials>,
mut tasks: ResMut<MesherTasks>,
mut meshes: ResMut<Assets<Mesh>>,
) {
if let Some(mesh) = create_cross_mesh_for_chunk(chunk, texture_manager) {
let mesh_handle = meshes.add(mesh);

commands.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(
materials
.transparent_material
.clone()
.expect("Transparent material exists"),
),
Transform::from_xyz(
chunk.position.x * CHUNK_SIZE as f32,
chunk.position.y * CHUNK_SIZE as f32,
chunk.position.z * CHUNK_SIZE as f32,
),
terrain_components::ChunkMesh {
key: [
chunk.position.x as i32,
chunk.position.y as i32,
chunk.position.z as i32,
],
},
));
}
let mut next_poll_indicies: Vec<usize> = Vec::new();
tasks
.task_list
.iter_mut()
.enumerate()
.for_each(|(index, future_chunk)| {
let chunk_position = future_chunk.position;
let task_result =
bevy::tasks::block_on(future::poll_once(&mut future_chunk.mesh_task.0));
if task_result.is_none() {
// Check next poll
next_poll_indicies.push(index);
return;
}
let mesh_option = task_result.unwrap();
if mesh_option.is_none() {
return;
Copy link
Owner Author

@CuddlyBunion341 CuddlyBunion341 Mar 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info: If chunk is empty, a None Mesh will be created.
In that case the world should not be updated.

}
let mesh = mesh_option.expect("Mesh exists");
let mesh_handle = meshes.add(mesh);

let material_handle = match future_chunk.mesh_type {
MeshType::Solid => materials.chunk_material.clone(),
MeshType::Transparent => materials.transparent_material.clone(),
};

commands.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(material_handle.expect("Material exists")),
Transform::from_xyz(
chunk_position.x * CHUNK_SIZE as f32,
chunk_position.y * CHUNK_SIZE as f32,
chunk_position.z * CHUNK_SIZE as f32,
),
terrain_components::ChunkMesh {
key: [
chunk_position.x as i32,
chunk_position.y as i32,
chunk_position.z as i32,
],
},
));
});

let mut index = 0;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugly index, can't use enumerate() in this context.

tasks.task_list.retain(|_| {
let contains = next_poll_indicies.contains(&index);
index += 1;
contains
})
}

fn create_transparent_material(texture_handle: Handle<Image>) -> StandardMaterial {
Expand Down
2 changes: 1 addition & 1 deletion src/client/terrain/util/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub mod client_block {
use client_block::block_properties;
use TextureName::*;

#[derive(Resource)]
#[derive(Resource, Clone)]
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea how to circumvent this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clone shouldn't be too expensive though, as we are only cloning a hashmap with a few primitives.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The atlas currently consists of 16 slots, so there are not many UVs and enum values that will be cloned.

pub struct TextureManager {
textures: HashMap<TextureName, TextureUV>,
}
Expand Down