mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Merge pull request #11645 from vegetableovosh/fix/11214-chunk-refresh
Fix/11214 chunk refresh
This commit is contained in:
4 files changed
+108
-42
No files matched your search
@@ -1126,18 +1126,29 @@ impl Streaming {
|
||||
pub fn lod_source(&self) -> Arc<dyn LodSource> {
|
||||
self.saves.clone()
|
||||
}
|
||||
/// Resource packs reloaded (`LevelRenderer.allChanged`): mesh with the new
|
||||
/// Resource packs reloaded (`LevelExtractor.allChanged`): mesh with the new
|
||||
/// models and sprites, and rebuild every loaded section. Meshes in flight
|
||||
/// were made with the old resources; their results are dropped.
|
||||
pub fn reload_resources(&mut self, world: &mut World, context: Arc<WorkerContext>) {
|
||||
*self.shared.context.write().unwrap() = context;
|
||||
self.reload_sections(world);
|
||||
}
|
||||
/// `LevelExtractor.allChanged`: discard mesh work made before the reload and
|
||||
/// rebuild every loaded, lit section.
|
||||
pub fn reload_sections(&mut self, world: &mut World) {
|
||||
self.meshing.clear();
|
||||
self.compile_started.clear();
|
||||
Self::mark_loaded_sections_dirty(world, &self.lit);
|
||||
}
|
||||
/// The resident sections `LevelExtractor.allChanged` queues again. An
|
||||
/// unlit column will queue itself when its light job completes.
|
||||
fn mark_loaded_sections_dirty(world: &mut World, lit: &FxHashSet<(i32, i32)>) {
|
||||
for &(x, z) in world.chunks.keys() {
|
||||
if self.lit.contains(&(x, z)) {
|
||||
for sy in 0..world.layout().sections() as i32 {
|
||||
world.dirty_sections.insert((x, sy, z));
|
||||
}
|
||||
if !lit.contains(&(x, z)) {
|
||||
continue;
|
||||
}
|
||||
for sy in 0..world.layout().sections() as i32 {
|
||||
world.dirty_sections.insert((x, sy, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4462,3 +4473,38 @@ mod cover_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reload_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reload_marks_every_section_of_loaded_lit_columns() {
|
||||
let mut world = World::new(horizon_world::registry::BlockStateId(0), 0);
|
||||
world.chunks.insert((2, -3), Arc::new(Chunk::empty(2, -3)));
|
||||
world.chunks.insert((4, 5), Arc::new(Chunk::empty(4, 5)));
|
||||
let lit = FxHashSet::from_iter([(2, -3), (99, 99)]);
|
||||
|
||||
Streaming::mark_loaded_sections_dirty(&mut world, &lit);
|
||||
|
||||
let sections = world.layout().sections() as i32;
|
||||
assert_eq!(world.dirty_sections.len(), sections as usize);
|
||||
for sy in 0..sections {
|
||||
assert!(world.dirty_sections.contains(&(2, sy, -3)));
|
||||
assert!(!world.dirty_sections.contains(&(4, sy, 5)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_keeps_existing_work_and_ignores_unloaded_lit_columns() {
|
||||
let mut world = World::new(horizon_world::registry::BlockStateId(0), 0);
|
||||
world.chunks.insert((4, 5), Arc::new(Chunk::empty(4, 5)));
|
||||
world.dirty_sections.insert((4, 3, 5));
|
||||
let lit = FxHashSet::from_iter([(99, 99)]);
|
||||
|
||||
Streaming::mark_loaded_sections_dirty(&mut world, &lit);
|
||||
|
||||
assert_eq!(world.dirty_sections.len(), 1);
|
||||
assert!(world.dirty_sections.contains(&(4, 3, 5)));
|
||||
}
|
||||
}
|
||||
@@ -15481,27 +15481,30 @@ impl Game<'_> {
|
||||
// LocalPlayer.refreshChatAbilities: the chat shows what the Chat option allows.
|
||||
let abilities = self.chat_abilities();
|
||||
self.ui.chat.set_abilities(&self.gui.atlas.font, abilities);
|
||||
let render_distance_changed = self.streaming.radius != self.settings.render_distance;
|
||||
self.streaming.radius = self.settings.render_distance;
|
||||
// Options.biomeBlendRadius, ambientOcclusion, cutoutLeaves and the
|
||||
// leaf-culling mode: LevelExtractor.allChanged when they change.
|
||||
// Options.renderDistance, biomeBlendRadius, ambientOcclusion,
|
||||
// cutoutLeaves and the leaf-culling mode: LevelExtractor.allChanged
|
||||
// when they change.
|
||||
let leaf_culling = self.effective_leaf_culling();
|
||||
let (s, st) = (&self.settings, &self.streaming);
|
||||
let remesh = (
|
||||
s.biome_blend,
|
||||
s.smooth_lighting,
|
||||
s.cutout_leaves,
|
||||
leaf_culling,
|
||||
) != (
|
||||
st.biome_blend,
|
||||
st.smooth_lighting,
|
||||
st.cutout_leaves,
|
||||
st.leaf_culling,
|
||||
);
|
||||
let reload = render_distance_changed
|
||||
|| (
|
||||
s.biome_blend,
|
||||
s.smooth_lighting,
|
||||
s.cutout_leaves,
|
||||
leaf_culling,
|
||||
) != (
|
||||
st.biome_blend,
|
||||
st.smooth_lighting,
|
||||
st.cutout_leaves,
|
||||
st.leaf_culling,
|
||||
);
|
||||
self.streaming.biome_blend = self.settings.biome_blend;
|
||||
self.streaming.smooth_lighting = self.settings.smooth_lighting;
|
||||
self.streaming.cutout_leaves = self.settings.cutout_leaves;
|
||||
self.streaming.leaf_culling = leaf_culling;
|
||||
if remesh {
|
||||
if reload {
|
||||
self.all_sections_changed();
|
||||
}
|
||||
self.audio.master = self.settings.master_volume;
|
||||
@@ -15522,13 +15525,15 @@ impl Game<'_> {
|
||||
self.all_sections_changed();
|
||||
}
|
||||
}
|
||||
/// `LevelExtractor.allChanged`: every loaded section is rebuilt.
|
||||
/// `LevelExtractor.allChanged`: displayed sections and mesh work are
|
||||
/// discarded, then every loaded section is rebuilt.
|
||||
fn all_sections_changed(&mut self) {
|
||||
for &(x, z) in self.world.chunks.keys() {
|
||||
for sy in 0..self.world.layout().height() / 16 {
|
||||
self.world.dirty_sections.insert((x, sy, z));
|
||||
}
|
||||
self.clear_lod();
|
||||
self.pending_uploads.clear();
|
||||
if let Some(renderer) = self.renderer.as_mut() {
|
||||
renderer.clear_sections(self.world.layout());
|
||||
}
|
||||
self.streaming.reload_sections(&mut self.world);
|
||||
}
|
||||
/// Whether precipitation at a column is snow (Biome.coldEnoughToSnow:
|
||||
/// height adjusted temperature) or none (no precipitation).
|
||||
|
||||
@@ -73,14 +73,17 @@ fn columns_loaded_past_the_render_distance_mesh_when_they_come_in_range() {
|
||||
streaming.radius = 4;
|
||||
let mut world = World::new(BlockStateId(0), 0);
|
||||
let mut meshed = HashSet::new();
|
||||
let mut settle_at = |streaming: &mut Streaming, center: (i32, i32)| {
|
||||
let settle_at = |streaming: &mut Streaming,
|
||||
world: &mut World,
|
||||
center: (i32, i32),
|
||||
meshed: &mut HashSet<(i32, i32, i32)>| {
|
||||
streaming.center = center;
|
||||
let deadline = Instant::now() + Duration::from_secs(60);
|
||||
let mut quiet = 0;
|
||||
while quiet < 40 {
|
||||
assert!(Instant::now() < deadline, "streaming never settled");
|
||||
let before = (world.chunks.len(), meshed.len());
|
||||
let (uploads, _) = streaming.update(&mut world);
|
||||
let (uploads, _) = streaming.update(world);
|
||||
meshed.extend(uploads.iter().map(|(key, _)| *key));
|
||||
// As the game does once they are uploaded: a worker frees them,
|
||||
// and nothing comes back (`pending` empties).
|
||||
@@ -92,25 +95,35 @@ fn columns_loaded_past_the_render_distance_mesh_when_they_come_in_range() {
|
||||
};
|
||||
// Columns up to three chunks past the render distance load around the
|
||||
// first position; the second brings some of them within it.
|
||||
settle_at(&mut streaming, (0, 0));
|
||||
settle_at(&mut streaming, (3, 0));
|
||||
settle_at(&mut streaming, &mut world, (0, 0), &mut meshed);
|
||||
settle_at(&mut streaming, &mut world, (3, 0), &mut meshed);
|
||||
let (cx, cz): (i32, i32) = (3, 0);
|
||||
let mut checked = 0;
|
||||
for x in cx - 4..=cx + 4 {
|
||||
for z in cz - 4..=cz + 4 {
|
||||
if (x - cx).pow(2) + (z - cz).pow(2) > 16 {
|
||||
continue;
|
||||
let all_in_range = |meshed: &HashSet<(i32, i32, i32)>| {
|
||||
let mut checked = 0;
|
||||
for x in cx - 4..=cx + 4 {
|
||||
for z in cz - 4..=cz + 4 {
|
||||
if (x - cx).pow(2) + (z - cz).pow(2) > 16 {
|
||||
continue;
|
||||
}
|
||||
for sy in 0..HEIGHT / 16 {
|
||||
assert!(
|
||||
meshed.contains(&(x, sy, z)),
|
||||
"section {x} {sy} {z} is in range but was never meshed"
|
||||
);
|
||||
}
|
||||
checked += 1;
|
||||
}
|
||||
for sy in 0..HEIGHT / 16 {
|
||||
assert!(
|
||||
meshed.contains(&(x, sy, z)),
|
||||
"section {x} {sy} {z} is in range but was never meshed"
|
||||
);
|
||||
}
|
||||
checked += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(checked, 49);
|
||||
checked
|
||||
};
|
||||
assert_eq!(all_in_range(&meshed), 49);
|
||||
|
||||
// `LevelExtractor.allChanged` (F3+A and a changed render distance) drops
|
||||
// every displayed section. Streaming must produce all of them again.
|
||||
meshed.clear();
|
||||
streaming.reload_sections(&mut world);
|
||||
settle_at(&mut streaming, &mut world, (3, 0), &mut meshed);
|
||||
assert_eq!(all_in_range(&meshed), 49);
|
||||
}
|
||||
|
||||
/// A one-block cave under the sky: a stone floor at y 62 with a glowstone,
|
||||
|
||||
@@ -87,6 +87,8 @@ impl InterpolatedSampler {
|
||||
/// increment once per Y. The X lerps at a level of corners are the top of one
|
||||
/// cell and the bottom of the next (the same lerps of the same samples), so they
|
||||
/// are computed once.
|
||||
// The replacements Clippy suggests require Rust 1.87 and 1.88, above our 1.85 MSRV.
|
||||
#[allow(clippy::manual_is_multiple_of, clippy::chunks_exact_to_as_chunks)]
|
||||
fn fill_whole_cells(
|
||||
&self,
|
||||
output: &mut [f32],
|
||||
|
||||
Reference in new issue
Block a user