diff --git a/pumpkin-protocol/src/bedrock/client/level_chunk.rs b/pumpkin-protocol/src/bedrock/client/level_chunk.rs index 114be5e30..ccd5c1fd5 100644 --- a/pumpkin-protocol/src/bedrock/client/level_chunk.rs +++ b/pumpkin-protocol/src/bedrock/client/level_chunk.rs @@ -26,7 +26,7 @@ impl PacketWrite for CLevelChunk<'_> { VarInt(self.chunk.z).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); VarUInt(sub_chunk_count).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; // 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:byte][num_storages:byte][sub_chunk_index:byte][block storage1]...[blockStorageN] let y = i as i8 + min_y; diff --git a/pumpkin-protocol/src/java/client/play/chunk_data.rs b/pumpkin-protocol/src/java/client/play/chunk_data.rs index df7fd7bf5..6cdecd767 100644 --- a/pumpkin-protocol/src/java/client/play/chunk_data.rs +++ b/pumpkin-protocol/src/java/client/play/chunk_data.rs @@ -32,7 +32,7 @@ impl ClientPacket for CChunkData<'_> { // Chunk 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 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(); - for section in &self.0.section.sections { + for section in self.0.section.sections.lock().unwrap().iter() { // Block count 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)?; @@ -119,8 +119,9 @@ impl ClientPacket for CChunkData<'_> { write.write_slice(&blocks_and_biomes_buf)?; }; - write.write_var_int(&VarInt(self.0.block_entities.len() as i32))?; - for block_entity in self.0.block_entities.values() { + let block_entities = self.0.block_entities.lock().unwrap(); + write.write_var_int(&VarInt(block_entities.len() as i32))?; + for block_entity in block_entities.values() { let pos = block_entity.get_position(); let local_xz = ((get_local_cord(pos.0.x) & 0xF) << 4) | (get_local_cord(pos.0.z) & 0xF); diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs index eb2c0aed9..7ea2994f1 100644 --- a/pumpkin-world/src/chunk/format/mod.rs +++ b/pumpkin-world/src/chunk/format/mod.rs @@ -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 futures::future::join_all; use pumpkin_data::{Block, chunk::ChunkStatus, fluid::Fluid}; use pumpkin_nbt::{compound::NbtCompound, from_bytes, nbt_long_array}; use rustc_hash::FxHashMap; +use tokio::sync::Mutex; use uuid::Uuid; use crate::{ @@ -58,13 +64,13 @@ impl PathFromLevelFolder for ChunkData { impl Dirtiable for ChunkData { #[inline] - fn mark_dirty(&mut self, flag: bool) { - self.dirty = flag; + fn mark_dirty(&self, flag: bool) { + self.dirty.store(flag, Ordering::Relaxed); } #[inline] 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(), }; 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 { section, - heightmap: chunk_data.heightmaps, + heightmap: std::sync::Mutex::new(chunk_data.heightmaps), x: position.x, z: position.y, // 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), fluid_ticks: ChunkTickScheduler::from_iter(chunk_data.fluid_ticks), block_entities: { @@ -178,7 +185,7 @@ impl ChunkData { block_entities.insert(block_entity.get_position(), block_entity); } } - block_entities + std::sync::Mutex::new(block_entities) }, light_engine, status: chunk_data.status, @@ -186,25 +193,50 @@ impl ChunkData { } async fn internal_to_bytes(&self) -> Result { - let sections: Vec<_> = self - .section - .sections - .iter() - .enumerate() - .map(|(i, section)| ChunkSectionNBT { - y: (i as i8) + section_coords::block_to_section(self.section.min_y) as i8, - block_states: Some(section.block_states.to_disk_nbt()), - biomes: Some(section.biomes.to_disk_nbt()), - block_light: match self.light_engine.block_light[i].clone() { - LightContainer::Empty(_) => None, - LightContainer::Full(data) => Some(data), - }, - sky_light: match self.light_engine.sky_light[i].clone() { - LightContainer::Empty(_) => None, - LightContainer::Full(data) => Some(data), - }, - }) - .collect(); + // 1. Process sections (Standard Mutex) + // We do this in a block or just let the guard drop naturally + // before the first .await + let sections: Vec<_> = { + let sections_guard = self.section.sections.lock().unwrap(); + sections_guard + .iter() + .enumerate() + .map(|(i, section)| ChunkSectionNBT { + y: (i as i8) + section_coords::block_to_section(self.section.min_y) as i8, + block_states: Some(section.block_states.to_disk_nbt()), + biomes: Some(section.biomes.to_disk_nbt()), + block_light: match self.light_engine.block_light[i].clone() { + LightContainer::Empty(_) => None, + LightContainer::Full(data) => Some(data), + }, + sky_light: match self.light_engine.sky_light[i].clone() { + LightContainer::Empty(_) => None, + LightContainer::Full(data) => Some(data), + }, + }) + .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::>() + }; + // 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 { data_version: WORLD_DATA_VERSION, @@ -212,17 +244,11 @@ impl ChunkData { z_pos: self.z, min_y_section: section_coords::block_to_section(self.section.min_y), status: self.status, - heightmaps: self.heightmap.clone(), + heightmaps, sections, block_ticks: self.block_ticks.to_vec(), fluid_ticks: self.fluid_ticks.to_vec(), - block_entities: join_all(self.block_entities.values().map(|block_entity| async move { - let mut nbt = NbtCompound::new(); - block_entity.write_internal(&mut nbt).await; - nbt - })) - .await, - // we have not implemented light engine + block_entities: block_entities_nbt, light_correct: false, }; @@ -242,13 +268,13 @@ impl PathFromLevelFolder for ChunkEntityData { impl Dirtiable for ChunkEntityData { #[inline] - fn mark_dirty(&mut self, flag: bool) { - self.dirty = flag; + fn mark_dirty(&self, flag: bool) { + self.dirty.store(flag, Ordering::Relaxed); } #[inline] fn is_dirty(&self) -> bool { - self.dirty + self.dirty.load(Ordering::Relaxed) } } @@ -262,7 +288,7 @@ impl SingleChunkDataSerializer for ChunkEntityData { fn to_bytes( &self, ) -> Pin> + Send + '_>> { - Box::pin(async move { self.internal_to_bytes() }) + Box::pin(async move { self.internal_to_bytes().await }) } #[inline] @@ -315,16 +341,16 @@ impl ChunkEntityData { Ok(Self { x: position.x, z: position.y, - data: map, - dirty: false, + data: Mutex::new(map), + dirty: AtomicBool::new(false), }) } - fn internal_to_bytes(&self) -> Result { + async fn internal_to_bytes(&self) -> Result { let nbt = EntityNbt { data_version: WORLD_DATA_VERSION, position: [self.x, self.z], - entities: self.data.values().cloned().collect(), + entities: self.data.lock().await.values().cloned().collect(), }; let mut result = Vec::new(); diff --git a/pumpkin-world/src/chunk/io/file_manager.rs b/pumpkin-world/src/chunk/io/file_manager.rs index 7b77f4a21..73f371b70 100644 --- a/pumpkin-world/src/chunk/io/file_manager.rs +++ b/pumpkin-world/src/chunk/io/file_manager.rs @@ -148,7 +148,7 @@ where S: ChunkSerializer, S::ChunkConfig: Send + Sync, { - type Data = Arc>; + type Data = Arc; fn watch_chunks<'a>( &'a self, @@ -237,14 +237,13 @@ where } }; - // Intermediate channel for wrapping the data with the Arc let (send, mut recv) = mpsc::channel::>(1); let intermediary = async move { // Note: 'task_stream' is captured and moved here 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() { // Stream is closed, so stop unneeded computation and io return; @@ -313,14 +312,11 @@ where let update_tasks = chunk_locks.into_iter().map(|chunk_lock| { let chunk_serializer = chunk_serializer.clone(); async move { - let mut chunk = chunk_lock.write().await; + let chunk = chunk_lock; let chunk_is_dirty = chunk.is_dirty(); // Edge case: this chunk is loaded while we were saving, mark it as cleaned since we are // updating what we will write here 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 if chunk_is_dirty { diff --git a/pumpkin-world/src/chunk/io/mod.rs b/pumpkin-world/src/chunk/io/mod.rs index 64d69f9e0..75ebb94e3 100644 --- a/pumpkin-world/src/chunk/io/mod.rs +++ b/pumpkin-world/src/chunk/io/mod.rs @@ -35,7 +35,7 @@ impl LoadedData { pub trait Dirtiable { fn is_dirty(&self) -> bool; - fn mark_dirty(&mut self, flag: bool); + fn mark_dirty(&self, flag: bool); } type BoxFuture<'a, T> = Pin + Send + 'a>>; diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 8515908c4..46e63e75d 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -16,7 +16,9 @@ use serde::{Deserialize, Serialize}; use std::fmt::Write; use std::ops::{BitAnd, BitOr}; use std::sync::Arc; +use std::sync::atomic::AtomicBool; use thiserror::Error; +use tokio::sync::Mutex; pub mod format; pub mod io; @@ -72,26 +74,25 @@ pub enum CompressionError { pub struct ChunkData { pub section: ChunkSections, /// See `https://minecraft.wiki/w/Heightmap` for more info - pub heightmap: ChunkHeightmaps, + pub heightmap: std::sync::Mutex, pub x: i32, pub z: i32, pub block_ticks: ChunkTickScheduler<&'static Block>, pub fluid_ticks: ChunkTickScheduler<&'static Fluid>, - pub block_entities: FxHashMap>, + pub block_entities: std::sync::Mutex>>, pub light_engine: ChunkLight, pub status: ChunkStatus, - pub dirty: bool, + pub dirty: AtomicBool, } -#[derive(Clone)] pub struct ChunkEntityData { /// Chunk X pub x: i32, /// Chunk Z pub z: i32, - pub data: FxHashMap, + pub data: Mutex>, - pub dirty: bool, + pub dirty: AtomicBool, } /// Represents pure block data for a chunk. @@ -100,9 +101,8 @@ pub struct ChunkEntityData { /// /// A chunk can be: /// - Subchunks: 24 separate subchunks are stored. -#[derive(Clone)] pub struct ChunkSections { - pub sections: Box<[SubChunk]>, + pub sections: std::sync::Mutex>, pub min_y: i32, } @@ -112,7 +112,7 @@ impl ChunkSections { pub fn dump_blocks(&self) -> Vec { // TODO: this is not optimal, we could use rust iters 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| { dump.push(raw_id); }); @@ -125,7 +125,7 @@ impl ChunkSections { pub fn dump_biomes(&self) -> Vec { // TODO: this is not optimal, we could use rust iters let mut dump = Vec::new(); - for section in &self.sections { + for section in self.sections.lock().unwrap().iter() { section.biomes.for_each(|raw_id| { dump.push(raw_id); }); @@ -292,7 +292,7 @@ impl Default for ChunkHeightmaps { impl ChunkSections { #[must_use] - pub const fn new(sections: Box<[SubChunk]>, min_y: i32) -> Self { + pub const fn new(sections: std::sync::Mutex>, min_y: i32) -> Self { Self { sections, min_y } } @@ -335,7 +335,7 @@ impl ChunkSections { /// Returns the replaced block state ID pub fn set_block_absolute_y( - &mut self, + &self, relative_x: usize, y: i32, relative_z: usize, @@ -363,6 +363,8 @@ impl ChunkSections { let section_index = relative_y / BlockPalette::SIZE; let relative_y = relative_y % BlockPalette::SIZE; self.sections + .lock() + .unwrap() .get(section_index) .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 #[inline] pub fn set_relative_block( - &mut self, + &self, relative_x: usize, relative_y: usize, relative_z: usize, @@ -386,7 +388,7 @@ impl ChunkSections { /// 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` pub fn set_block_no_heightmap_update( - &mut self, + &self, relative_x: usize, relative_y: usize, relative_z: usize, @@ -397,7 +399,7 @@ impl ChunkSections { let section_index = 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 .block_states .set(relative_x, relative_y, relative_z, block_state_id); @@ -417,7 +419,7 @@ impl ChunkSections { let section_index = 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 .biomes .set(relative_x, relative_y, relative_z, biome_id); @@ -435,6 +437,8 @@ impl ChunkSections { debug_assert!(scale_x < BiomePalette::SIZE); debug_assert!(scale_z < BiomePalette::SIZE); self.sections + .lock() + .unwrap() .get(index) .map(|section| section.biomes.get(scale_x, scale_y, scale_z)) } @@ -503,7 +507,7 @@ impl ChunkData { } //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 mut heightmaps = ChunkHeightmaps::default(); @@ -578,13 +582,13 @@ impl ChunkData { #[must_use] pub fn get_highest_non_empty_subchunk(&self) -> usize { - self.section - .sections + let sections = self.section.sections.lock().unwrap(); + sections .iter() .enumerate() .rev() .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) } } diff --git a/pumpkin-world/src/chunk_system.rs b/pumpkin-world/src/chunk_system.rs index cc2b2ee64..3bbb03c9c 100644 --- a/pumpkin-world/src/chunk_system.rs +++ b/pumpkin-world/src/chunk_system.rs @@ -15,6 +15,7 @@ use pumpkin_data::chunk_gen_settings::GenerationSettings; use pumpkin_data::dimension::Dimension; use std::default::Default; use std::pin::Pin; +use std::sync::atomic::AtomicBool; use crate::generation::height_limit::HeightLimitView; @@ -52,7 +53,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use slotmap::{Key, SlotMap, new_key_type}; use std::sync::atomic::Ordering::{Relaxed, SeqCst}; use std::thread; -use tokio::sync::{RwLock, oneshot}; +use tokio::sync::oneshot; type HashMapType = FxHashMap; type HashSetType = FxHashSet; @@ -786,8 +787,8 @@ impl Chunk { let proto_chunk = self.get_proto_chunk(); let total_sections = dimension.height as usize / 16; - let mut sections = ChunkSections::new( - vec![SubChunk::default(); total_sections].into_boxed_slice(), + let sections = ChunkSections::new( + Mutex::new(vec![SubChunk::default(); total_sections].into_boxed_slice()), dimension.min_y, ); @@ -798,7 +799,7 @@ impl Chunk { let section_index = 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; for z in 0..4 { @@ -816,7 +817,7 @@ impl Chunk { let section_index = (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 x in 0..16 { let block = @@ -827,9 +828,10 @@ impl Chunk { } } + let len = sections.sections.lock().unwrap().len(); let mut chunk = ChunkData { light_engine: ChunkLight { - sky_light: (0..sections.sections.len()) + sky_light: (0..len) .map(|_| { if dimension.has_skylight { // Overworld: Start with full sky light before occlusion @@ -840,23 +842,21 @@ impl Chunk { } }) .collect(), - block_light: (0..sections.sections.len()) - .map(|_| LightContainer::new_empty(0)) - .collect(), + block_light: (0..len).map(|_| LightContainer::new_empty(0)).collect(), }, section: sections, heightmap: Default::default(), x: proto_chunk.x, z: proto_chunk.z, - dirty: true, + dirty: AtomicBool::new(true), block_ticks: Default::default(), fluid_ticks: Default::default(), block_entities: Default::default(), status: proto_chunk.stage.into(), }; - chunk.heightmap = chunk.calculate_heightmap(); - *self = Self::Level(Arc::new(RwLock::new(chunk))); + chunk.heightmap = Mutex::new(chunk.calculate_heightmap()); + *self = Self::Level(Arc::new(chunk)); } } @@ -1006,15 +1006,11 @@ impl GenerationCache for Cache { return RawBlockState::AIR; } match &self.chunks[(dx * self.size + dz) as usize] { - Chunk::Level(data) => { - let chunk = data.blocking_read(); - RawBlockState( - chunk - .section - .get_block_absolute_y((pos.x & 15) as usize, pos.y, (pos.z & 15) as usize) - .unwrap_or(0), - ) - } + Chunk::Level(data) => RawBlockState( + data.section + .get_block_absolute_y((pos.x & 15) as usize, pos.y, (pos.z & 15) as usize) + .unwrap_or(0), + ), 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] { Chunk::Level(data) => { - let mut chunk = data.blocking_write(); - chunk.section.set_block_absolute_y( + data.section.set_block_absolute_y( (pos.x & 15) as usize, pos.y, (pos.z & 15) as usize, @@ -1069,13 +1064,10 @@ impl GenerationCache for Cache { debug_assert!(dx >= 0 && dy >= 0); match &self.chunks[(dx * self.size + dy) as usize] { Chunk::Level(data) => { - let chunk = data.blocking_read(); - chunk.heightmap.get( - ChunkHeightmapType::MotionBlocking, - x, - z, - chunk.section.min_y, - ) + let heightmap = data.heightmap.lock().unwrap(); + let min_y = data.section.min_y; + + heightmap.get(ChunkHeightmapType::MotionBlocking, x, z, min_y) } 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); match &self.chunks[(dx * self.size + dy) as usize] { Chunk::Level(data) => { - let chunk = data.blocking_read(); - chunk.heightmap.get( - ChunkHeightmapType::MotionBlockingNoLeaves, - x, - z, - chunk.section.min_y, - ) + let heightmap = data.heightmap.lock().unwrap(); + let min_y = data.section.min_y; + heightmap.get(ChunkHeightmapType::MotionBlockingNoLeaves, x, z, min_y) } 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); match &self.chunks[(dx * self.size + dy) as usize] { Chunk::Level(data) => { - let chunk = data.blocking_read(); - chunk - .heightmap - .get(ChunkHeightmapType::WorldSurface, x, z, chunk.section.min_y) // can we return this? + let heightmap = data.heightmap.lock().unwrap(); + let min_y = data.section.min_y; + heightmap.get(ChunkHeightmapType::WorldSurface, x, z, min_y) // can we return this? } Chunk::Proto(data) => data.top_block_height_exclusive(x, z), } @@ -1138,8 +1125,7 @@ impl GenerationCache for Cache { Chunk::Level(data) => { // Could this happen? Biome::from_id( - data.blocking_read() - .section + data.section .get_rough_biome_absolute_y((x & 15) as usize, y, (z & 15) as usize) .unwrap_or(0), ) @@ -1666,7 +1652,7 @@ impl GenerationSchedule { }; match data { Loaded(chunk) => { - if chunk.read().await.status == ChunkStatus::Full { + if chunk.status == ChunkStatus::Full { if send .send((pos, RecvChunk::IO(Chunk::Level(chunk)))) .is_err() @@ -1675,13 +1661,15 @@ impl GenerationSchedule { } } else { // debug!("io read thread receive proto chunk {pos:?}",); - let val = - RecvChunk::IO(Chunk::Proto(Box::new(ProtoChunk::from_chunk_data( - &*chunk.read().await, + let val = RecvChunk::IO(Chunk::Proto(Box::new( + ProtoChunk::from_chunk_data( + &chunk, dimension, level.world_gen.default_block, biome_mixer_seed, - )))); + ) + .await, + ))); if send.send((pos, val)).is_err() { break; } diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index ae4c042b6..f6d0c9545 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -212,8 +212,7 @@ impl ProtoChunk { } } - #[must_use] - pub fn from_chunk_data( + pub async fn from_chunk_data( chunk_data: &ChunkData, dimension: &Dimension, default_block: &'static BlockState, @@ -227,36 +226,36 @@ impl ProtoChunk { biome_mixer_seed, ); - for (section_y, section) in chunk_data.section.sections.iter().enumerate() { - // 1. Calculate the base Y for this section + let section_data = &chunk_data.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; if section_base_y >= proto_chunk.height() as i32 { continue; } + // --- Block States --- for x in 0..16 { for y in 0..16 { for z in 0..16 { let block_state_id = section.block_states.get(x, y, z); let block_state = BlockState::from_id(block_state_id); - - let absolute_y = section_base_y + y as i32 + chunk_data.section.min_y; + let absolute_y = section_base_y + y as i32 + section_data.min_y; proto_chunk.set_block_state(x as i32, absolute_y, z as i32, block_state); } } } + // --- Biomes --- for x in 0..4 { for y in 0..4 { for z in 0..4 { let biome_id = section.biomes.get(x, y, z); - let relative_y_block = section_base_y + (y as i32 * 4); - let biome_y_idx = biome_coords::from_block(relative_y_block); - let index = proto_chunk.local_biome_pos_to_biome_index( x as i32, biome_y_idx, @@ -269,33 +268,29 @@ impl ProtoChunk { } } + // --- Heightmaps --- for z 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, x, z, - chunk_data.section.min_y, - ); - let index = ((z << 4) + x) as usize; - proto_chunk.flat_motion_blocking_height_map[index] = motion_blocking_height as i16; + section_data.min_y, + ) 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, x, z, - chunk_data.section.min_y, - ); - proto_chunk.flat_motion_blocking_no_leaves_height_map[index] = - motion_blocking_no_leaves_height as i16; + section_data.min_y, + ) + as i16; - let world_surface_height = chunk_data.heightmap.get( - ChunkHeightmapType::WorldSurface, - x, - z, - chunk_data.section.min_y, - ); - proto_chunk.flat_surface_height_map[index] = world_surface_height as i16; + proto_chunk.flat_surface_height_map[index] = + heightmap_data.get(ChunkHeightmapType::WorldSurface, x, z, section_data.min_y) + as i16; } } diff --git a/pumpkin-world/src/level.rs b/pumpkin-world/src/level.rs index 9e91cce12..d0e12d213 100644 --- a/pumpkin-world/src/level.rs +++ b/pumpkin-world/src/level.rs @@ -40,7 +40,6 @@ use std::{ use tokio::{ select, sync::{ - RwLock, mpsc::{self, UnboundedReceiver}, oneshot, }, @@ -48,8 +47,8 @@ use tokio::{ }; use tokio_util::task::TaskTracker; -pub type SyncChunk = Arc>; -pub type SyncEntityChunk = Arc>; +pub type SyncChunk = Arc; +pub type SyncEntityChunk = Arc; /// 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)) { Ok(pos) => { - let arc_chunk = Arc::new(RwLock::new(ChunkEntityData { + let arc_chunk = Arc::new(ChunkEntityData { x: pos.x, z: pos.y, - data: FxHashMap::default(), - dirty: true, - })); + data: tokio::sync::Mutex::new(FxHashMap::default()), + dirty: AtomicBool::new(true), + }); level_clone .loaded_entity_chunks @@ -439,16 +438,14 @@ impl Level { let r = rand::random::(); - for chunk_sync in self.loaded_chunks.iter() { - let mut chunk = chunk_sync.write().await; - + for chunk in self.loaded_chunks.iter() { let chunk_x_base = chunk.x * 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 .block_entities - .extend(chunk.block_entities.values().cloned()); + .extend(chunk.block_entities.lock().unwrap().values().cloned()); for i in 0..section_count { let y_base = i as i32 * 16; @@ -476,13 +473,13 @@ impl Level { } } } - ticks.block_ticks.append(&mut chunk.block_ticks.step_tick()); ticks.fluid_ticks.append(&mut chunk.fluid_ticks.step_tick()); } ticks.block_ticks.sort_unstable(); ticks.fluid_ticks.sort_unstable(); + ticks } @@ -597,9 +594,7 @@ impl Level { while let Some(data) = rx.recv().await { match data { LoadedData::Loaded(chunk) => { - let tmp_chunk = chunk.read().await; - let pos = Vector2::new(tmp_chunk.x, tmp_chunk.z); - drop(tmp_chunk); + let pos = Vector2::new(chunk.x, chunk.z); level.loaded_entity_chunks.insert(pos, chunk.clone()); let _ = sender.send((chunk, false)); } @@ -664,7 +659,7 @@ impl Level { let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); 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.y, relative.z as usize, @@ -678,7 +673,7 @@ impl Level { let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); 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.y, relative.z as usize, @@ -696,8 +691,6 @@ impl Level { ) -> BlockStateId { let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); 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( relative.x as usize, relative.y, @@ -744,7 +737,7 @@ impl Level { } } - pub fn try_get_chunk(&self, coordinates: &Vector2) -> Option>> { + pub fn try_get_chunk(&self, coordinates: &Vector2) -> Option> { self.loaded_chunks .get(coordinates) .map(|x| x.value().clone()) @@ -753,7 +746,7 @@ impl Level { pub fn try_get_entity_chunk( &self, coordinates: Vector2, - ) -> Option, Arc>>> { + ) -> Option, Arc>> { 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 tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed); - let mut chunk = chunk.write().await; chunk.block_ticks.schedule_tick( &ScheduledTick { delay, @@ -787,7 +779,6 @@ impl Level { ) { let chunk = self.get_chunk(block_pos.chunk_position()).await; let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed); - let mut chunk = chunk.write().await; chunk.fluid_ticks.schedule_tick( &ScheduledTick { delay, @@ -805,7 +796,6 @@ impl Level { block: &Block, ) -> bool { let chunk = self.get_chunk(block_pos.chunk_position()).await; - let chunk = chunk.read().await; chunk.block_ticks.is_scheduled(*block_pos, block) } @@ -815,7 +805,6 @@ impl Level { fluid: &Fluid, ) -> bool { let chunk = self.get_chunk(block_pos.chunk_position()).await; - let chunk = chunk.read().await; chunk.fluid_ticks.is_scheduled(*block_pos, fluid) } } diff --git a/pumpkin-world/src/tick/scheduler.rs b/pumpkin-world/src/tick/scheduler.rs index 50b507c85..9863b99fa 100644 --- a/pumpkin-world/src/tick/scheduler.rs +++ b/pumpkin-world/src/tick/scheduler.rs @@ -1,32 +1,49 @@ +use std::sync::{ + Mutex, + atomic::{AtomicUsize, Ordering}, +}; + use pumpkin_util::math::position::BlockPos; use rustc_hash::FxHashSet; use crate::tick::{MAX_TICK_DELAY, OrderedTick, ScheduledTick}; -#[derive(Clone)] pub struct ChunkTickScheduler { - tick_queue: [Vec>; MAX_TICK_DELAY], - queued_ticks: FxHashSet<(BlockPos, T)>, - offset: usize, + tick_queue: Mutex<[Vec>; MAX_TICK_DELAY]>, + queued_ticks: Mutex>, + offset: AtomicUsize, } impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> { - pub fn step_tick(&mut self) -> Vec> { - self.offset += 1; - self.offset %= MAX_TICK_DELAY; - let mut res = Vec::new(); - std::mem::swap(&mut res, &mut self.tick_queue[self.offset]); - for next_tick in &res { - self.queued_ticks - .remove(&(next_tick.position, next_tick.value)); + pub fn step_tick(&self) -> Vec> { + // Atomic update for the offset + let current_offset = self.offset.fetch_add(1, Ordering::SeqCst) % MAX_TICK_DELAY; + let next_offset = (current_offset + 1) % MAX_TICK_DELAY; + 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 { + set.remove(&(next_tick.position, next_tick.value)); + } } res } - pub fn schedule_tick(&mut self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) { - if self.queued_ticks.insert((tick.position, tick.value)) { - let index = (self.offset + tick.delay as usize) % MAX_TICK_DELAY; - self.tick_queue[index].push(OrderedTick { + pub fn schedule_tick(&self, tick: &ScheduledTick<&'a T>, sub_tick_order: u64) { + let mut set = self.queued_ticks.lock().unwrap(); + + 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, sub_tick_order, 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 { - self.queued_ticks.contains(&(pos, value)) + pub fn is_scheduled(&self, pos: BlockPos, value: &T) -> bool { + self.queued_ticks.lock().unwrap().contains(&(pos, value)) } #[must_use] pub fn to_vec(&self) -> Vec> { + let offset = self.offset.load(Ordering::SeqCst); + let queue = self.tick_queue.lock().unwrap(); let mut res = Vec::new(); + for i in 0..MAX_TICK_DELAY { - let index = (self.offset + i) % MAX_TICK_DELAY; - res.extend(self.tick_queue[index].iter().map(|x| ScheduledTick { + let index = (offset + i) % MAX_TICK_DELAY; + res.extend(queue[index].iter().map(|x| ScheduledTick { delay: i as u8, priority: x.priority, position: x.position, @@ -59,12 +79,12 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator> for ChunkTickScheduler<&'a T> { fn from_iter>>(iter: I) -> Self { - let mut scheduler = Self::default(); + let scheduler = Self::default(); let iter = iter.into_iter(); let (lower, _) = iter.size_hint(); if lower > 0 { - scheduler.queued_ticks.reserve(lower); + scheduler.queued_ticks.lock().unwrap().reserve(lower); } for tick in iter { @@ -77,9 +97,9 @@ impl<'a, T: std::hash::Hash + Eq + 'static> FromIterator> impl Default for ChunkTickScheduler { fn default() -> Self { Self { - tick_queue: std::array::from_fn(|_| Vec::new()), - queued_ticks: FxHashSet::default(), - offset: 0, + tick_queue: Mutex::new(std::array::from_fn(|_| Vec::new())), + queued_ticks: Mutex::new(FxHashSet::default()), + offset: AtomicUsize::new(0), } } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index eaacd2040..0d4ed6905 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -25,7 +25,7 @@ use pumpkin_protocol::bedrock::server::text::SText; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_world::chunk::{ChunkData, ChunkEntityData}; use pumpkin_world::inventory::Inventory; -use tokio::sync::{Mutex, RwLock}; +use tokio::sync::Mutex; use tokio::task::JoinHandle; use uuid::Uuid; @@ -297,7 +297,7 @@ impl ChunkManager { pub fn next_chunk(&mut self) -> Box<[SyncChunk]> { let mut chunk_size = self.chunk_queue.len().min(self.chunks_per_tick); - let mut chunks = Vec::>>::with_capacity(chunk_size); + let mut chunks = Vec::>::with_capacity(chunk_size); while chunk_size > 0 { chunks.push(self.chunk_queue.pop().unwrap().2); chunk_size -= 1; @@ -316,7 +316,7 @@ impl ChunkManager { pub fn next_entity(&mut self) -> Box<[SyncEntityChunk]> { let chunk_size = self.entity_chunk_queue.len().min(self.chunks_per_tick); - let chunks: Box<[Arc>]> = self + let chunks: Box<[Arc]> = self .entity_chunk_queue .drain(..chunk_size) .map(|(_, chunk)| chunk) @@ -1304,7 +1304,6 @@ impl Player { ClientPlatform::Java(java_client) => { java_client.send_packet_now(&CChunkBatchStart).await; for chunk in chunk_of_chunks { - let chunk = chunk.read().await; // 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 // player or something. @@ -1316,8 +1315,6 @@ impl Player { } ClientPlatform::Bedrock(bedrock_client) => { for chunk in chunk_of_chunks { - let chunk = chunk.read().await; - bedrock_client .send_game_packet(&CLevelChunk { dimension: 0, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 9b684be26..dcdc59fed 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -126,7 +126,6 @@ use rand::{RngExt, rng}; use scoreboard::Scoreboard; use time::LevelTime; use tokio::sync::Mutex; -use tokio::sync::RwLock; pub mod border; pub mod bossbar; @@ -272,20 +271,20 @@ impl World { if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { let old_chunk = old_chunk.to_vec2_i32(); let chunk = self.level.get_entity_chunk(old_chunk).await; - let mut chunk = chunk.write().await; chunk.mark_dirty(true); + let mut data = chunk.data.lock().await; if old_chunk == current_chunk_coordinate { - chunk.data.insert(uuid, nbt); + data.insert(uuid, nbt); return; } // 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 let chunk = self.level.get_entity_chunk(current_chunk_coordinate).await; - let mut chunk = chunk.write().await; - chunk.data.insert(uuid, nbt); + let mut data = chunk.data.lock().await; + data.insert(uuid, nbt); chunk.mark_dirty(true); } @@ -294,16 +293,13 @@ impl World { if let Some(old_chunk) = entity.first_loaded_chunk_position.load() { let old_chunk = old_chunk.to_vec2_i32(); let chunk = self.level.get_entity_chunk(old_chunk).await; - let mut chunk = chunk.write().await; chunk.mark_dirty(true); if old_chunk == current_chunk_coordinate { - chunk.data.remove(&entity.entity_uuid); - drop(chunk); + chunk.data.lock().await.remove(&entity.entity_uuid); } else { 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 - chunk.data.remove(&entity.entity_uuid); + chunk.data.lock().await.remove(&entity.entity_uuid); chunk.mark_dirty(true); } } @@ -791,7 +787,7 @@ impl World { ); // log::debug!("spawning list size {}", spawn_list.len()); - let mut spawning_chunks: Vec<(Vector2, Arc>)> = + let mut spawning_chunks: Vec<(Vector2, Arc)> = spawning_chunks_map.into_iter().collect(); spawning_chunks.shuffle(&mut rng()); @@ -1099,7 +1095,7 @@ impl World { pub async fn tick_spawning_chunk( self: &Arc, chunk_pos: Vector2, - chunk: &Arc>, + chunk: &Arc, spawn_list: &Vec<&'static MobCategory>, 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 random_pos = Vector3::new( chunk_pos.x << 4, - chunk.read().await.heightmap.get( + chunk.heightmap.lock().unwrap().get( MotionBlocking, chunk_pos.x << 4, chunk_pos.y << 4, @@ -1174,9 +1170,9 @@ impl World { let chunk_pos = Vector2::new(x >> 4, z >> 4); let chunk = self.level.get_chunk(chunk_pos).await; chunk - .read() - .await .heightmap + .lock() + .unwrap() .get(MotionBlocking, x, z, self.min_y) } @@ -2113,9 +2109,7 @@ impl World { let Some((chunk, _first_load)) = recv_result else { break; }; - let tmp_chunk = chunk.read().await; - let position = Vector2::new(tmp_chunk.x, tmp_chunk.z); - drop(tmp_chunk); + let position = Vector2::new(chunk.x, chunk.z); let chunk = if level.is_chunk_watched(&position) { chunk @@ -2126,7 +2120,7 @@ impl World { ); 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 { log::warn!("Entity has no ID"); continue; @@ -2151,22 +2145,21 @@ impl World { if let Some(old_chunk) = base_entity.first_loaded_chunk_position.load() { let old_chunk = old_chunk.to_vec2_i32(); let chunk = world.level.get_entity_chunk(old_chunk).await; - let mut chunk = chunk.write().await; chunk.mark_dirty(true); let base_entity = entity.get_entity(); let current_chunk_coordinate = base_entity.block_pos.load().chunk_position(); + let mut data = chunk.data.lock().await; if old_chunk == current_chunk_coordinate { - chunk.data.insert(*uuid, nbt); + data.insert(*uuid, nbt); return; } // 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.insert(*uuid, nbt); + chunk.data.lock().await.insert(*uuid, nbt); chunk.mark_dirty(true); } @@ -2188,11 +2181,10 @@ impl World { continue 'main; }; - let entity_chunk = chunk.read().await; // Add all new Entities to the world let mut entities_to_add: Vec> = 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 { log::warn!("Entity has no ID"); continue; @@ -2552,10 +2544,9 @@ impl World { let chunk_coordinate = base_entity.block_pos.load().chunk_position(); let chunk = self.level.get_entity_chunk(chunk_coordinate).await; { - let mut chunk = chunk.write().await; let mut nbt = NbtCompound::new(); 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); }; @@ -2727,10 +2718,7 @@ impl World { pub async fn get_block_light_level(&self, position: &BlockPos) -> Option { let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); 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; // +1 since block light has 1 section padding on both top and bottom 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( &self, - position: &BlockPos, - light_level: u8, + _position: &BlockPos, + _light_level: u8, ) -> Result<(), String> { - let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); - 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 section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; - if section_index >= chunk.light_engine.block_light.len() { - return Err("Invalid section index".to_string()); - } - let relative_y = (relative.y - chunk.section.min_y) as usize % BlockPalette::SIZE; - chunk.light_engine.block_light[section_index + 1].set( - relative.x as usize, - relative_y, - relative.z as usize, - light_level, - ); - chunk.mark_dirty(true); + // TODO + // let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); + // let chunk = self.level.get_chunk(chunk_coordinate).await; + + // let section_index = (relative.y - chunk.section.min_y) as usize / BlockPalette::SIZE; + // if section_index >= chunk.light_engine.block_light.len() { + // return Err("Invalid section index".to_string()); + // } + // let relative_y = (relative.y - chunk.section.min_y) as usize % BlockPalette::SIZE; + // // chunk.light_engine.block_light[section_index + 1].set( + // // relative.x as usize, + // // relative_y, + // // relative.z as usize, + // // light_level, + // // ); + // chunk.mark_dirty(true); Ok(()) } @@ -2780,11 +2766,7 @@ impl World { ) -> BlockStateId { let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); 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( relative.x as usize, relative.y, @@ -3326,15 +3308,12 @@ impl World { pub async fn get_block_entity(&self, block_pos: &BlockPos) -> Option> { let chunk = self.level.get_chunk(block_pos.chunk_position()).await; - let chunk: tokio::sync::RwLockReadGuard = chunk.read().await; - - chunk.block_entities.get(block_pos).cloned() + chunk.block_entities.lock().unwrap().get(block_pos).cloned() } pub async fn add_block_entity(&self, block_entity: Arc) { let block_pos = block_entity.get_position(); let chunk = self.level.get_chunk(block_pos.chunk_position()).await; - let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; let block_entity_nbt = block_entity.chunk_data_nbt(); if let Some(nbt) = &block_entity_nbt { @@ -3348,21 +3327,23 @@ impl World { .await; } - chunk.block_entities.insert(block_pos, block_entity); + chunk + .block_entities + .lock() + .unwrap() + .insert(block_pos, block_entity); chunk.mark_dirty(true); } pub async fn remove_block_entity(&self, block_pos: &BlockPos) { let chunk = self.level.get_chunk(block_pos.chunk_position()).await; - let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; - chunk.block_entities.remove(block_pos); + chunk.block_entities.lock().unwrap().remove(block_pos); chunk.mark_dirty(true); } pub async fn update_block_entity(&self, block_entity: &Arc) { let block_pos = block_entity.get_position(); let chunk = self.level.get_chunk(block_pos.chunk_position()).await; - let mut chunk: tokio::sync::RwLockWriteGuard = chunk.write().await; let block_entity_nbt = block_entity.chunk_data_nbt(); if let Some(nbt) = &block_entity_nbt { diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index 1190fcac0..4b4fadf70 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -26,7 +26,6 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; use std::fmt; use std::sync::Arc; -use tokio::sync::RwLock; use uuid::Uuid; const MAGIC_NUMBER: i32 = 17 * 17; @@ -316,14 +315,14 @@ pub fn get_filtered_spawning_categories( pub async fn spawn_for_chunk( world: &Arc, chunk_pos: Vector2, - chunk: &Arc>, + chunk: &Arc, spawn_state: &mut SpawnState, spawn_list: &Vec<&'static MobCategory>, ) { // debug!("spawn for chunk {:?}", chunk_pos); for category in spawn_list { 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 { spawn_category_for_position(category, world, random_pos, &chunk_pos, spawn_state) .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, chunk_pos: &Vector2, - chunk: &Arc>, + chunk: &Arc, ) -> BlockPos { let mut rng = Xoroshiro::from_seed(get_seed()); let x = (chunk_pos.x << 4) + rng.next_bounded_i32(16); let z = (chunk_pos.y << 4) + rng.next_bounded_i32(16); let temp_y = chunk - .read() - .await .heightmap + .lock() + .unwrap() .get(ChunkHeightmapType::WorldSurface, x, z, min_y) + 1; 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 mut chunk_lock = chunk_handle.write().await; + let mut data = chunk_handle.data.lock().await; 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| { let mut new_entities = (**current_entities).clone();