Compare commits

...
Author SHA1 Message Date
Ludovic MarieandClaude Opus 5.5 47ee870dfd LAN: keep each column's non-air sections for the light masks
Which light layers a chunk packet carries depends on the non-air
sections of the column and its eight neighbours. Each chunk sent looked
up all nine columns' sections in the section cache (216 hash lookups);
now each column's bits are kept with the sections they came from and
reused while the column holds the same sections.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-17 17:50:38 +02:00
+53 -13
View File
@@ -97,6 +97,8 @@ pub struct ChunkEncoder {
cache: std::sync::Mutex<FxHashMap<usize, Encoded>>,
/// Each column's heightmaps, with the sections they were made from.
heights: std::sync::Mutex<HashMap<(i32, i32), Heights>>,
/// Each column's non-air sections, with the sections they were found in.
occupied: std::sync::Mutex<FxHashMap<(i32, i32), Occupied>>,
/// The level's sections among a column's: all of them in the Overworld,
/// y 0 to 255 in the Nether and the End (`DimensionType` min_y and height).
sections: std::ops::Range<usize>,
@@ -104,6 +106,24 @@ pub struct ChunkEncoder {
sky: bool,
}
/// A column's non-air sections as bits (bit i for the level's section i),
/// valid while it holds the same sections: what nine columns' worth of
/// section lookups would give, for each column sent.
struct Occupied {
sections: Vec<std::sync::Weak<Section>>,
bits: u64,
}
/// Whether a column still holds the sections `weak` was made from (the
/// `Weak`s keep their addresses from being reused).
fn same_sections(weak: &[std::sync::Weak<Section>], sections: &[Arc<Section>]) -> bool {
weak.len() == sections.len()
&& weak
.iter()
.zip(sections)
.all(|(w, s)| std::ptr::eq(w.as_ptr(), Arc::as_ptr(s)))
}
/// A column's client heightmaps, valid while it holds the same sections.
struct Heights {
sections: Vec<std::sync::Weak<Section>>,
@@ -226,6 +246,7 @@ impl ChunkEncoder {
tables: None,
cache: Default::default(),
heights: Default::default(),
occupied: Default::default(),
sections: 0..SECTIONS,
sky: true,
}
@@ -234,16 +255,9 @@ impl ChunkEncoder {
/// The client heightmaps (Heightmap.prime over the column), from the
/// cache while the column's sections are unchanged.
fn heightmaps(&self, chunk: &Chunk, tables: &crate::save::SaveTables) -> ColumnHeightmaps {
let same = |h: &Heights| {
h.sections.len() == chunk.sections.len()
&& h.sections
.iter()
.zip(&chunk.sections)
.all(|(w, s)| std::ptr::eq(w.as_ptr(), Arc::as_ptr(s)))
};
let mut heights = self.heights.lock().unwrap();
if let Some(h) = heights.get(&(chunk.x, chunk.z))
&& same(h)
&& same_sections(&h.sections, &chunk.sections)
{
return h.maps.clone();
}
@@ -330,8 +344,9 @@ impl ChunkEncoder {
let n = usize::try_from(height >> 4).unwrap_or(SECTIONS);
self.sections = lo..(lo + n).min(SECTIONS);
self.sky = sky;
// The heightmaps depend on the level's height.
// The heightmaps and the section bits depend on the level's height.
self.heights.get_mut().unwrap().clear();
self.occupied.get_mut().unwrap().clear();
}
pub fn entity_type(&self, state: u16) -> Option<i32> {
self.entity_types
@@ -401,27 +416,52 @@ impl ChunkEncoder {
)
}
/// The chunk column with its light and block entities.
/// The non-air sections of a column as bits (LevelChunkSection.hasOnlyAir).
#[cfg(test)]
fn occupied(&self, chunk: &Chunk) -> u64 {
self.occupied_in(&mut self.occupied.lock().unwrap(), chunk)
}
/// The column's bits from `columns` while it holds the same sections,
/// or from its sections' entries.
fn occupied_in(&self, columns: &mut FxHashMap<(i32, i32), Occupied>, chunk: &Chunk) -> u64 {
let sections = &chunk.sections[self.sections.clone()];
if let Some(o) = columns.get(&(chunk.x, chunk.z))
&& same_sections(&o.sections, sections)
{
return o.bits;
}
if columns.len() >= CACHE_SWEEP {
columns.retain(|_, o| o.sections.iter().all(|w| w.strong_count() > 0));
if columns.len() >= CACHE_SWEEP / 2 {
columns.clear();
}
}
let mut bits = 0u64;
// One lock for the column: body_in asks for nine columns' sections.
let mut cache = self.cache.lock().unwrap();
for (i, s) in chunk.sections[self.sections.clone()].iter().enumerate() {
for (i, s) in sections.iter().enumerate() {
if self.entry(&mut cache, s).non_air {
bits |= 1 << i;
}
}
columns.insert(
(chunk.x, chunk.z),
Occupied {
sections: sections.iter().map(Arc::downgrade).collect(),
bits,
},
);
bits
}
/// The non-air sections of the column and its neighbours, as bits.
fn around(&self, chunk: &Chunk, world: &crate::world::World) -> u64 {
let mut columns = self.occupied.lock().unwrap();
let mut around = 0u64;
for dz in -1..=1 {
for dx in -1..=1 {
if let Some(c) = world.chunks.get(&(chunk.x + dx, chunk.z + dz)) {
around |= self.occupied(c);
around |= self.occupied_in(&mut columns, c);
}
}
}