chore: refactor chunk reading/writing
This commit is contained in:
parent
5165d16c29
commit
3c87523112
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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<Bytes, ChunkSerializingError> {
|
||||
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::<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 {
|
||||
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<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]
|
||||
@ -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<Bytes, ChunkSerializingError> {
|
||||
async fn internal_to_bytes(&self) -> Result<Bytes, ChunkSerializingError> {
|
||||
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();
|
||||
|
||||
@ -148,7 +148,7 @@ where
|
||||
S: ChunkSerializer<Data = P, WriteBackend = PathBuf>,
|
||||
S::ChunkConfig: Send + Sync,
|
||||
{
|
||||
type Data = Arc<RwLock<S::Data>>;
|
||||
type Data = Arc<S::Data>;
|
||||
|
||||
fn watch_chunks<'a>(
|
||||
&'a self,
|
||||
@ -237,14 +237,13 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
// Intermediate channel for wrapping the data with the Arc<RwLock>
|
||||
let (send, mut recv) =
|
||||
mpsc::channel::<LoadedData<S::Data, ChunkReadingError>>(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 {
|
||||
|
||||
@ -35,7 +35,7 @@ impl<D: Send, E: error::Error> LoadedData<D, E> {
|
||||
|
||||
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<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
@ -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<ChunkHeightmaps>,
|
||||
pub x: i32,
|
||||
pub z: i32,
|
||||
pub block_ticks: ChunkTickScheduler<&'static Block>,
|
||||
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 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<uuid::Uuid, NbtCompound>,
|
||||
pub data: Mutex<FxHashMap<uuid::Uuid, NbtCompound>>,
|
||||
|
||||
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<Box<[SubChunk]>>,
|
||||
pub min_y: i32,
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ impl ChunkSections {
|
||||
pub fn dump_blocks(&self) -> Vec<u16> {
|
||||
// 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<u8> {
|
||||
// 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<Box<[SubChunk]>>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<K, V> = FxHashMap<K, V>;
|
||||
type HashSetType<K> = FxHashSet<K>;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<RwLock<ChunkData>>;
|
||||
pub type SyncEntityChunk = Arc<RwLock<ChunkEntityData>>;
|
||||
pub type SyncChunk = Arc<ChunkData>;
|
||||
pub type SyncEntityChunk = Arc<ChunkEntityData>;
|
||||
|
||||
/// 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::<u32>();
|
||||
|
||||
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<i32>) -> Option<Arc<RwLock<ChunkData>>> {
|
||||
pub fn try_get_chunk(&self, coordinates: &Vector2<i32>) -> Option<Arc<ChunkData>> {
|
||||
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<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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<T> {
|
||||
tick_queue: [Vec<OrderedTick<T>>; MAX_TICK_DELAY],
|
||||
queued_ticks: FxHashSet<(BlockPos, T)>,
|
||||
offset: usize,
|
||||
tick_queue: Mutex<[Vec<OrderedTick<T>>; MAX_TICK_DELAY]>,
|
||||
queued_ticks: Mutex<FxHashSet<(BlockPos, T)>>,
|
||||
offset: AtomicUsize,
|
||||
}
|
||||
|
||||
impl<'a, T: std::hash::Hash + Eq> ChunkTickScheduler<&'a T> {
|
||||
pub fn step_tick(&mut self) -> Vec<OrderedTick<&'a T>> {
|
||||
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<OrderedTick<&'a T>> {
|
||||
// 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<ScheduledTick<&'a T>> {
|
||||
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<ScheduledTick<&'a T>>
|
||||
for ChunkTickScheduler<&'a T>
|
||||
{
|
||||
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 (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<ScheduledTick<&'a T>>
|
||||
impl<T> Default for ChunkTickScheduler<T> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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::<Arc<RwLock<ChunkData>>>::with_capacity(chunk_size);
|
||||
let mut chunks = Vec::<Arc<ChunkData>>::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<RwLock<ChunkEntityData>>]> = self
|
||||
let chunks: Box<[Arc<ChunkEntityData>]> = 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,
|
||||
|
||||
@ -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<i32>, Arc<RwLock<ChunkData>>)> =
|
||||
let mut spawning_chunks: Vec<(Vector2<i32>, Arc<ChunkData>)> =
|
||||
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<Self>,
|
||||
chunk_pos: Vector2<i32>,
|
||||
chunk: &Arc<RwLock<ChunkData>>,
|
||||
chunk: &Arc<ChunkData>,
|
||||
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<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 {
|
||||
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<u8> {
|
||||
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<Arc<dyn BlockEntity>> {
|
||||
let chunk = self.level.get_chunk(block_pos.chunk_position()).await;
|
||||
let chunk: tokio::sync::RwLockReadGuard<ChunkData> = 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<dyn BlockEntity>) {
|
||||
let block_pos = block_entity.get_position();
|
||||
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();
|
||||
|
||||
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<ChunkData> = 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<dyn BlockEntity>) {
|
||||
let block_pos = block_entity.get_position();
|
||||
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();
|
||||
|
||||
if let Some(nbt) = &block_entity_nbt {
|
||||
|
||||
@ -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<World>,
|
||||
chunk_pos: Vector2<i32>,
|
||||
chunk: &Arc<RwLock<ChunkData>>,
|
||||
chunk: &Arc<ChunkData>,
|
||||
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<i32>,
|
||||
chunk: &Arc<RwLock<ChunkData>>,
|
||||
chunk: &Arc<ChunkData>,
|
||||
) -> 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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user