refactor(client): abstract chunk mesh upload behind a MeshSink trait
This commit is contained in:
parent
2abe08bf57
commit
dae0bafcd3
|
|
@ -6,6 +6,8 @@ use std::collections::{HashMap, HashSet};
|
|||
use std::sync::Arc;
|
||||
|
||||
use renderer::meshing::Neighbors;
|
||||
use renderer::vertex::Vertex;
|
||||
use renderer::{MeshKey, RendererError};
|
||||
use shared::protocol::chunk::ChunkMessage;
|
||||
use shared::world::{CHUNK_SIZE, Chunk, ChunkPos};
|
||||
use tracing::{debug, error};
|
||||
|
|
@ -31,6 +33,43 @@ const NEIGHBOR_OFFSETS: [(i32, i32, i32); 6] = [
|
|||
(0, 0, -1),
|
||||
];
|
||||
|
||||
/// Sink that receives finished chunk meshes for upload.
|
||||
///
|
||||
/// The production sink is the Vulkan [`Renderer`](renderer::Renderer); the abstraction exists so the meshing pipeline can be exercised against a recording double in tests, which have no GPU. Method signatures mirror the renderer's exactly so the production `impl` is a direct forward.
|
||||
pub trait MeshSink {
|
||||
/// Uploads (or replaces) the mesh identified by `key`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`RendererError`] when the underlying implementation fails to allocate or write the GPU buffers for the mesh.
|
||||
fn insert_mesh(
|
||||
&mut self,
|
||||
key: MeshKey,
|
||||
vertices: &[Vertex],
|
||||
indices: &[u32],
|
||||
world_offset: [f32; 3],
|
||||
) -> Result<(), RendererError>;
|
||||
|
||||
/// Removes any mesh currently associated with `key`; a no-op when none exists.
|
||||
fn remove_mesh(&mut self, key: MeshKey);
|
||||
}
|
||||
|
||||
impl MeshSink for renderer::Renderer {
|
||||
fn insert_mesh(
|
||||
&mut self,
|
||||
key: MeshKey,
|
||||
vertices: &[Vertex],
|
||||
indices: &[u32],
|
||||
world_offset: [f32; 3],
|
||||
) -> Result<(), RendererError> {
|
||||
renderer::Renderer::insert_mesh(self, key, vertices, indices, world_offset)
|
||||
}
|
||||
|
||||
fn remove_mesh(&mut self, key: MeshKey) {
|
||||
renderer::Renderer::remove_mesh(self, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks which server-streamed chunks are resident and orchestrates neighbour-aware meshing.
|
||||
pub struct ChunkManager {
|
||||
/// Resident chunks keyed by position, retained so the mesher can sample voxels across chunk boundaries. Stored behind [`Arc`] so a future worker pool can hand a chunk to a thread without copying it.
|
||||
|
|
@ -60,11 +99,11 @@ impl ChunkManager {
|
|||
&mut self,
|
||||
center: ChunkPos,
|
||||
deliveries: &mut net::ChunkStream,
|
||||
renderer: &mut renderer::Renderer,
|
||||
sink: &mut impl MeshSink,
|
||||
) {
|
||||
let unloaded = self.unload_outside(center, renderer);
|
||||
let (loaded, dropped) = self.apply_deliveries(deliveries, renderer);
|
||||
let meshed = self.drain_remesh(renderer);
|
||||
let unloaded = self.unload_outside(center, sink);
|
||||
let (loaded, dropped) = self.apply_deliveries(deliveries, sink);
|
||||
let meshed = self.drain_remesh(sink);
|
||||
|
||||
if loaded > 0 || dropped > 0 || unloaded > 0 || meshed > 0 {
|
||||
debug!(
|
||||
|
|
@ -85,7 +124,7 @@ impl ChunkManager {
|
|||
fn apply_deliveries(
|
||||
&mut self,
|
||||
deliveries: &mut net::ChunkStream,
|
||||
renderer: &mut renderer::Renderer,
|
||||
sink: &mut impl MeshSink,
|
||||
) -> (usize, usize) {
|
||||
let mut loaded = Vec::new();
|
||||
let mut dropped = Vec::new();
|
||||
|
|
@ -99,7 +138,7 @@ impl ChunkManager {
|
|||
}
|
||||
Ok(ChunkMessage::Drop { pos }) => {
|
||||
if self.resident.remove(&pos).is_some() {
|
||||
renderer.remove_mesh((pos.x, pos.y, pos.z));
|
||||
sink.remove_mesh((pos.x, pos.y, pos.z));
|
||||
dropped.push(pos);
|
||||
}
|
||||
}
|
||||
|
|
@ -115,7 +154,7 @@ impl ChunkManager {
|
|||
/// Evicts every resident chunk outside the load radius around `center`, returning the number removed.
|
||||
///
|
||||
/// Each evicted chunk's resident neighbours have a boundary toward it that is now exposed, so they are enqueued for re-meshing.
|
||||
fn unload_outside(&mut self, center: ChunkPos, renderer: &mut renderer::Renderer) -> usize {
|
||||
fn unload_outside(&mut self, center: ChunkPos, sink: &mut impl MeshSink) -> usize {
|
||||
let desired = desired_chunks(center, LOAD_RADIUS);
|
||||
let stale: Vec<ChunkPos> = self
|
||||
.resident
|
||||
|
|
@ -124,7 +163,7 @@ impl ChunkManager {
|
|||
.copied()
|
||||
.collect();
|
||||
for pos in &stale {
|
||||
renderer.remove_mesh((pos.x, pos.y, pos.z));
|
||||
sink.remove_mesh((pos.x, pos.y, pos.z));
|
||||
self.resident.remove(pos);
|
||||
}
|
||||
self.queue_remesh(&[], &stale);
|
||||
|
|
@ -140,7 +179,7 @@ impl ChunkManager {
|
|||
/// Meshes and uploads up to [`MESHES_PER_UPDATE`] chunks from the pending set, returning the number processed.
|
||||
///
|
||||
/// Positions no longer resident (dropped after being enqueued) are discarded without meshing.
|
||||
fn drain_remesh(&mut self, renderer: &mut renderer::Renderer) -> usize {
|
||||
fn drain_remesh(&mut self, sink: &mut impl MeshSink) -> usize {
|
||||
// Take a bounded batch out of the set; the remainder stays queued for later frames.
|
||||
let batch: Vec<ChunkPos> = self
|
||||
.pending_remesh
|
||||
|
|
@ -153,17 +192,17 @@ impl ChunkManager {
|
|||
for pos in batch {
|
||||
self.pending_remesh.remove(&pos);
|
||||
if self.resident.contains_key(&pos) {
|
||||
self.mesh_and_upload(pos, renderer);
|
||||
self.mesh_and_upload(pos, sink);
|
||||
meshed += 1;
|
||||
}
|
||||
}
|
||||
meshed
|
||||
}
|
||||
|
||||
/// Meshes the resident chunk at `pos` against its resident neighbours and uploads the result to the renderer.
|
||||
/// Meshes the resident chunk at `pos` against its resident neighbours and uploads the result to the sink.
|
||||
///
|
||||
/// A chunk that meshes to no geometry (all air, or fully enclosed by solid neighbours) is removed from the renderer rather than uploaded, since a zero-length buffer is invalid; this also clears any mesh a previous state had left there.
|
||||
fn mesh_and_upload(&mut self, pos: ChunkPos, renderer: &mut renderer::Renderer) {
|
||||
/// A chunk that meshes to no geometry (all air, or fully enclosed by solid neighbours) is removed from the sink rather than uploaded, since a zero-length buffer is invalid; this also clears any mesh a previous state had left there.
|
||||
fn mesh_and_upload(&mut self, pos: ChunkPos, sink: &mut impl MeshSink) {
|
||||
let Some(chunk) = self.resident.get(&pos) else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -171,7 +210,7 @@ impl ChunkManager {
|
|||
let (vertices, indices) = renderer::meshing::generate_mesh(chunk, &neighbors);
|
||||
|
||||
if indices.is_empty() {
|
||||
renderer.remove_mesh((pos.x, pos.y, pos.z));
|
||||
sink.remove_mesh((pos.x, pos.y, pos.z));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -189,9 +228,7 @@ impl ChunkManager {
|
|||
]
|
||||
};
|
||||
|
||||
if let Err(e) =
|
||||
renderer.insert_mesh((pos.x, pos.y, pos.z), &vertices, &indices, world_offset)
|
||||
{
|
||||
if let Err(e) = sink.insert_mesh((pos.x, pos.y, pos.z), &vertices, &indices, world_offset) {
|
||||
error!(?pos, "failed to upload chunk mesh: {e}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue