chore: refactor chunk reading/writing

This commit is contained in:
Alexander Medvedev 2026-02-05 01:54:34 +01:00
parent 5165d16c29
commit 3c87523112
13 changed files with 290 additions and 285 deletions

View File

@ -26,7 +26,7 @@ impl PacketWrite for CLevelChunk<'_> {
VarInt(self.chunk.z).write(writer)?; VarInt(self.chunk.z).write(writer)?;
VarInt(self.dimension).write(writer)?; VarInt(self.dimension).write(writer)?;
let sub_chunk_count = self.chunk.section.sections.len() as u32; let sub_chunk_count = self.chunk.section.sections.lock().unwrap().len() as u32;
assert_eq!(sub_chunk_count, 24); assert_eq!(sub_chunk_count, 24);
VarUInt(sub_chunk_count).write(writer)?; VarUInt(sub_chunk_count).write(writer)?;
self.cache_enabled.write(writer)?; self.cache_enabled.write(writer)?;
@ -37,7 +37,15 @@ impl PacketWrite for CLevelChunk<'_> {
let min_y = (self.chunk.section.min_y >> 4) as i8; let min_y = (self.chunk.section.min_y >> 4) as i8;
// Blocks // Blocks
for (i, sub_chunk) in self.chunk.section.sections.iter().enumerate() { for (i, sub_chunk) in self
.chunk
.section
.sections
.lock()
.unwrap()
.iter()
.enumerate()
{
// Version 9 // Version 9
// [version:byte][num_storages:byte][sub_chunk_index:byte][block storage1]...[blockStorageN] // [version:byte][num_storages:byte][sub_chunk_index:byte][block storage1]...[blockStorageN]
let y = i as i8 + min_y; let y = i as i8 + min_y;

View File

@ -32,7 +32,7 @@ impl ClientPacket for CChunkData<'_> {
// Chunk Z // Chunk Z
write.write_i32_be(self.0.z)?; write.write_i32_be(self.0.z)?;
let heightmaps = &self.0.heightmap; let heightmaps = &self.0.heightmap.lock().unwrap();
write.write_var_int(&VarInt(3))?; // Map size write.write_var_int(&VarInt(3))?; // Map size
let mut write_heightmap = |index: i32, data: &[i64]| -> Result<(), WritingError> { let mut write_heightmap = |index: i32, data: &[i64]| -> Result<(), WritingError> {
@ -50,7 +50,7 @@ impl ClientPacket for CChunkData<'_> {
{ {
let mut blocks_and_biomes_buf = Vec::new(); let mut blocks_and_biomes_buf = Vec::new();
for section in &self.0.section.sections { for section in self.0.section.sections.lock().unwrap().iter() {
// Block count // Block count
let non_empty_block_count = section.block_states.non_air_block_count() as i16; let non_empty_block_count = section.block_states.non_air_block_count() as i16;
blocks_and_biomes_buf.write_i16_be(non_empty_block_count)?; blocks_and_biomes_buf.write_i16_be(non_empty_block_count)?;
@ -119,8 +119,9 @@ impl ClientPacket for CChunkData<'_> {
write.write_slice(&blocks_and_biomes_buf)?; write.write_slice(&blocks_and_biomes_buf)?;
}; };
write.write_var_int(&VarInt(self.0.block_entities.len() as i32))?; let block_entities = self.0.block_entities.lock().unwrap();
for block_entity in self.0.block_entities.values() { write.write_var_int(&VarInt(block_entities.len() as i32))?;
for block_entity in block_entities.values() {
let pos = block_entity.get_position(); let pos = block_entity.get_position();
let local_xz = ((get_local_cord(pos.0.x) & 0xF) << 4) | (get_local_cord(pos.0.z) & 0xF); let local_xz = ((get_local_cord(pos.0.x) & 0xF) << 4) | (get_local_cord(pos.0.z) & 0xF);

View File

@ -1,10 +1,16 @@
use std::{io::Cursor, path::PathBuf, pin::Pin}; use std::{
io::Cursor,
path::PathBuf,
pin::Pin,
sync::atomic::{AtomicBool, Ordering},
};
use bytes::Bytes; use bytes::Bytes;
use futures::future::join_all; use futures::future::join_all;
use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid}; use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid};
use pumpkin_nbt::{compound::NbtCompound, from_bytes, nbt_long_array}; use pumpkin_nbt::{compound::NbtCompound, from_bytes, nbt_long_array};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tokio::sync::Mutex;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
@ -58,13 +64,13 @@ impl PathFromLevelFolder for ChunkData {
impl Dirtiable for ChunkData { impl Dirtiable for ChunkData {
#[inline] #[inline]
fn mark_dirty(&mut self, flag: bool) { fn mark_dirty(&self, flag: bool) {
self.dirty = flag; self.dirty.store(flag, Ordering::Relaxed);
} }
#[inline] #[inline]
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty self.dirty.load(Ordering::Relaxed)
} }
} }
@ -159,15 +165,16 @@ impl ChunkData {
sky_light: sky_lights.into_boxed_slice(), sky_light: sky_lights.into_boxed_slice(),
}; };
let min_y = section_coords::section_to_block(chunk_data.min_y_section); let min_y = section_coords::section_to_block(chunk_data.min_y_section);
let section = ChunkSections::new(sub_chunks.into_boxed_slice(), min_y); let section =
ChunkSections::new(std::sync::Mutex::new(sub_chunks.into_boxed_slice()), min_y);
Ok(Self { Ok(Self {
section, section,
heightmap: chunk_data.heightmaps, heightmap: std::sync::Mutex::new(chunk_data.heightmaps),
x: position.x, x: position.x,
z: position.y, z: position.y,
// This chunk is read from disk, so it has not been modified // This chunk is read from disk, so it has not been modified
dirty: false, dirty: AtomicBool::new(false),
block_ticks: ChunkTickScheduler::from_iter(chunk_data.block_ticks), block_ticks: ChunkTickScheduler::from_iter(chunk_data.block_ticks),
fluid_ticks: ChunkTickScheduler::from_iter(chunk_data.fluid_ticks), fluid_ticks: ChunkTickScheduler::from_iter(chunk_data.fluid_ticks),
block_entities: { block_entities: {
@ -178,7 +185,7 @@ impl ChunkData {
block_entities.insert(block_entity.get_position(), block_entity); block_entities.insert(block_entity.get_position(), block_entity);
} }
} }
block_entities std::sync::Mutex::new(block_entities)
}, },
light_engine, light_engine,
status: chunk_data.status, status: chunk_data.status,
@ -186,9 +193,12 @@ impl ChunkData {
} }
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> { async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
let sections: Vec<_> = self // 1. Process sections (Standard Mutex)
.section // We do this in a block or just let the guard drop naturally
.sections // before the first .await
let sections: Vec<_> = {
let sections_guard = self.section.sections.lock().unwrap();
sections_guard
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, section)| ChunkSectionNBT { .map(|(i, section)| ChunkSectionNBT {
@ -204,7 +214,29 @@ impl ChunkData {
LightContainer::Full(data) => Some(data), LightContainer::Full(data) => Some(data),
}, },
}) })
.collect(); .collect()
};
// 2. Clone heightmaps (Standard Mutex)
let heightmaps = self.heightmap.lock().unwrap().clone();
// 3. Prepare block entities (CRITICAL: Must drop lock before .await)
let entities_to_serialize = {
// We clone the Arcs out of the map while holding the lock
let entities_guard = self.block_entities.lock().unwrap();
entities_guard.values().cloned().collect::<Vec<_>>()
};
// entities_guard drops here automatically!
// 4. Now we can safely .await because no MutexGuards are held
let block_entities_nbt = join_all(entities_to_serialize.into_iter().map(
|block_entity| async move {
let mut nbt = NbtCompound::new();
block_entity.write_internal(&mut nbt).await;
nbt
},
))
.await;
let nbt = ChunkNbt { let nbt = ChunkNbt {
data_version: WORLD_DATA_VERSION, data_version: WORLD_DATA_VERSION,
@ -212,17 +244,11 @@ impl ChunkData {
z_pos: self.z, z_pos: self.z,
min_y_section: section_coords::block_to_section(self.section.min_y), min_y_section: section_coords::block_to_section(self.section.min_y),
status: self.status, status: self.status,
heightmaps: self.heightmap.clone(), heightmaps,
sections, sections,
block_ticks: self.block_ticks.to_vec(), block_ticks: self.block_ticks.to_vec(),
fluid_ticks: self.fluid_ticks.to_vec(), fluid_ticks: self.fluid_ticks.to_vec(),
block_entities: join_all(self.block_entities.values().map(|block_entity| async move { block_entities: block_entities_nbt,
let mut nbt = NbtCompound::new();
block_entity.write_internal(&mut nbt).await;
nbt
}))
.await,
// we have not implemented light engine
light_correct: false, light_correct: false,
}; };
@ -242,13 +268,13 @@ impl PathFromLevelFolder for ChunkEntityData {
impl Dirtiable for ChunkEntityData { impl Dirtiable for ChunkEntityData {
#[inline] #[inline]
fn mark_dirty(&mut self, flag: bool) { fn mark_dirty(&self, flag: bool) {
self.dirty = flag; self.dirty.store(flag, Ordering::Relaxed);
} }
#[inline] #[inline]
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty self.dirty.load(Ordering::Relaxed)
} }
} }
@ -262,7 +288,7 @@ impl SingleChunkDataSerializer for ChunkEntityData {
fn to_bytes( fn to_bytes(
&self, &self,
) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> { ) -> Pin<Box<dyn Future<Output = Result<Bytes, ChunkSerializingError>> + Send + '_>> {
Box::pin(async move { self.internal_to_bytes() }) Box::pin(async move { self.internal_to_bytes().await })
} }
#[inline] #[inline]
@ -315,16 +341,16 @@ impl ChunkEntityData {
Ok(Self { Ok(Self {
x: position.x, x: position.x,
z: position.y, z: position.y,
data: map, data: Mutex::new(map),
dirty: false, dirty: AtomicBool::new(false),
}) })
} }
fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> { async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
let nbt = EntityNbt { let nbt = EntityNbt {
data_version: WORLD_DATA_VERSION, data_version: WORLD_DATA_VERSION,
position: [self.x, self.z], position: [self.x, self.z],
entities: self.data.values().cloned().collect(), entities: self.data.lock().await.values().cloned().collect(),
}; };
let mut result = Vec::new(); let mut result = Vec::new();

View File

@ -148,7 +148,7 @@ where
S: ChunkSerializer<Data = P, WriteBackend = PathBuf>, S: ChunkSerializer<Data = P, WriteBackend = PathBuf>,
S::ChunkConfig: Send + Sync, S::ChunkConfig: Send + Sync,
{ {
type Data = Arc<RwLock<S::Data>>; type Data = Arc<S::Data>;
fn watch_chunks<'a>( fn watch_chunks<'a>(
&'a self, &'a self,
@ -237,14 +237,13 @@ where
} }
}; };
// Intermediate channel for wrapping the data with the Arc<RwLock>
let (send, mut recv) = let (send, mut recv) =
mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1); mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(1);
let intermediary = async move { let intermediary = async move {
// Note: 'task_stream' is captured and moved here // Note: 'task_stream' is captured and moved here
while let Some(data) = recv.recv().await { while let Some(data) = recv.recv().await {
let wrapped_data = data.map_loaded(|data| Arc::new(RwLock::new(data))); let wrapped_data = data.map_loaded(|data| Arc::new(data));
if task_stream.send(wrapped_data).await.is_err() { if task_stream.send(wrapped_data).await.is_err() {
// Stream is closed, so stop unneeded computation and io // Stream is closed, so stop unneeded computation and io
return; return;
@ -313,14 +312,11 @@ where
let update_tasks = chunk_locks.into_iter().map(|chunk_lock| { let update_tasks = chunk_locks.into_iter().map(|chunk_lock| {
let chunk_serializer = chunk_serializer.clone(); let chunk_serializer = chunk_serializer.clone();
async move { async move {
let mut chunk = chunk_lock.write().await; let chunk = chunk_lock;
let chunk_is_dirty = chunk.is_dirty(); let chunk_is_dirty = chunk.is_dirty();
// Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are // Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are
// updating what we will write here // updating what we will write here
chunk.mark_dirty(false); chunk.mark_dirty(false);
// It is important that we keep the lock after we mark the chunk as clean so no one else
// can modify it
let chunk = chunk.downgrade();
// We only need to update the chunk if it is dirty // We only need to update the chunk if it is dirty
if chunk_is_dirty { if chunk_is_dirty {

View File

@ -35,7 +35,7 @@ impl<D: Send, E: error::Error> LoadedData<D, E> {
pub trait Dirtiable { pub trait Dirtiable {
fn is_dirty(&self) -> bool; fn is_dirty(&self) -> bool;
fn mark_dirty(&mut self, flag: bool); fn mark_dirty(&self, flag: bool);
} }
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

View File

@ -16,7 +16,9 @@ use serde::{Deserialize, Serialize};
use std::fmt::Write; use std::fmt::Write;
use std::ops::{BitAnd, BitOr}; use std::ops::{BitAnd, BitOr};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use thiserror::Error; use thiserror::Error;
use tokio::sync::Mutex;
pub mod format; pub mod format;
pub mod io; pub mod io;
@ -72,26 +74,25 @@ pub enum CompressionError {
pub struct ChunkData { pub struct ChunkData {
pub section: ChunkSections, pub section: ChunkSections,
/// See `https://minecraft.wiki/w/Heightmap` for more info /// See `https://minecraft.wiki/w/Heightmap` for more info
pub heightmap: ChunkHeightmaps, pub heightmap: std::sync::Mutex<ChunkHeightmaps>,
pub x: i32, pub x: i32,
pub z: i32, pub z: i32,
pub block_ticks: ChunkTickScheduler<&'static Block>, pub block_ticks: ChunkTickScheduler<&'static Block>,
pub fluid_ticks: ChunkTickScheduler<&'static Fluid>, pub fluid_ticks: ChunkTickScheduler<&'static Fluid>,
pub block_entities: FxHashMap<BlockPos, Arc<dyn BlockEntity>>, pub block_entities: std::sync::Mutex<FxHashMap<BlockPos, Arc<dyn BlockEntity>>>,
pub light_engine: ChunkLight, pub light_engine: ChunkLight,
pub status: ChunkStatus, pub status: ChunkStatus,
pub dirty: bool, pub dirty: AtomicBool,
} }
#[derive(Clone)]
pub struct ChunkEntityData { pub struct ChunkEntityData {
/// Chunk X /// Chunk X
pub x: i32, pub x: i32,
/// Chunk Z /// Chunk Z
pub z: i32, pub z: i32,
pub data: FxHashMap<uuid::Uuid, NbtCompound>, pub data: Mutex<FxHashMap<uuid::Uuid, NbtCompound>>,
pub dirty: bool, pub dirty: AtomicBool,
} }
/// Represents pure block data for a chunk. /// Represents pure block data for a chunk.
@ -100,9 +101,8 @@ pub struct ChunkEntityData {
/// ///
/// A chunk can be: /// A chunk can be:
/// - Subchunks: 24 separate subchunks are stored. /// - Subchunks: 24 separate subchunks are stored.
#[derive(Clone)]
pub struct ChunkSections { pub struct ChunkSections {
pub sections: Box<[SubChunk]>, pub sections: std::sync::Mutex<Box<[SubChunk]>>,
pub min_y: i32, pub min_y: i32,
} }
@ -112,7 +112,7 @@ impl ChunkSections {
pub fn dump_blocks(&self) -> Vec<u16> { pub fn dump_blocks(&self) -> Vec<u16> {
// TODO: this is not optimal, we could use rust iters // TODO: this is not optimal, we could use rust iters
let mut dump = Vec::new(); let mut dump = Vec::new();
for section in &self.sections { for section in self.sections.lock().unwrap().iter() {
section.block_states.for_each(|raw_id| { section.block_states.for_each(|raw_id| {
dump.push(raw_id); dump.push(raw_id);
}); });
@ -125,7 +125,7 @@ impl ChunkSections {
pub fn dump_biomes(&self) -> Vec<u8> { pub fn dump_biomes(&self) -> Vec<u8> {
// TODO: this is not optimal, we could use rust iters // TODO: this is not optimal, we could use rust iters
let mut dump = Vec::new(); let mut dump = Vec::new();
for section in &self.sections { for section in self.sections.lock().unwrap().iter() {
section.biomes.for_each(|raw_id| { section.biomes.for_each(|raw_id| {
dump.push(raw_id); dump.push(raw_id);
}); });
@ -292,7 +292,7 @@ impl Default for ChunkHeightmaps {
impl ChunkSections { impl ChunkSections {
#[must_use] #[must_use]
pub const fn new(sections: Box<[SubChunk]>, min_y: i32) -> Self { pub const fn new(sections: std::sync::Mutex<Box<[SubChunk]>>, min_y: i32) -> Self {
Self { sections, min_y } Self { sections, min_y }
} }
@ -335,7 +335,7 @@ impl ChunkSections {
/// Returns the replaced block state ID /// Returns the replaced block state ID
pub fn set_block_absolute_y( pub fn set_block_absolute_y(
&mut self, &self,
relative_x: usize, relative_x: usize,
y: i32, y: i32,
relative_z: usize, relative_z: usize,
@ -363,6 +363,8 @@ impl ChunkSections {
let section_index = relative_y / BlockPalette::SIZE; let section_index = relative_y / BlockPalette::SIZE;
let relative_y = relative_y % BlockPalette::SIZE; let relative_y = relative_y % BlockPalette::SIZE;
self.sections self.sections
.lock()
.unwrap()
.get(section_index) .get(section_index)
.map(|section| section.block_states.get(relative_x, relative_y, relative_z)) .map(|section| section.block_states.get(relative_x, relative_y, relative_z))
} }
@ -370,7 +372,7 @@ impl ChunkSections {
/// Sets the given block in the chunk, returning the old block state ID /// Sets the given block in the chunk, returning the old block state ID
#[inline] #[inline]
pub fn set_relative_block( pub fn set_relative_block(
&mut self, &self,
relative_x: usize, relative_x: usize,
relative_y: usize, relative_y: usize,
relative_z: usize, relative_z: usize,
@ -386,7 +388,7 @@ impl ChunkSections {
/// Only use this if you know you don't need to update the heightmap /// Only use this if you know you don't need to update the heightmap
/// or if you manually set the heightmap in `empty_with_heightmap` /// or if you manually set the heightmap in `empty_with_heightmap`
pub fn set_block_no_heightmap_update( pub fn set_block_no_heightmap_update(
&mut self, &self,
relative_x: usize, relative_x: usize,
relative_y: usize, relative_y: usize,
relative_z: usize, relative_z: usize,
@ -397,7 +399,7 @@ impl ChunkSections {
let section_index = relative_y / BlockPalette::SIZE; let section_index = relative_y / BlockPalette::SIZE;
let relative_y = relative_y % BlockPalette::SIZE; let relative_y = relative_y % BlockPalette::SIZE;
if let Some(section) = self.sections.get_mut(section_index) { if let Some(section) = self.sections.lock().unwrap().get_mut(section_index) {
return section return section
.block_states .block_states
.set(relative_x, relative_y, relative_z, block_state_id); .set(relative_x, relative_y, relative_z, block_state_id);
@ -417,7 +419,7 @@ impl ChunkSections {
let section_index = relative_y / BiomePalette::SIZE; let section_index = relative_y / BiomePalette::SIZE;
let relative_y = relative_y % BiomePalette::SIZE; let relative_y = relative_y % BiomePalette::SIZE;
if let Some(section) = self.sections.get_mut(section_index) { if let Some(section) = self.sections.lock().unwrap().get_mut(section_index) {
section section
.biomes .biomes
.set(relative_x, relative_y, relative_z, biome_id); .set(relative_x, relative_y, relative_z, biome_id);
@ -435,6 +437,8 @@ impl ChunkSections {
debug_assert!(scale_x < BiomePalette::SIZE); debug_assert!(scale_x < BiomePalette::SIZE);
debug_assert!(scale_z < BiomePalette::SIZE); debug_assert!(scale_z < BiomePalette::SIZE);
self.sections self.sections
.lock()
.unwrap()
.get(index) .get(index)
.map(|section| section.biomes.get(scale_x, scale_y, scale_z)) .map(|section| section.biomes.get(scale_x, scale_y, scale_z))
} }
@ -503,7 +507,7 @@ impl ChunkData {
} }
//TODO: Tracking heightmaps update. //TODO: Tracking heightmaps update.
pub fn calculate_heightmap(&mut self) -> ChunkHeightmaps { pub fn calculate_heightmap(&self) -> ChunkHeightmaps {
let highest_non_empty_subchunk = self.get_highest_non_empty_subchunk(); let highest_non_empty_subchunk = self.get_highest_non_empty_subchunk();
let mut heightmaps = ChunkHeightmaps::default(); let mut heightmaps = ChunkHeightmaps::default();
@ -578,13 +582,13 @@ impl ChunkData {
#[must_use] #[must_use]
pub fn get_highest_non_empty_subchunk(&self) -> usize { pub fn get_highest_non_empty_subchunk(&self) -> usize {
self.section let sections = self.section.sections.lock().unwrap();
.sections sections
.iter() .iter()
.enumerate() .enumerate()
.rev() .rev()
.position(|(_, sub)| !sub.block_states.has_only_air()) .position(|(_, sub)| !sub.block_states.has_only_air())
.map_or(0, |p| self.section.sections.len() - 1 - p) .map_or(0, |p| sections.len() - 1 - p)
} }
} }

View File

@ -15,6 +15,7 @@ use pumpkin_data::chunk_gen_settings::GenerationSettings;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use std::default::Default; use std::default::Default;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::AtomicBool;
use crate::generation::height_limit::HeightLimitView; use crate::generation::height_limit::HeightLimitView;
@ -52,7 +53,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use slotmap::{Key, SlotMap, new_key_type}; use slotmap::{Key, SlotMap, new_key_type};
use std::sync::atomic::Ordering::{Relaxed, SeqCst}; use std::sync::atomic::Ordering::{Relaxed, SeqCst};
use std::thread; use std::thread;
use tokio::sync::{RwLock, oneshot}; use tokio::sync::oneshot;
type HashMapType<K, V> = FxHashMap<K, V>; type HashMapType<K, V> = FxHashMap<K, V>;
type HashSetType<K> = FxHashSet<K>; type HashSetType<K> = FxHashSet<K>;
@ -786,8 +787,8 @@ impl Chunk {
let proto_chunk = self.get_proto_chunk(); let proto_chunk = self.get_proto_chunk();
let total_sections = dimension.height as usize / 16; let total_sections = dimension.height as usize / 16;
let mut sections = ChunkSections::new( let sections = ChunkSections::new(
vec![SubChunk::default(); total_sections].into_boxed_slice(), Mutex::new(vec![SubChunk::default(); total_sections].into_boxed_slice()),
dimension.min_y, dimension.min_y,
); );
@ -798,7 +799,7 @@ impl Chunk {
let section_index = y_offset as usize / 4; let section_index = y_offset as usize / 4;
let relative_y = y_offset as usize % 4; let relative_y = y_offset as usize % 4;
if let Some(section) = sections.sections.get_mut(section_index) { if let Some(section) = sections.sections.lock().unwrap().get_mut(section_index) {
let absolute_biome_y = biome_min_y + y_offset as i32; let absolute_biome_y = biome_min_y + y_offset as i32;
for z in 0..4 { for z in 0..4 {
@ -816,7 +817,7 @@ impl Chunk {
let section_index = (y_offset as usize) / 16; let section_index = (y_offset as usize) / 16;
let relative_y = (y_offset as usize) % 16; let relative_y = (y_offset as usize) % 16;
if let Some(section) = sections.sections.get_mut(section_index) { if let Some(section) = sections.sections.lock().unwrap().get_mut(section_index) {
for z in 0..16 { for z in 0..16 {
for x in 0..16 { for x in 0..16 {
let block = let block =
@ -827,9 +828,10 @@ impl Chunk {
} }
} }
let len = sections.sections.lock().unwrap().len();
let mut chunk = ChunkData { let mut chunk = ChunkData {
light_engine: ChunkLight { light_engine: ChunkLight {
sky_light: (0..sections.sections.len()) sky_light: (0..len)
.map(|_| { .map(|_| {
if dimension.has_skylight { if dimension.has_skylight {
// Overworld: Start with full sky light before occlusion // Overworld: Start with full sky light before occlusion
@ -840,23 +842,21 @@ impl Chunk {
} }
}) })
.collect(), .collect(),
block_light: (0..sections.sections.len()) block_light: (0..len).map(|_| LightContainer::new_empty(0)).collect(),
.map(|_| LightContainer::new_empty(0))
.collect(),
}, },
section: sections, section: sections,
heightmap: Default::default(), heightmap: Default::default(),
x: proto_chunk.x, x: proto_chunk.x,
z: proto_chunk.z, z: proto_chunk.z,
dirty: true, dirty: AtomicBool::new(true),
block_ticks: Default::default(), block_ticks: Default::default(),
fluid_ticks: Default::default(), fluid_ticks: Default::default(),
block_entities: Default::default(), block_entities: Default::default(),
status: proto_chunk.stage.into(), status: proto_chunk.stage.into(),
}; };
chunk.heightmap = chunk.calculate_heightmap(); chunk.heightmap = Mutex::new(chunk.calculate_heightmap());
*self = Self::Level(Arc::new(RwLock::new(chunk))); *self = Self::Level(Arc::new(chunk));
} }
} }
@ -1006,15 +1006,11 @@ impl GenerationCache for Cache {
return RawBlockState::AIR; return RawBlockState::AIR;
} }
match &self.chunks[(dx * self.size + dz) as usize] { match &self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(data) => { Chunk::Level(data) => RawBlockState(
let chunk = data.blocking_read(); data.section
RawBlockState(
chunk
.section
.get_block_absolute_y((pos.x & 15) as usize, pos.y, (pos.z & 15) as usize) .get_block_absolute_y((pos.x & 15) as usize, pos.y, (pos.z & 15) as usize)
.unwrap_or(0), .unwrap_or(0),
) ),
}
Chunk::Proto(data) => data.get_block_state(pos), Chunk::Proto(data) => data.get_block_state(pos),
} }
} }
@ -1035,8 +1031,7 @@ impl GenerationCache for Cache {
} }
match &mut self.chunks[(dx * self.size + dz) as usize] { match &mut self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(data) => { Chunk::Level(data) => {
let mut chunk = data.blocking_write(); data.section.set_block_absolute_y(
chunk.section.set_block_absolute_y(
(pos.x & 15) as usize, (pos.x & 15) as usize,
pos.y, pos.y,
(pos.z & 15) as usize, (pos.z & 15) as usize,
@ -1069,13 +1064,10 @@ impl GenerationCache for Cache {
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => { Chunk::Level(data) => {
let chunk = data.blocking_read(); let heightmap = data.heightmap.lock().unwrap();
chunk.heightmap.get( let min_y = data.section.min_y;
ChunkHeightmapType::MotionBlocking,
x, heightmap.get(ChunkHeightmapType::MotionBlocking, x, z, min_y)
z,
chunk.section.min_y,
)
} }
Chunk::Proto(data) => data.top_motion_blocking_block_height_exclusive(x, z), Chunk::Proto(data) => data.top_motion_blocking_block_height_exclusive(x, z),
} }
@ -1088,13 +1080,9 @@ impl GenerationCache for Cache {
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => { Chunk::Level(data) => {
let chunk = data.blocking_read(); let heightmap = data.heightmap.lock().unwrap();
chunk.heightmap.get( let min_y = data.section.min_y;
ChunkHeightmapType::MotionBlockingNoLeaves, heightmap.get(ChunkHeightmapType::MotionBlockingNoLeaves, x, z, min_y)
x,
z,
chunk.section.min_y,
)
} }
Chunk::Proto(data) => data.top_motion_blocking_block_no_leaves_height_exclusive(x, z), Chunk::Proto(data) => data.top_motion_blocking_block_no_leaves_height_exclusive(x, z),
} }
@ -1107,10 +1095,9 @@ impl GenerationCache for Cache {
debug_assert!(dx >= 0 && dy >= 0); debug_assert!(dx >= 0 && dy >= 0);
match &self.chunks[(dx * self.size + dy) as usize] { match &self.chunks[(dx * self.size + dy) as usize] {
Chunk::Level(data) => { Chunk::Level(data) => {
let chunk = data.blocking_read(); let heightmap = data.heightmap.lock().unwrap();
chunk let min_y = data.section.min_y;
.heightmap heightmap.get(ChunkHeightmapType::WorldSurface, x, z, min_y) // can we return this?
.get(ChunkHeightmapType::WorldSurface, x, z, chunk.section.min_y) // can we return this?
} }
Chunk::Proto(data) => data.top_block_height_exclusive(x, z), Chunk::Proto(data) => data.top_block_height_exclusive(x, z),
} }
@ -1138,8 +1125,7 @@ impl GenerationCache for Cache {
Chunk::Level(data) => { Chunk::Level(data) => {
// Could this happen? // Could this happen?
Biome::from_id( Biome::from_id(
data.blocking_read() data.section
.section
.get_rough_biome_absolute_y((x & 15) as usize, y, (z & 15) as usize) .get_rough_biome_absolute_y((x & 15) as usize, y, (z & 15) as usize)
.unwrap_or(0), .unwrap_or(0),
) )
@ -1666,7 +1652,7 @@ impl GenerationSchedule {
}; };
match data { match data {
Loaded(chunk) => { Loaded(chunk) => {
if chunk.read().await.status == ChunkStatus::Full { if chunk.status == ChunkStatus::Full {
if send if send
.send((pos, RecvChunk::IO(Chunk::Level(chunk)))) .send((pos, RecvChunk::IO(Chunk::Level(chunk))))
.is_err() .is_err()
@ -1675,13 +1661,15 @@ impl GenerationSchedule {
} }
} else { } else {
// debug!("io read thread receive proto chunk {pos:?}",); // debug!("io read thread receive proto chunk {pos:?}",);
let val = let val = RecvChunk::IO(Chunk::Proto(Box::new(
RecvChunk::IO(Chunk::Proto(Box::new(ProtoChunk::from_chunk_data( ProtoChunk::from_chunk_data(
&*chunk.read().await, &chunk,
dimension, dimension,
level.world_gen.default_block, level.world_gen.default_block,
biome_mixer_seed, biome_mixer_seed,
)))); )
.await,
)));
if send.send((pos, val)).is_err() { if send.send((pos, val)).is_err() {
break; break;
} }

View File

@ -212,8 +212,7 @@ impl ProtoChunk {
} }
} }
#[must_use] pub async fn from_chunk_data(
pub fn from_chunk_data(
chunk_data: &ChunkData, chunk_data: &ChunkData,
dimension: &Dimension, dimension: &Dimension,
default_block: &'static BlockState, default_block: &'static BlockState,
@ -227,36 +226,36 @@ impl ProtoChunk {
biome_mixer_seed, biome_mixer_seed,
); );
for (section_y, section) in chunk_data.section.sections.iter().enumerate() { let section_data = &chunk_data.section;
// 1. Calculate the base Y for this section let heightmap_data = chunk_data.heightmap.lock().unwrap();
for (section_y, section) in section_data.sections.lock().unwrap().iter().enumerate() {
let section_base_y = section_y as i32 * 16; let section_base_y = section_y as i32 * 16;
if section_base_y >= proto_chunk.height() as i32 { if section_base_y >= proto_chunk.height() as i32 {
continue; continue;
} }
// --- Block States ---
for x in 0..16 { for x in 0..16 {
for y in 0..16 { for y in 0..16 {
for z in 0..16 { for z in 0..16 {
let block_state_id = section.block_states.get(x, y, z); let block_state_id = section.block_states.get(x, y, z);
let block_state = BlockState::from_id(block_state_id); let block_state = BlockState::from_id(block_state_id);
let absolute_y = section_base_y + y as i32 + section_data.min_y;
let absolute_y = section_base_y + y as i32 + chunk_data.section.min_y;
proto_chunk.set_block_state(x as i32, absolute_y, z as i32, block_state); proto_chunk.set_block_state(x as i32, absolute_y, z as i32, block_state);
} }
} }
} }
// --- Biomes ---
for x in 0..4 { for x in 0..4 {
for y in 0..4 { for y in 0..4 {
for z in 0..4 { for z in 0..4 {
let biome_id = section.biomes.get(x, y, z); let biome_id = section.biomes.get(x, y, z);
let relative_y_block = section_base_y + (y as i32 * 4); let relative_y_block = section_base_y + (y as i32 * 4);
let biome_y_idx = biome_coords::from_block(relative_y_block); let biome_y_idx = biome_coords::from_block(relative_y_block);
let index = proto_chunk.local_biome_pos_to_biome_index( let index = proto_chunk.local_biome_pos_to_biome_index(
x as i32, x as i32,
biome_y_idx, biome_y_idx,
@ -269,33 +268,29 @@ impl ProtoChunk {
} }
} }
// --- Heightmaps ---
for z in 0..16 { for z in 0..16 {
for x in 0..16 { for x in 0..16 {
let motion_blocking_height = chunk_data.heightmap.get( let index = ((z << 4) + x) as usize;
proto_chunk.flat_motion_blocking_height_map[index] = heightmap_data.get(
ChunkHeightmapType::MotionBlocking, ChunkHeightmapType::MotionBlocking,
x, x,
z, z,
chunk_data.section.min_y, section_data.min_y,
); ) as i16;
let index = ((z << 4) + x) as usize;
proto_chunk.flat_motion_blocking_height_map[index] = motion_blocking_height as i16;
let motion_blocking_no_leaves_height = chunk_data.heightmap.get( proto_chunk.flat_motion_blocking_no_leaves_height_map[index] = heightmap_data.get(
ChunkHeightmapType::MotionBlockingNoLeaves, ChunkHeightmapType::MotionBlockingNoLeaves,
x, x,
z, z,
chunk_data.section.min_y, section_data.min_y,
); )
proto_chunk.flat_motion_blocking_no_leaves_height_map[index] = as i16;
motion_blocking_no_leaves_height as i16;
let world_surface_height = chunk_data.heightmap.get( proto_chunk.flat_surface_height_map[index] =
ChunkHeightmapType::WorldSurface, heightmap_data.get(ChunkHeightmapType::WorldSurface, x, z, section_data.min_y)
x, as i16;
z,
chunk_data.section.min_y,
);
proto_chunk.flat_surface_height_map[index] = world_surface_height as i16;
} }
} }

View File

@ -40,7 +40,6 @@ use std::{
use tokio::{ use tokio::{
select, select,
sync::{ sync::{
RwLock,
mpsc::{self, UnboundedReceiver}, mpsc::{self, UnboundedReceiver},
oneshot, oneshot,
}, },
@ -48,8 +47,8 @@ use tokio::{
}; };
use tokio_util::task::TaskTracker; use tokio_util::task::TaskTracker;
pub type SyncChunk = Arc<RwLock<ChunkData>>; pub type SyncChunk = Arc<ChunkData>;
pub type SyncEntityChunk = Arc<RwLock<ChunkEntityData>>; pub type SyncEntityChunk = Arc<ChunkEntityData>;
/// The `Level` module provides functionality for working with chunks within or outside a Minecraft world. /// The `Level` module provides functionality for working with chunks within or outside a Minecraft world.
/// ///
@ -228,12 +227,12 @@ impl Level {
match rx.recv_timeout(std::time::Duration::from_millis(500)) { match rx.recv_timeout(std::time::Duration::from_millis(500)) {
Ok(pos) => { Ok(pos) => {
let arc_chunk = Arc::new(RwLock::new(ChunkEntityData { let arc_chunk = Arc::new(ChunkEntityData {
x: pos.x, x: pos.x,
z: pos.y, z: pos.y,
data: FxHashMap::default(), data: tokio::sync::Mutex::new(FxHashMap::default()),
dirty: true, dirty: AtomicBool::new(true),
})); });
level_clone level_clone
.loaded_entity_chunks .loaded_entity_chunks
@ -439,16 +438,14 @@ impl Level {
let r = rand::random::<u32>(); let r = rand::random::<u32>();
for chunk_sync in self.loaded_chunks.iter() { for chunk in self.loaded_chunks.iter() {
let mut chunk = chunk_sync.write().await;
let chunk_x_base = chunk.x * 16; let chunk_x_base = chunk.x * 16;
let chunk_z_base = chunk.z * 16; let chunk_z_base = chunk.z * 16;
let section_count = chunk.section.sections.len(); let section_count = chunk.section.sections.lock().unwrap().len();
ticks ticks
.block_entities .block_entities
.extend(chunk.block_entities.values().cloned()); .extend(chunk.block_entities.lock().unwrap().values().cloned());
for i in 0..section_count { for i in 0..section_count {
let y_base = i as i32 * 16; let y_base = i as i32 * 16;
@ -476,13 +473,13 @@ impl Level {
} }
} }
} }
ticks.block_ticks.append(&mut chunk.block_ticks.step_tick()); ticks.block_ticks.append(&mut chunk.block_ticks.step_tick());
ticks.fluid_ticks.append(&mut chunk.fluid_ticks.step_tick()); ticks.fluid_ticks.append(&mut chunk.fluid_ticks.step_tick());
} }
ticks.block_ticks.sort_unstable(); ticks.block_ticks.sort_unstable();
ticks.fluid_ticks.sort_unstable(); ticks.fluid_ticks.sort_unstable();
ticks ticks
} }
@ -597,9 +594,7 @@ impl Level {
while let Some(data) = rx.recv().await { while let Some(data) = rx.recv().await {
match data { match data {
LoadedData::Loaded(chunk) => { LoadedData::Loaded(chunk) => {
let tmp_chunk = chunk.read().await; let pos = Vector2::new(chunk.x, chunk.z);
let pos = Vector2::new(tmp_chunk.x, tmp_chunk.z);
drop(tmp_chunk);
level.loaded_entity_chunks.insert(pos, chunk.clone()); level.loaded_entity_chunks.insert(pos, chunk.clone());
let _ = sender.send((chunk, false)); let _ = sender.send((chunk, false));
} }
@ -664,7 +659,7 @@ impl Level {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let chunk = self.get_chunk(chunk_coordinate).await; let chunk = self.get_chunk(chunk_coordinate).await;
let Some(id) = chunk.read().await.section.get_block_absolute_y( let Some(id) = chunk.section.get_block_absolute_y(
relative.x as usize, relative.x as usize,
relative.y, relative.y,
relative.z as usize, relative.z as usize,
@ -678,7 +673,7 @@ impl Level {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let chunk = self.get_chunk(chunk_coordinate).await; let chunk = self.get_chunk(chunk_coordinate).await;
let Some(id) = chunk.read().await.section.get_rough_biome_absolute_y( let Some(id) = chunk.section.get_rough_biome_absolute_y(
relative.x as usize, relative.x as usize,
relative.y, relative.y,
relative.z as usize, relative.z as usize,
@ -696,8 +691,6 @@ impl Level {
) -> BlockStateId { ) -> BlockStateId {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let chunk = self.get_chunk(chunk_coordinate).await; let chunk = self.get_chunk(chunk_coordinate).await;
let mut chunk = chunk.write().await;
let replaced_block_state_id = chunk.section.set_block_absolute_y( let replaced_block_state_id = chunk.section.set_block_absolute_y(
relative.x as usize, relative.x as usize,
relative.y, relative.y,
@ -744,7 +737,7 @@ impl Level {
} }
} }
pub fn try_get_chunk(&self, coordinates: &Vector2<i32>) -> Option<Arc<RwLock<ChunkData>>> { pub fn try_get_chunk(&self, coordinates: &Vector2<i32>) -> Option<Arc<ChunkData>> {
self.loaded_chunks self.loaded_chunks
.get(coordinates) .get(coordinates)
.map(|x| x.value().clone()) .map(|x| x.value().clone())
@ -753,7 +746,7 @@ impl Level {
pub fn try_get_entity_chunk( pub fn try_get_entity_chunk(
&self, &self,
coordinates: Vector2<i32>, coordinates: Vector2<i32>,
) -> Option<dashmap::mapref::one::Ref<'_, Vector2<i32>, Arc<RwLock<ChunkEntityData>>>> { ) -> Option<dashmap::mapref::one::Ref<'_, Vector2<i32>, Arc<ChunkEntityData>>> {
self.loaded_entity_chunks.try_get(&coordinates).try_unwrap() self.loaded_entity_chunks.try_get(&coordinates).try_unwrap()
} }
@ -766,7 +759,6 @@ impl Level {
) { ) {
let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = self.get_chunk(block_pos.chunk_position()).await;
let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed); let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed);
let mut chunk = chunk.write().await;
chunk.block_ticks.schedule_tick( chunk.block_ticks.schedule_tick(
&ScheduledTick { &ScheduledTick {
delay, delay,
@ -787,7 +779,6 @@ impl Level {
) { ) {
let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = self.get_chunk(block_pos.chunk_position()).await;
let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed); let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed);
let mut chunk = chunk.write().await;
chunk.fluid_ticks.schedule_tick( chunk.fluid_ticks.schedule_tick(
&ScheduledTick { &ScheduledTick {
delay, delay,
@ -805,7 +796,6 @@ impl Level {
block: &Block, block: &Block,
) -> bool { ) -> bool {
let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = self.get_chunk(block_pos.chunk_position()).await;
let chunk = chunk.read().await;
chunk.block_ticks.is_scheduled(*block_pos, block) chunk.block_ticks.is_scheduled(*block_pos, block)
} }
@ -815,7 +805,6 @@ impl Level {
fluid: &Fluid, fluid: &Fluid,
) -> bool { ) -> bool {
let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = self.get_chunk(block_pos.chunk_position()).await;
let chunk = chunk.read().await;
chunk.fluid_ticks.is_scheduled(*block_pos, fluid) chunk.fluid_ticks.is_scheduled(*block_pos, fluid)
} }
} }

View File

@ -1,32 +1,49 @@
use std::sync::{
Mutex,
atomic::{AtomicUsize, Ordering},
};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::tick::{MAX_TICK_DELAY, OrderedTick, ScheduledTick}; use crate::tick::{MAX_TICK_DELAY, OrderedTick, ScheduledTick};
#[derive(Clone)]
pub struct ChunkTickScheduler<T> { pub struct ChunkTickScheduler<T> {
tick_queue: [Vec<OrderedTick<T>>; MAX_TICK_DELAY], tick_queue: Mutex<[Vec<OrderedTick<T>>; MAX_TICK_DELAY]>,
queued_ticks: FxHashSet<(BlockPos, T)>, queued_ticks: Mutex<FxHashSet<(BlockPos, T)>>,
offset: usize, offset: AtomicUsize,
} }
impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> { impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
pub fn step_tick(&mut self) -> Vec<OrderedTick<&'a T>> { pub fn step_tick(&self) -> Vec<OrderedTick<&'a T>> {
self.offset += 1; // Atomic update for the offset
self.offset %= MAX_TICK_DELAY; let current_offset = self.offset.fetch_add(1, Ordering::SeqCst) % MAX_TICK_DELAY;
let mut res = Vec::new(); let next_offset = (current_offset + 1) % MAX_TICK_DELAY;
std::mem::swap(&mut res, &mut self.tick_queue[self.offset]); self.offset.store(next_offset, Ordering::SeqCst);
let res = {
let mut queue = self.tick_queue.lock().unwrap();
std::mem::take(&mut queue[current_offset])
};
if !res.is_empty() {
let mut set = self.queued_ticks.lock().unwrap();
for next_tick in &res { for next_tick in &res {
self.queued_ticks set.remove(&(next_tick.position, next_tick.value));
.remove(&(next_tick.position, next_tick.value)); }
} }
res res
} }
pub fn schedule_tick(&mut self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) { pub fn schedule_tick(&self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) {
if self.queued_ticks.insert((tick.position, tick.value)) { let mut set = self.queued_ticks.lock().unwrap();
let index = (self.offset + tick.delay as usize) % MAX_TICK_DELAY;
self.tick_queue[index].push(OrderedTick { if set.insert((tick.position, tick.value)) {
let mut queue = self.tick_queue.lock().unwrap();
let offset = self.offset.load(Ordering::SeqCst);
let index = (offset + tick.delay as usize) % MAX_TICK_DELAY;
queue[index].push(OrderedTick {
priority: tick.priority, priority: tick.priority,
sub_tick_order, sub_tick_order,
position: tick.position, position: tick.position,
@ -35,16 +52,19 @@ impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
} }
} }
pub fn is_scheduled(&self, pos: BlockPos, value: &'a T) -> bool { pub fn is_scheduled(&self, pos: BlockPos, value: &T) -> bool {
self.queued_ticks.contains(&(pos, value)) self.queued_ticks.lock().unwrap().contains(&(pos, value))
} }
#[must_use] #[must_use]
pub fn to_vec(&self) -> Vec<ScheduledTick<&'a T>> { pub fn to_vec(&self) -> Vec<ScheduledTick<&'a T>> {
let offset = self.offset.load(Ordering::SeqCst);
let queue = self.tick_queue.lock().unwrap();
let mut res = Vec::new(); let mut res = Vec::new();
for i in 0..MAX_TICK_DELAY { for i in 0..MAX_TICK_DELAY {
let index = (self.offset + i) % MAX_TICK_DELAY; let index = (offset + i) % MAX_TICK_DELAY;
res.extend(self.tick_queue[index].iter().map(|x| ScheduledTick { res.extend(queue[index].iter().map(|x| ScheduledTick {
delay: i as u8, delay: i as u8,
priority: x.priority, priority: x.priority,
position: x.position, position: x.position,
@ -59,12 +79,12 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator<ScheduledTick<&'a T>>
for ChunkTickScheduler<&'a T> for ChunkTickScheduler<&'a T>
{ {
fn from_iter<I: IntoIterator<Item = ScheduledTick<&'a T>>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = ScheduledTick<&'a T>>>(iter: I) -> Self {
let mut scheduler = Self::default(); let scheduler = Self::default();
let iter = iter.into_iter(); let iter = iter.into_iter();
let (lower, _) = iter.size_hint(); let (lower, _) = iter.size_hint();
if lower > 0 { if lower > 0 {
scheduler.queued_ticks.reserve(lower); scheduler.queued_ticks.lock().unwrap().reserve(lower);
} }
for tick in iter { for tick in iter {
@ -77,9 +97,9 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator<ScheduledTick<&'a T>>
impl<T> Default for ChunkTickScheduler<T> { impl<T> Default for ChunkTickScheduler<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
tick_queue: std::array::from_fn(|_| Vec::new()), tick_queue: Mutex::new(std::array::from_fn(|_| Vec::new())),
queued_ticks: FxHashSet::default(), queued_ticks: Mutex::new(FxHashSet::default()),
offset: 0, offset: AtomicUsize::new(0),
} }
} }
} }

View File

@ -25,7 +25,7 @@ use pumpkin_protocol::bedrock::server::text::SText;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_world::chunk::{ChunkData, ChunkEntityData}; use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
use pumpkin_world::inventory::Inventory; use pumpkin_world::inventory::Inventory;
use tokio::sync::{Mutex, RwLock}; use tokio::sync::Mutex;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use uuid::Uuid; use uuid::Uuid;
@ -297,7 +297,7 @@ impl ChunkManager {
pub fn next_chunk(&mut self) -> Box<[SyncChunk]> { pub fn next_chunk(&mut self) -> Box<[SyncChunk]> {
let mut chunk_size = self.chunk_queue.len().min(self.chunks_per_tick); let mut chunk_size = self.chunk_queue.len().min(self.chunks_per_tick);
let mut chunks = Vec::<Arc<RwLock<ChunkData>>>::with_capacity(chunk_size); let mut chunks = Vec::<Arc<ChunkData>>::with_capacity(chunk_size);
while chunk_size > 0 { while chunk_size > 0 {
chunks.push(self.chunk_queue.pop().unwrap().2); chunks.push(self.chunk_queue.pop().unwrap().2);
chunk_size -= 1; chunk_size -= 1;
@ -316,7 +316,7 @@ impl ChunkManager {
pub fn next_entity(&mut self) -> Box<[SyncEntityChunk]> { pub fn next_entity(&mut self) -> Box<[SyncEntityChunk]> {
let chunk_size = self.entity_chunk_queue.len().min(self.chunks_per_tick); let chunk_size = self.entity_chunk_queue.len().min(self.chunks_per_tick);
let chunks: Box<[Arc<RwLock<ChunkEntityData>>]> = self let chunks: Box<[Arc<ChunkEntityData>]> = self
.entity_chunk_queue .entity_chunk_queue
.drain(..chunk_size) .drain(..chunk_size)
.map(|(_, chunk)| chunk) .map(|(_, chunk)| chunk)
@ -1304,7 +1304,6 @@ impl Player {
ClientPlatform::Java(java_client) => { ClientPlatform::Java(java_client) => {
java_client.send_packet_now(&CChunkBatchStart).await; java_client.send_packet_now(&CChunkBatchStart).await;
for chunk in chunk_of_chunks { for chunk in chunk_of_chunks {
let chunk = chunk.read().await;
// log::debug!("send chunk {:?}", chunk.position); // log::debug!("send chunk {:?}", chunk.position);
// TODO: Can we check if we still need to send the chunk? Like if it's a fast moving // TODO: Can we check if we still need to send the chunk? Like if it's a fast moving
// player or something. // player or something.
@ -1316,8 +1315,6 @@ impl Player {
} }
ClientPlatform::Bedrock(bedrock_client) => { ClientPlatform::Bedrock(bedrock_client) => {
for chunk in chunk_of_chunks { for chunk in chunk_of_chunks {
let chunk = chunk.read().await;
bedrock_client bedrock_client
.send_game_packet(&CLevelChunk { .send_game_packet(&CLevelChunk {
dimension: 0, dimension: 0,

View File

@ -126,7 +126,6 @@ use rand::{RngExt, rng};
use scoreboard::Scoreboard; use scoreboard::Scoreboard;
use time::LevelTime; use time::LevelTime;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio::sync::RwLock;
pub mod border; pub mod border;
pub mod bossbar; pub mod bossbar;
@ -272,20 +271,20 @@ impl World {
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() {
let old_chunk = old_chunk.to_vec2_i32(); let old_chunk = old_chunk.to_vec2_i32();
let chunk = self.level.get_entity_chunk(old_chunk).await; let chunk = self.level.get_entity_chunk(old_chunk).await;
let mut chunk = chunk.write().await;
chunk.mark_dirty(true); chunk.mark_dirty(true);
let mut data = chunk.data.lock().await;
if old_chunk == current_chunk_coordinate { if old_chunk == current_chunk_coordinate {
chunk.data.insert(uuid, nbt); data.insert(uuid, nbt);
return; return;
} }
// The chunk has changed, lets remove the entity from the old chunk // The chunk has changed, lets remove the entity from the old chunk
chunk.data.remove(&uuid); data.remove(&uuid);
} }
// We did not continue, so lets save data in a new chunk // We did not continue, so lets save data in a new chunk
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await; let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
let mut chunk = chunk.write().await; let mut data = chunk.data.lock().await;
chunk.data.insert(uuid, nbt); data.insert(uuid, nbt);
chunk.mark_dirty(true); chunk.mark_dirty(true);
} }
@ -294,16 +293,13 @@ impl World {
if let Some(old_chunk) = entity.first_loaded_chunk_position.load() { if let Some(old_chunk) = entity.first_loaded_chunk_position.load() {
let old_chunk = old_chunk.to_vec2_i32(); let old_chunk = old_chunk.to_vec2_i32();
let chunk = self.level.get_entity_chunk(old_chunk).await; let chunk = self.level.get_entity_chunk(old_chunk).await;
let mut chunk = chunk.write().await;
chunk.mark_dirty(true); chunk.mark_dirty(true);
if old_chunk == current_chunk_coordinate { if old_chunk == current_chunk_coordinate {
chunk.data.remove(&entity.entity_uuid); chunk.data.lock().await.remove(&entity.entity_uuid);
drop(chunk);
} else { } else {
let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await; let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await;
let mut chunk = chunk.write().await;
// The chunk has changed, lets remove the entity from the old chunk // The chunk has changed, lets remove the entity from the old chunk
chunk.data.remove(&entity.entity_uuid); chunk.data.lock().await.remove(&entity.entity_uuid);
chunk.mark_dirty(true); chunk.mark_dirty(true);
} }
} }
@ -791,7 +787,7 @@ impl World {
); );
// log::debug!("spawning list size {}", spawn_list.len()); // log::debug!("spawning list size {}", spawn_list.len());
let mut spawning_chunks: Vec<(Vector2<i32>, Arc<RwLock<ChunkData>>)> = let mut spawning_chunks: Vec<(Vector2<i32>, Arc<ChunkData>)> =
spawning_chunks_map.into_iter().collect(); spawning_chunks_map.into_iter().collect();
spawning_chunks.shuffle(&mut rng()); spawning_chunks.shuffle(&mut rng());
@ -1099,7 +1095,7 @@ impl World {
pub async fn tick_spawning_chunk( pub async fn tick_spawning_chunk(
self: &Arc<Self>, self: &Arc<Self>,
chunk_pos: Vector2<i32>, chunk_pos: Vector2<i32>,
chunk: &Arc<RwLock<ChunkData>>, chunk: &Arc<ChunkData>,
spawn_list: &Vec<&'static MobCategory>, spawn_list: &Vec<&'static MobCategory>,
spawn_state: &mut SpawnState, spawn_state: &mut SpawnState,
) { ) {
@ -1111,7 +1107,7 @@ impl World {
let delta = Vector3::new(rand_value & 15, rand_value >> 16 & 15, rand_value >> 8 & 15); let delta = Vector3::new(rand_value & 15, rand_value >> 16 & 15, rand_value >> 8 & 15);
let random_pos = Vector3::new( let random_pos = Vector3::new(
chunk_pos.x << 4, chunk_pos.x << 4,
chunk.read().await.heightmap.get( chunk.heightmap.lock().unwrap().get(
MotionBlocking, MotionBlocking,
chunk_pos.x << 4, chunk_pos.x << 4,
chunk_pos.y << 4, chunk_pos.y << 4,
@ -1174,9 +1170,9 @@ impl World {
let chunk_pos = Vector2::new(x >> 4, z >> 4); let chunk_pos = Vector2::new(x >> 4, z >> 4);
let chunk = self.level.get_chunk(chunk_pos).await; let chunk = self.level.get_chunk(chunk_pos).await;
chunk chunk
.read()
.await
.heightmap .heightmap
.lock()
.unwrap()
.get(MotionBlocking, x, z, self.min_y) .get(MotionBlocking, x, z, self.min_y)
} }
@ -2113,9 +2109,7 @@ impl World {
let Some((chunk, _first_load)) = recv_result else { let Some((chunk, _first_load)) = recv_result else {
break; break;
}; };
let tmp_chunk = chunk.read().await; let position = Vector2::new(chunk.x, chunk.z);
let position = Vector2::new(tmp_chunk.x, tmp_chunk.z);
drop(tmp_chunk);
let chunk = if level.is_chunk_watched(&position) { let chunk = if level.is_chunk_watched(&position) {
chunk chunk
@ -2126,7 +2120,7 @@ impl World {
); );
let mut ids_to_remove = Vec::new(); let mut ids_to_remove = Vec::new();
for (uuid, entity_nbt) in &chunk.read().await.data { for (uuid, entity_nbt) in chunk.data.lock().await.iter() {
let Some(id) = entity_nbt.get_string("id") else { let Some(id) = entity_nbt.get_string("id") else {
log::warn!("Entity has no ID"); log::warn!("Entity has no ID");
continue; continue;
@ -2151,22 +2145,21 @@ impl World {
if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() {
let old_chunk = old_chunk.to_vec2_i32(); let old_chunk = old_chunk.to_vec2_i32();
let chunk = world.level.get_entity_chunk(old_chunk).await; let chunk = world.level.get_entity_chunk(old_chunk).await;
let mut chunk = chunk.write().await;
chunk.mark_dirty(true); chunk.mark_dirty(true);
let base_entity = entity.get_entity(); let base_entity = entity.get_entity();
let current_chunk_coordinate = let current_chunk_coordinate =
base_entity.block_pos.load().chunk_position(); base_entity.block_pos.load().chunk_position();
let mut data = chunk.data.lock().await;
if old_chunk == current_chunk_coordinate { if old_chunk == current_chunk_coordinate {
chunk.data.insert(*uuid, nbt); data.insert(*uuid, nbt);
return; return;
} }
// The chunk has changed, lets remove the entity from the old chunk // The chunk has changed, lets remove the entity from the old chunk
chunk.data.remove(uuid); data.remove(uuid);
} }
let mut chunk = chunk.write().await; chunk.data.lock().await.insert(*uuid, nbt);
chunk.data.insert(*uuid, nbt);
chunk.mark_dirty(true); chunk.mark_dirty(true);
} }
@ -2188,11 +2181,10 @@ impl World {
continue 'main; continue 'main;
}; };
let entity_chunk = chunk.read().await;
// Add all new Entities to the world // Add all new Entities to the world
let mut entities_to_add: Vec<Arc<dyn EntityBase>> = Vec::new(); let mut entities_to_add: Vec<Arc<dyn EntityBase>> = Vec::new();
for (uuid, entity_nbt) in &entity_chunk.data { for (uuid, entity_nbt) in chunk.data.lock().await.iter() {
let Some(id) = entity_nbt.get_string("id") else { let Some(id) = entity_nbt.get_string("id") else {
log::warn!("Entity has no ID"); log::warn!("Entity has no ID");
continue; continue;
@ -2552,10 +2544,9 @@ impl World {
let chunk_coordinate = base_entity.block_pos.load().chunk_position(); let chunk_coordinate = base_entity.block_pos.load().chunk_position();
let chunk = self.level.get_entity_chunk(chunk_coordinate).await; let chunk = self.level.get_entity_chunk(chunk_coordinate).await;
{ {
let mut chunk = chunk.write().await;
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
entity.write_nbt(&mut nbt).await; entity.write_nbt(&mut nbt).await;
chunk.data.insert(base_entity.entity_uuid, nbt); chunk.data.lock().await.insert(base_entity.entity_uuid, nbt);
chunk.mark_dirty(true); chunk.mark_dirty(true);
}; };
@ -2727,10 +2718,7 @@ impl World {
pub async fn get_block_light_level(&self, position: &BlockPos) -> Option<u8> { pub async fn get_block_light_level(&self, position: &BlockPos) -> Option<u8> {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let chunk = self.level.get_chunk(chunk_coordinate).await; let chunk = self.level.get_chunk(chunk_coordinate).await;
let Ok(chunk) = tokio::time::timeout(std::time::Duration::from_secs(1), chunk.read()).await
else {
panic!("Timed out while waiting to acquire chunk read lock")
};
let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
// +1 since block light has 1 section padding on both top and bottom // +1 since block light has 1 section padding on both top and bottom
if section_index + 1 >= chunk.light_engine.block_light.len() { if section_index + 1 >= chunk.light_engine.block_light.len() {
@ -2743,30 +2731,28 @@ impl World {
)) ))
} }
#[expect(clippy::unused_async)]
pub async fn set_block_light_level( pub async fn set_block_light_level(
&self, &self,
position: &BlockPos, _position: &BlockPos,
light_level: u8, _light_level: u8,
) -> Result<(), String> { ) -> Result<(), String> {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); // TODO
let chunk = self.level.get_chunk(chunk_coordinate).await; // let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let Ok(mut chunk) = // let chunk = self.level.get_chunk(chunk_coordinate).await;
tokio::time::timeout(std::time::Duration::from_secs(1), chunk.write()).await
else { // let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE;
panic!("Timed out while waiting to acquire chunk write lock") // if section_index >= chunk.light_engine.block_light.len() {
}; // return Err("Invalid section index".to_string());
let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; // }
if section_index >= chunk.light_engine.block_light.len() { // let relative_y = (relative.y - chunk.section.min_y) as usize % BlockPalette::SIZE;
return Err("Invalid section index".to_string()); // // chunk.light_engine.block_light[section_index + 1].set(
} // // relative.x as usize,
let relative_y = (relative.y - chunk.section.min_y) as usize % BlockPalette::SIZE; // // relative_y,
chunk.light_engine.block_light[section_index + 1].set( // // relative.z as usize,
relative.x as usize, // // light_level,
relative_y, // // );
relative.z as usize, // chunk.mark_dirty(true);
light_level,
);
chunk.mark_dirty(true);
Ok(()) Ok(())
} }
@ -2780,11 +2766,7 @@ impl World {
) -> BlockStateId { ) -> BlockStateId {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let chunk = self.level.get_chunk(chunk_coordinate).await; let chunk = self.level.get_chunk(chunk_coordinate).await;
let Ok(mut chunk) =
tokio::time::timeout(std::time::Duration::from_secs(1), chunk.write()).await
else {
panic!("Timed out while waiting to acquire chunk write lock")
};
let replaced_block_state_id = chunk.section.set_block_absolute_y( let replaced_block_state_id = chunk.section.set_block_absolute_y(
relative.x as usize, relative.x as usize,
relative.y, relative.y,
@ -3326,15 +3308,12 @@ impl World {
pub async fn get_block_entity(&self, block_pos: &BlockPos) -> Option<Arc<dyn BlockEntity>> { pub async fn get_block_entity(&self, block_pos: &BlockPos) -> Option<Arc<dyn BlockEntity>> {
let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
let chunk: tokio::sync::RwLockReadGuard<ChunkData> = chunk.read().await; chunk.block_entities.lock().unwrap().get(block_pos).cloned()
chunk.block_entities.get(block_pos).cloned()
} }
pub async fn add_block_entity(&self, block_entity: Arc<dyn BlockEntity>) { pub async fn add_block_entity(&self, block_entity: Arc<dyn BlockEntity>) {
let block_pos = block_entity.get_position(); let block_pos = block_entity.get_position();
let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
let mut chunk: tokio::sync::RwLockWriteGuard<ChunkData> = chunk.write().await;
let block_entity_nbt = block_entity.chunk_data_nbt(); let block_entity_nbt = block_entity.chunk_data_nbt();
if let Some(nbt) = &block_entity_nbt { if let Some(nbt) = &block_entity_nbt {
@ -3348,21 +3327,23 @@ impl World {
.await; .await;
} }
chunk.block_entities.insert(block_pos, block_entity); chunk
.block_entities
.lock()
.unwrap()
.insert(block_pos, block_entity);
chunk.mark_dirty(true); chunk.mark_dirty(true);
} }
pub async fn remove_block_entity(&self, block_pos: &BlockPos) { pub async fn remove_block_entity(&self, block_pos: &BlockPos) {
let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
let mut chunk: tokio::sync::RwLockWriteGuard<ChunkData> = chunk.write().await; chunk.block_entities.lock().unwrap().remove(block_pos);
chunk.block_entities.remove(block_pos);
chunk.mark_dirty(true); chunk.mark_dirty(true);
} }
pub async fn update_block_entity(&self, block_entity: &Arc<dyn BlockEntity>) { pub async fn update_block_entity(&self, block_entity: &Arc<dyn BlockEntity>) {
let block_pos = block_entity.get_position(); let block_pos = block_entity.get_position();
let chunk = self.level.get_chunk(block_pos.chunk_position()).await; let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
let mut chunk: tokio::sync::RwLockWriteGuard<ChunkData> = chunk.write().await;
let block_entity_nbt = block_entity.chunk_data_nbt(); let block_entity_nbt = block_entity.chunk_data_nbt();
if let Some(nbt) = &block_entity_nbt { if let Some(nbt) = &block_entity_nbt {

View File

@ -26,7 +26,6 @@ use std::collections::HashMap;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
const MAGIC_NUMBER: i32 = 17 * 17; const MAGIC_NUMBER: i32 = 17 * 17;
@ -316,14 +315,14 @@ pub fn get_filtered_spawning_categories(
pub async fn spawn_for_chunk( pub async fn spawn_for_chunk(
world: &Arc<World>, world: &Arc<World>,
chunk_pos: Vector2<i32>, chunk_pos: Vector2<i32>,
chunk: &Arc<RwLock<ChunkData>>, chunk: &Arc<ChunkData>,
spawn_state: &mut SpawnState, spawn_state: &mut SpawnState,
spawn_list: &Vec<&'static MobCategory>, spawn_list: &Vec<&'static MobCategory>,
) { ) {
// debug!("spawn for chunk {:?}", chunk_pos); // debug!("spawn for chunk {:?}", chunk_pos);
for category in spawn_list { for category in spawn_list {
if spawn_state.can_spawn_for_category_local(world, category, chunk_pos) { if spawn_state.can_spawn_for_category_local(world, category, chunk_pos) {
let random_pos = get_random_pos_within(world.min_y, &chunk_pos, chunk).await; let random_pos = get_random_pos_within(world.min_y, &chunk_pos, chunk);
if random_pos.0.y > world.min_y { if random_pos.0.y > world.min_y {
spawn_category_for_position(category, world, random_pos, &chunk_pos, spawn_state) spawn_category_for_position(category, world, random_pos, &chunk_pos, spawn_state)
.await; .await;
@ -331,19 +330,19 @@ pub async fn spawn_for_chunk(
} }
} }
} }
pub async fn get_random_pos_within( pub fn get_random_pos_within(
min_y: i32, min_y: i32,
chunk_pos: &Vector2<i32>, chunk_pos: &Vector2<i32>,
chunk: &Arc<RwLock<ChunkData>>, chunk: &Arc<ChunkData>,
) -> BlockPos { ) -> BlockPos {
let mut rng = Xoroshiro::from_seed(get_seed()); let mut rng = Xoroshiro::from_seed(get_seed());
let x = (chunk_pos.x << 4) + rng.next_bounded_i32(16); let x = (chunk_pos.x << 4) + rng.next_bounded_i32(16);
let z = (chunk_pos.y << 4) + rng.next_bounded_i32(16); let z = (chunk_pos.y << 4) + rng.next_bounded_i32(16);
let temp_y = chunk let temp_y = chunk
.read()
.await
.heightmap .heightmap
.lock()
.unwrap()
.get(ChunkHeightmapType::WorldSurface, x, z, min_y) .get(ChunkHeightmapType::WorldSurface, x, z, min_y)
+ 1; + 1;
let y = rng.next_inbetween_i32(min_y, temp_y); let y = rng.next_inbetween_i32(min_y, temp_y);
@ -437,12 +436,13 @@ pub async fn spawn_category_for_position(
{ {
let chunk_handle = world.level.get_entity_chunk(*chunk_pos).await; let chunk_handle = world.level.get_entity_chunk(*chunk_pos).await;
let mut chunk_lock = chunk_handle.write().await; let mut data = chunk_handle.data.lock().await;
for (uuid, nbt, _, _) in &prepared_data { for (uuid, nbt, _, _) in &prepared_data {
chunk_lock.data.insert(*uuid, nbt.clone()); data.insert(*uuid, nbt.clone());
} }
chunk_lock.mark_dirty(true); drop(data);
chunk_handle.mark_dirty(true);
world.entities.rcu(|current_entities| { world.entities.rcu(|current_entities| {
let mut new_entities = (**current_entities).clone(); let mut new_entities = (**current_entities).clone();