chore: this should speed things up

This commit is contained in:
Alexander Medvedev 2026-01-20 18:59:05 +01:00
parent d737b56cbc
commit afc4cb0cd3
21 changed files with 430 additions and 383 deletions

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::chunk::ChunkConfig; use crate::chunk::ChunkConfig;
#[derive(Deserialize, Serialize, Default)] #[derive(Deserialize, Serialize, Default, Clone)]
pub struct LevelConfig { pub struct LevelConfig {
pub chunk: ChunkConfig, pub chunk: ChunkConfig,
// TODO: More options // TODO: More options

View File

@ -428,12 +428,17 @@ impl PistonBehavior {
} }
impl BlockState { impl BlockState {
const IS_AIR: u16 = 1 << 0;
const HAS_RANDOM_TICKS: u16 = 1 << 9; const HAS_RANDOM_TICKS: u16 = 1 << 9;
fn has_random_ticks(&self) -> bool { fn has_random_ticks(&self) -> bool {
self.state_flags & Self::HAS_RANDOM_TICKS != 0 self.state_flags & Self::HAS_RANDOM_TICKS != 0
} }
pub const fn is_air(&self) -> bool {
self.state_flags & Self::IS_AIR != 0
}
fn to_tokens(&self) -> TokenStream { fn to_tokens(&self) -> TokenStream {
let mut tokens = TokenStream::new(); let mut tokens = TokenStream::new();
let id = LitInt::new(&self.id.to_string(), Span::call_site()); let id = LitInt::new(&self.id.to_string(), Span::call_site());
@ -659,6 +664,8 @@ pub(crate) fn build() -> TokenStream {
.collect(); .collect();
let mut random_tick_states = Vec::new(); let mut random_tick_states = Vec::new();
let mut air_states = Vec::new();
let mut constants_list = Vec::new(); let mut constants_list = Vec::new();
let mut block_from_name_entries = Vec::new(); let mut block_from_name_entries = Vec::new();
let mut block_from_item_id_arms = Vec::new(); let mut block_from_item_id_arms = Vec::new();
@ -679,6 +686,10 @@ pub(crate) fn build() -> TokenStream {
let state_id = LitInt::new(&state.id.to_string(), Span::call_site()); let state_id = LitInt::new(&state.id.to_string(), Span::call_site());
random_tick_states.push(state_id); random_tick_states.push(state_id);
} }
if state.is_air() {
let state_id = LitInt::new(&state.id.to_string(), Span::call_site());
air_states.push(state_id);
}
} }
let mut property_collection = HashSet::new(); let mut property_collection = HashSet::new();
@ -817,6 +828,8 @@ pub(crate) fn build() -> TokenStream {
.iter() .iter()
.map(|shape| shape.to_token_stream()); .map(|shape| shape.to_token_stream());
let air_state_ids = quote! { #(#air_states)|* };
let block_props = block_properties.iter().map(|prop| prop.to_token_stream()); let block_props = block_properties.iter().map(|prop| prop.to_token_stream());
let properties = property_enums.values().map(|prop| prop.to_token_stream()); let properties = property_enums.values().map(|prop| prop.to_token_stream());
@ -908,6 +921,11 @@ pub(crate) fn build() -> TokenStream {
#(#block_entity_types),* #(#block_entity_types),*
]; ];
#[inline(always)]
pub fn is_air(state_id: u16) -> bool {
matches!(state_id, #air_state_ids)
}
#[inline(always)] #[inline(always)]
pub fn has_random_ticks(state_id: u16) -> bool { pub fn has_random_ticks(state_id: u16) -> bool {
#mod_ident::#contains_ident(state_id) #mod_ident::#contains_ident(state_id)

View File

@ -77,6 +77,14 @@ pub(crate) fn build() -> TokenStream {
} }
.to_token_stream(); .to_token_stream();
let block_id_map: BTreeMap<String, u16> = blocks_assets
.blocks
.iter()
.map(|b| (b.name.clone(), b.id))
.collect();
let fluid_id_map: BTreeMap<String, u16> =
fluids.iter().map(|f| (f.name.clone(), f.id)).collect();
// Generate tag arrays for each registry key // Generate tag arrays for each registry key
let mut tag_dicts = Vec::new(); let mut tag_dicts = Vec::new();
let mut match_arms_value = Vec::new(); let mut match_arms_value = Vec::new();
@ -84,80 +92,48 @@ pub(crate) fn build() -> TokenStream {
let mut match_arms_tags_all = Vec::new(); let mut match_arms_tags_all = Vec::new();
let mut tag_identifiers = Vec::new(); let mut tag_identifiers = Vec::new();
for (key, tag_map) in tags.into_iter() { for (key, tag_map) in tags {
let key_pascal = format_ident!("{}", key.to_pascal_case()); let key_pascal = format_ident!("{}", key.to_pascal_case());
let dict_name = format_ident!("{}_TAGS", key.to_pascal_case().to_uppercase()); let dict_name = format_ident!("{}_TAGS", key.to_pascal_case().to_uppercase());
// Create a BTreeMap to store tag name -> index mapping let mut tag_entries = Vec::new();
let mut tag_values = Vec::new(); let mut tag_map_entries = Vec::new();
// Collect all unique tags
for (tag_name, values) in tag_map { for (tag_name, values) in tag_map {
tag_values.push((tag_name, values)); let ids: Vec<u16> = values
.iter()
.filter_map(|v| match key.as_str() {
"worldgen/biome" => biomes.get(v).map(|b| b.id as u16),
"fluid" => fluid_id_map.get(v).copied(),
"item" => items.get(v).map(|i| i.id),
"block" => block_id_map.get(v).copied(),
"enchantment" => enchantments
.get(&format!("minecraft:{}", v))
.map(|e| e.id as u16),
"entity_type" => entities.get(v).map(|e| e.id),
_ => None,
})
.collect();
let tag_const_name =
format_ident!("{}", tag_name.replace([':', '/'], "_").to_uppercase());
tag_entries.push(quote! {
pub const #tag_const_name: Tag = (&[#(#values),*], &[#(#ids),*]);
});
tag_map_entries.push(quote! {
#tag_name => &#key_pascal::#tag_const_name
});
} }
tag_values.sort();
// Generate the static array of tag values
let tag_array_entries = tag_values
.iter()
.map(|(tag_name, values)| {
let tag_values_array = values.iter().map(|v| quote! { #v }).collect::<Vec<_>>();
let tag_id_array = match &key {
t if t == "worldgen/biome" => values.iter().map(|v| {
let id = biomes.get(v).unwrap().id as u16;
quote! { #id }
}).collect::<Vec<_>>(),
t if t == "fluid" => values.iter().map(|v| {
let id = fluids.iter().find(|i| { &i.name == v }).unwrap().id;
quote! { #id }
}).collect::<Vec<_>>(),
t if t == "item" => values.iter().map(|v| {
let id = items.get(v).unwrap().id;
quote! { #id }
}).collect::<Vec<_>>(),
t if t == "block" => values.iter().map(|v| {
let id = blocks_assets.blocks.iter().find(|i| { &i.name == v }).unwrap().id;
quote! { #id }
}).collect::<Vec<_>>(),
t if t == "enchantment" => values.iter().map(|v| {
let id = enchantments.get(&("minecraft:".to_string() + v)).unwrap().id as u16;
quote! { #id }
}).collect::<Vec<_>>(),
t if t == "entity_type" => values.iter().map(|v| {
let id = entities.get(v).unwrap().id;
quote! { #id }
}).collect::<Vec<_>>(),
_ => Vec::new(),
};
let mapped_name = format_ident!("{}", tag_name.replace(":", "_").replace("/", "_").to_uppercase());
quote! {
pub const #mapped_name: Tag = (&[#(#tag_values_array),*], &[#(#tag_id_array),*]);
}
})
.collect::<Vec<_>>();
let tag_array_entries_map = tag_values
.iter()
.map(|(tag_name, _values)| {
let mapped_name = format_ident!(
"{}",
tag_name.replace(":", "_").replace("/", "_").to_uppercase()
);
quote! {
#tag_name => &#key_pascal::#mapped_name
}
})
.collect::<Vec<_>>();
// Add the static array declaration
tag_dicts.push(quote! { tag_dicts.push(quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub mod #key_pascal { pub mod #key_pascal {
use crate::tag::Tag; use super::Tag;
#(#tag_entries)*
#(#tag_array_entries)*
} }
static #dict_name: phf::Map<&str, &'static Tag> = phf::phf_map! { static #dict_name: phf::Map<&'static str, &'static Tag> = phf::phf_map! {
#(#tag_array_entries_map),* #(#tag_map_entries),*
}; };
}); });

View File

@ -27,6 +27,18 @@ pub struct Block {
pub experience: Option<Experience>, pub experience: Option<Experience>,
} }
impl PartialEq<u16> for Block {
fn eq(&self, other: &u16) -> bool {
self.id == *other
}
}
impl PartialEq<Block> for u16 {
fn eq(&self, other: &Block) -> bool {
*self == other.id
}
}
impl PartialEq for Block { impl PartialEq for Block {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id self.id == other.id

View File

@ -18,4 +18,9 @@ impl RawBlockState {
pub fn to_block(&self) -> &'static Block { pub fn to_block(&self) -> &'static Block {
Block::from_state_id(self.0) Block::from_state_id(self.0)
} }
#[inline]
pub fn to_block_id(&self) -> u16 {
Block::get_raw_id_from_state_id(self.0)
}
} }

View File

@ -1,7 +1,6 @@
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
io::ErrorKind, io::ErrorKind,
ops::{AddAssign, SubAssign},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
@ -162,62 +161,60 @@ where
{ {
type Data = Arc<RwLock<S::Data>>; type Data = Arc<RwLock<S::Data>>;
// Changed: Return BoxFuture<()>
fn watch_chunks<'a>( fn watch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>], chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> { ) -> BoxFuture<'a, ()> {
let paths: Vec<_> = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)))
.collect();
Box::pin(async move { Box::pin(async move {
// It is intentional that regions are watched multiple times (once per chunk)
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for chunk in chunks { for path in paths {
let key = S::get_chunk_key(chunk); watchers
let map_key = P::file_path(folder, &key); .entry(path)
match watchers.entry(map_key) { .and_modify(|count| *count += 1)
std::collections::btree_map::Entry::Vacant(vacant) => { .or_insert(1);
let _ = vacant.insert(1);
}
std::collections::btree_map::Entry::Occupied(mut occupied) => {
occupied.get_mut().add_assign(1);
}
}
} }
}) })
} }
// Changed: Return BoxFuture<()>
fn unwatch_chunks<'a>( fn unwatch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
chunks: &'a [Vector2<i32>], chunks: &'a [Vector2<i32>],
) -> BoxFuture<'a, ()> { ) -> BoxFuture<'a, ()> {
let paths: Vec<_> = chunks
.iter()
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)))
.collect();
Box::pin(async move { Box::pin(async move {
let mut watchers = self.watchers.write().await; let mut watchers = self.watchers.write().await;
for chunk in chunks {
let key = S::get_chunk_key(chunk); for path in paths {
let map_key = P::file_path(folder, &key); if let std::collections::btree_map::Entry::Occupied(mut occupied) =
match watchers.entry(map_key) { watchers.entry(path)
std::collections::btree_map::Entry::Vacant(_vacant) => {} {
std::collections::btree_map::Entry::Occupied(mut occupied) => { let count = occupied.get_mut();
occupied.get_mut().sub_assign(1); *count = count.saturating_sub(1);
if occupied.get().is_zero() {
occupied.remove_entry(); if *count == 0 {
} occupied.remove();
} }
} }
} }
}) })
} }
// Changed: Return BoxFuture<()>
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
self.watchers.write().await.clear(); self.watchers.write().await.clear();
}) })
} }
// Changed: Return BoxFuture<()>
fn fetch_chunks<'a>( fn fetch_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
@ -284,7 +281,6 @@ where
}) })
} }
// Changed: Return BoxFuture<Result<(), ChunkWritingError>>
fn save_chunks<'a>( fn save_chunks<'a>(
&'a self, &'a self,
folder: &'a LevelFolder, folder: &'a LevelFolder,
@ -374,8 +370,6 @@ where
// Decrement strong count // Decrement strong count
drop(chunk_serializer); drop(chunk_serializer);
// If there are still no watchers, drop from the locks
let mut locks = self.file_locks.write().await;
if self if self
.watchers .watchers
@ -384,6 +378,8 @@ where
.get(&path) .get(&path)
.is_none_or(|count| count.is_zero()) .is_none_or(|count| count.is_zero())
{ {
let mut locks = self.file_locks.write().await;
let can_remove = if let Some(loader) = locks.get(&path) { let can_remove = if let Some(loader) = locks.get(&path) {
loader.can_remove().await loader.can_remove().await
} else { } else {
@ -410,7 +406,6 @@ where
}) })
} }
// Changed: Return BoxFuture<()>
fn clean_up_log(&self) -> BoxFuture<'_, ()> { fn clean_up_log(&self) -> BoxFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
let locks = self.file_locks.read().await; let locks = self.file_locks.read().await;
@ -418,11 +413,10 @@ where
}) })
} }
// Changed: Return BoxFuture<()>
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> { fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
//we need to block any other operation //we need to block any other operation
let serializer_cache = self.file_locks.write().await; let serializer_cache = self.file_locks.read().await;
// Acquire a write lock on all entries to verify they are complete // Acquire a write lock on all entries to verify they are complete
let tasks = serializer_cache let tasks = serializer_cache

View File

@ -3,7 +3,7 @@ use crate::block::entities::BlockEntity;
use crate::chunk::format::LightContainer; use crate::chunk::format::LightContainer;
use crate::tick::scheduler::ChunkTickScheduler; use crate::tick::scheduler::ChunkTickScheduler;
use palette::{BiomePalette, BlockPalette}; use palette::{BiomePalette, BlockPalette};
use pumpkin_data::block_properties::blocks_movement; use pumpkin_data::block_properties::{blocks_movement, is_air};
use pumpkin_data::chunk::ChunkStatus; use pumpkin_data::chunk::ChunkStatus;
use pumpkin_data::fluid::Fluid; use pumpkin_data::fluid::Fluid;
use pumpkin_data::tag::Block::MINECRAFT_LEAVES; use pumpkin_data::tag::Block::MINECRAFT_LEAVES;
@ -438,7 +438,7 @@ impl ChunkSections {
let mut y = first_y; let mut y = first_y;
while y >= self.min_y { while y >= self.min_y {
if let Some(block_state_id) = self.get_block_absolute_y(relative_x, y, relative_z) if let Some(block_state_id) = self.get_block_absolute_y(relative_x, y, relative_z)
&& !BlockState::from_id(block_state_id).is_air() && !is_air(block_state_id)
{ {
return Some(y); return Some(y);
} }
@ -568,12 +568,14 @@ impl ChunkData {
} }
pub fn get_highest_non_empty_subchunk(&self) -> usize { pub fn get_highest_non_empty_subchunk(&self) -> usize {
for (i, sub_chunk) in self.section.sections.iter().enumerate().rev() { self.section
if sub_chunk.block_states.non_air_block_count() != 0 { .sections
return i; .iter()
} .enumerate()
} .rev()
0 .position(|(_, sub)| !sub.block_states.has_only_air())
.map(|p| self.section.sections.len() - 1 - p)
.unwrap_or(0)
} }
} }

View File

@ -1,6 +1,6 @@
use std::{collections::HashMap, hash::Hash}; use std::{collections::HashMap, hash::Hash};
use pumpkin_data::{Block, BlockState, chunk::Biome}; use pumpkin_data::{Block, BlockState, block_properties::is_air, chunk::Biome};
use pumpkin_util::encompassing_bits; use pumpkin_util::encompassing_bits;
use crate::block::BlockStateCodec; use crate::block::BlockStateCodec;
@ -475,10 +475,18 @@ impl BlockPalette {
} }
} }
/// Check if the entire chunk is filled with only air
pub fn has_only_air(&self) -> bool {
match self {
Self::Homogeneous(id) => is_air(*id),
Self::Heterogeneous(data) => data.palette.iter().all(|&id| is_air(id)),
}
}
pub fn non_air_block_count(&self) -> u16 { pub fn non_air_block_count(&self) -> u16 {
match self { match self {
Self::Homogeneous(registry_id) => { Self::Homogeneous(registry_id) => {
if !BlockState::from_id(*registry_id).is_air() { if !is_air(*registry_id) {
Self::VOLUME as u16 Self::VOLUME as u16
} else { } else {
0 0
@ -489,7 +497,7 @@ impl BlockPalette {
.iter() .iter()
.zip(data.counts.iter()) .zip(data.counts.iter())
.filter_map(|(registry_id, count)| { .filter_map(|(registry_id, count)| {
if !BlockState::from_id(*registry_id).is_air() { if !is_air(*registry_id) {
Some(*count) Some(*count)
} else { } else {
None None

View File

@ -9,6 +9,7 @@ TODO
use crate::block::RawBlockState; use crate::block::RawBlockState;
use crate::chunk::io::LoadedData::Loaded; use crate::chunk::io::LoadedData::Loaded;
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk}; use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk};
use pumpkin_data::block_properties::is_air;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use std::default::Default; use std::default::Default;
use std::pin::Pin; use std::pin::Pin;
@ -825,6 +826,7 @@ impl Chunk {
} }
} }
} }
let mut chunk = ChunkData { let mut chunk = ChunkData {
light_engine: ChunkLight { light_engine: ChunkLight {
sky_light: (0..sections.sections.len()) sky_light: (0..sections.sections.len())
@ -1140,9 +1142,7 @@ impl GenerationCache for Cache {
} }
fn is_air(&self, local_pos: &Vector3<i32>) -> bool { fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
GenerationCache::get_block_state(self, local_pos) is_air(GenerationCache::get_block_state(self, local_pos).0)
.to_state()
.is_air()
} }
} }

View File

@ -1,3 +1,4 @@
use pumpkin_data::block_properties::is_air;
use pumpkin_data::{Block, BlockDirection}; use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::{HeightMap, include_json_static}; use pumpkin_util::{HeightMap, include_json_static};
use serde::Deserialize; use serde::Deserialize;
@ -366,8 +367,8 @@ impl CountOnEveryLayerPlacementModifier {
} }
fn blocks_spawn(state: &RawBlockState) -> bool { fn blocks_spawn(state: &RawBlockState) -> bool {
let block = state.to_block(); let block = state.to_block_id();
state.to_state().is_air() || block == &Block::WATER || block == &Block::LAVA is_air(state.0) || block == Block::WATER || block == Block::LAVA
} }
} }

View File

@ -1,6 +1,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::pin::Pin; use std::pin::Pin;
use pumpkin_data::block_properties::is_air;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::{Fluid, FluidState}; use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::tag; use pumpkin_data::tag;
@ -430,8 +431,7 @@ impl ProtoChunk {
#[inline] #[inline]
pub fn is_air(&self, local_pos: &Vector3<i32>) -> bool { pub fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
let state = self.get_block_state(local_pos).to_state(); is_air(self.get_block_state(local_pos).0)
state.is_air()
} }
#[inline] #[inline]
@ -815,12 +815,10 @@ impl ProtoChunk {
let state = self let state = self
.get_block_state(&Vector3::new(local_x, search_y, local_z)) .get_block_state(&Vector3::new(local_x, search_y, local_z))
.to_block(); .to_block_id();
// TODO: Is there a better way to check that its not a fluid? // TODO: Is there a better way to check that its not a fluid?
if !(state != &AIR_BLOCK if !(state != AIR_BLOCK && state != WATER_BLOCK && state != LAVA_BLOCK)
&& state != &WATER_BLOCK
&& state != &LAVA_BLOCK)
{ {
min = search_y + 1; min = search_y + 1;
break; break;

View File

@ -152,12 +152,12 @@ impl SurfaceTerrainBuilder {
if surface_y <= elevation_y { if surface_y <= elevation_y {
for y in (chunk.bottom_y() as i32..=elevation_y).rev() { for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
let pos = Vector3::new(global_x, y, global_z); let pos = Vector3::new(global_x, y, global_z);
let block_state = chunk.get_block_state(&pos).to_block(); let block_state = chunk.get_block_state(&pos).to_block_id();
if block_state == Block::from_state_id(chunk.default_block.id) { if block_state == Block::get_raw_id_from_state_id(chunk.default_block.id) {
break; break;
} }
if block_state == &WATER_BLOCK { if block_state == WATER_BLOCK {
return; return;
} }
} }

View File

@ -13,7 +13,7 @@ use crate::{
world::BlockRegistryExt, world::BlockRegistryExt,
}; };
use crossbeam::channel::Sender; use crossbeam::channel::Sender;
use dashmap::{DashMap, Entry}; use dashmap::DashMap;
use log::trace; use log::trace;
use num_traits::Zero; use num_traits::Zero;
use pumpkin_config::{chunk::ChunkConfig, world::LevelConfig}; use pumpkin_config::{chunk::ChunkConfig, world::LevelConfig};
@ -35,7 +35,6 @@ use std::{
thread, thread,
}; };
// use tokio::runtime::Handle; // use tokio::runtime::Handle;
use tokio::time::Instant;
use tokio::{ use tokio::{
select, select,
sync::{ sync::{
@ -134,45 +133,40 @@ impl Level {
seed: i64, seed: i64,
dimension: Dimension, dimension: Dimension,
) -> Arc<Self> { ) -> Arc<Self> {
// If we are using an already existing world we want to read the seed from the level.dat, If not we want to check if there is a seed in the config, if not lets create a random one
let region_folder = root_folder.join("region"); let region_folder = root_folder.join("region");
if !region_folder.exists() {
std::fs::create_dir_all(&region_folder).expect("Failed to create Region folder");
}
let entities_folder = root_folder.join("entities"); let entities_folder = root_folder.join("entities");
if !entities_folder.exists() {
std::fs::create_dir_all(&region_folder).expect("Failed to create Entities folder"); std::fs::create_dir_all(&region_folder).expect("Failed to create Region folder");
} std::fs::create_dir_all(&entities_folder).expect("Failed to create Entities folder");
let level_folder = LevelFolder { let level_folder = LevelFolder {
root_folder, root_folder,
region_folder, region_folder,
entities_folder, entities_folder,
}; };
// TODO: Load info correctly based on world format type
let seed = Seed(seed as u64); let seed = Seed(seed as u64);
let world_gen = get_world_gen(seed, dimension).into(); let world_gen = get_world_gen(seed, dimension).into();
let chunk_saver: Arc<dyn FileIO<Data = SyncChunk>> = match &level_config.chunk { let chunk_saver: Arc<dyn FileIO<Data = SyncChunk>> = match &level_config.chunk {
ChunkConfig::Linear(chunk_config) => Arc::new( ChunkConfig::Linear(config) => Arc::new(
ChunkFileManager::<LinearFile<ChunkData>>::new(chunk_config.clone()), ChunkFileManager::<LinearFile<ChunkData>>::new(config.clone()),
),
ChunkConfig::Anvil(config) => Arc::new(
ChunkFileManager::<AnvilChunkFile<ChunkData>>::new(config.clone()),
), ),
ChunkConfig::Anvil(chunk_config) => Arc::new(ChunkFileManager::<
AnvilChunkFile<ChunkData>,
>::new(chunk_config.clone())),
}; };
let entity_saver: Arc<dyn FileIO<Data = SyncEntityChunk>> = match &level_config.chunk { let entity_saver: Arc<dyn FileIO<Data = SyncEntityChunk>> = match &level_config.chunk {
ChunkConfig::Linear(chunk_config) => Arc::new(ChunkFileManager::< ChunkConfig::Linear(config) => Arc::new(
LinearFile<ChunkEntityData>, ChunkFileManager::<LinearFile<ChunkEntityData>>::new(config.clone()),
>::new(chunk_config.clone())), ),
ChunkConfig::Anvil(chunk_config) => Arc::new(ChunkFileManager::< ChunkConfig::Anvil(config) => Arc::new(ChunkFileManager::<
AnvilChunkFile<ChunkEntityData>, AnvilChunkFile<ChunkEntityData>,
>::new(chunk_config.clone())), >::new(config.clone())),
}; };
let (gen_entity_request_tx, gen_entity_request_rx) = crossbeam::channel::unbounded(); let (gen_entity_request_tx, gen_entity_request_rx) = crossbeam::channel::unbounded();
let pending_entity_generations = Arc::new(DashMap::new()); let pending_entity_generations = Arc::new(DashMap::new());
let level_channel = Arc::new(LevelChannel::new()); let level_channel = Arc::new(LevelChannel::new());
let thread_tracker = Mutex::new(Vec::new()); let thread_tracker = Mutex::new(Vec::new());
let listener = Arc::new(ChunkListener::new()); let listener = Arc::new(ChunkListener::new());
@ -214,8 +208,6 @@ impl Level {
level_ref.thread_tracker.lock().unwrap().as_mut(), level_ref.thread_tracker.lock().unwrap().as_mut(),
); );
// let mut tracker = level_ref.thread_tracker.lock().unwrap();
// Entity Chunks
for thread_id in 0..(num_threads / 2).max(1) { for thread_id in 0..(num_threads / 2).max(1) {
let level_clone = level_ref.clone(); let level_clone = level_ref.clone();
let pending_clone = pending_entity_generations.clone(); let pending_clone = pending_entity_generations.clone();
@ -223,7 +215,7 @@ impl Level {
let builder = let builder =
thread::Builder::new().name(format!("Entity Chunk Generation Thread {thread_id}")); thread::Builder::new().name(format!("Entity Chunk Generation Thread {thread_id}"));
// tracker.push( TODO
builder builder
.spawn(move || { .spawn(move || {
while let Ok(pos) = rx.recv() { while let Ok(pos) = rx.recv() {
@ -231,11 +223,6 @@ impl Level {
break; break;
} }
// log::debug!(
// "Generating entity chunk {pos:?}, worker thread {thread_id:?}, queue length {}",
// rx.len()
// );
let chunk = ChunkEntityData { let chunk = ChunkEntityData {
x: pos.x, x: pos.x,
z: pos.y, z: pos.y,
@ -248,25 +235,15 @@ impl Level {
.loaded_entity_chunks .loaded_entity_chunks
.insert(pos, arc_chunk.clone()); .insert(pos, arc_chunk.clone());
if let Some(waiters) = pending_clone.remove(&pos) { if let Some((_, waiters)) = pending_clone.remove(&pos) {
for tx in waiters.1 { for tx in waiters {
let _ = tx.send(arc_chunk.clone()); let _ = tx.send(arc_chunk.clone());
} }
} }
} }
}) })
.unwrap(); .unwrap();
// );
} }
// drop(tracker);
// level_ref
// .chunk_loading
// .lock()
// .unwrap()
// .add_ticket(
// Vector2::<i32>::new(0, 0),
// ChunkLoading::FULL_CHUNK_LEVEL - 1,
// );
level_ref level_ref
} }
@ -385,21 +362,16 @@ impl Level {
/// before /// before
pub async fn mark_chunks_as_newly_watched(&self, chunks: &[Vector2<i32>]) { pub async fn mark_chunks_as_newly_watched(&self, chunks: &[Vector2<i32>]) {
for chunk in chunks { for chunk in chunks {
log::trace!("{chunk:?} marked as newly watched"); self.chunk_watchers
match self.chunk_watchers.entry(*chunk) { .entry(*chunk)
Entry::Occupied(mut occupied) => { .and_modify(|value| {
let value = occupied.get_mut();
if let Some(new_value) = value.checked_add(1) { if let Some(new_value) = value.checked_add(1) {
*value = new_value; *value = new_value;
//log::debug!("Watch value for {:?}: {}", chunk, value);
} else { } else {
log::error!("Watching overflow on chunk {chunk:?}"); log::error!("Watching overflow on chunk {:?}", chunk);
} }
} })
Entry::Vacant(vacant) => { .or_insert(1);
vacant.insert(1);
}
}
} }
// self.chunk_saver // self.chunk_saver
@ -416,22 +388,14 @@ impl Level {
let mut chunks_to_clean = Vec::new(); let mut chunks_to_clean = Vec::new();
for chunk in chunks { for chunk in chunks {
log::trace!("{chunk:?} marked as no longer watched"); if let Some(mut count) = self.chunk_watchers.get_mut(chunk) {
match self.chunk_watchers.entry(*chunk) { let value = count.value_mut();
Entry::Occupied(mut occupied) => { *value = value.saturating_sub(1);
let value = occupied.get_mut();
*value = value.saturating_sub(1);
if *value == 0 { if *value == 0 {
occupied.remove_entry(); drop(count);
chunks_to_clean.push(*chunk); self.chunk_watchers.remove(chunk);
} chunks_to_clean.push(*chunk);
}
Entry::Vacant(_) => {
// This can be:
// - Player disconnecting before all packets have been sent
// - Player moving so fast that the chunk leaves the render distance before it
// is loaded into memory
} }
} }
} }
@ -506,13 +470,10 @@ impl Level {
.iter() .iter()
.map(|x| x.value().clone()) .map(|x| x.value().clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for chunk in chunks { for chunk_sync in chunks {
let mut chunk = chunk.write().await; // Try to get a READ lock first.
ticks.block_ticks.append(&mut chunk.block_ticks.step_tick()); // Most chunks won't have pending ticks, so this is very fast.
ticks.fluid_ticks.append(&mut chunk.fluid_ticks.step_tick()); let chunk = chunk_sync.read().await;
let chunk = chunk.downgrade();
let chunk_x_base = chunk.x * 16; let chunk_x_base = chunk.x * 16;
let chunk_z_base = chunk.z * 16; let chunk_z_base = chunk.z * 16;
@ -544,21 +505,38 @@ impl Level {
} }
for section_data in section_blocks { for section_data in section_blocks {
for (random_pos, block_state_id) in section_data { ticks
if has_random_ticks(block_state_id) { .random_ticks
ticks.random_ticks.push(ScheduledTick { .extend(
position: random_pos, section_data
delay: 0, .into_iter()
priority: TickPriority::Normal, .filter_map(|(random_pos, block_state_id)| {
value: (), if has_random_ticks(block_state_id) {
}); Some(ScheduledTick {
} position: random_pos,
} delay: 0,
priority: TickPriority::Normal,
value: (),
})
} else {
None
}
}),
);
} }
ticks ticks
.block_entities .block_entities
.extend(chunk.block_entities.values().cloned()); .extend(chunk.block_entities.values().cloned());
drop(chunk);
let mut chunk_write = chunk_sync.write().await;
ticks
.block_ticks
.append(&mut chunk_write.block_ticks.step_tick());
ticks
.fluid_ticks
.append(&mut chunk_write.fluid_ticks.step_tick());
} }
ticks.block_ticks.sort_unstable(); ticks.block_ticks.sort_unstable();
@ -603,8 +581,6 @@ impl Level {
return chunk.clone(); return chunk.clone();
} }
log::debug!("Missing Chunk {pos:?}. Fetching.");
let clock = Instant::now();
let recv = self.chunk_listener.add_single_chunk_listener(pos); let recv = self.chunk_listener.add_single_chunk_listener(pos);
{ {
@ -613,16 +589,12 @@ impl Level {
lock.send_change(); lock.send_change();
} }
let ret = if let Some(chunk) = self.loaded_chunks.get(&pos) { if let Some(chunk) = self.loaded_chunks.get(&pos) {
chunk.clone() chunk.clone()
} else { } else {
recv.await recv.await
.expect("Chunk listener dropped without sending chunk") .expect("Chunk listener dropped without sending chunk")
}; }
log::debug!("Chunk {pos:?} received after {:?}.", Instant::now() - clock);
ret
} }
async fn load_single_entity_chunk( async fn load_single_entity_chunk(
@ -846,6 +818,7 @@ impl Level {
priority: TickPriority, priority: TickPriority,
) { ) {
let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = self.get_chunk(block_pos.chunk_position()).await;
let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed);
let mut chunk = chunk.write().await; let mut chunk = chunk.write().await;
chunk.block_ticks.schedule_tick( chunk.block_ticks.schedule_tick(
&ScheduledTick { &ScheduledTick {
@ -854,9 +827,8 @@ impl Level {
priority, priority,
value: unsafe { &*(block as *const Block) }, value: unsafe { &*(block as *const Block) },
}, },
self.schedule_tick_counts.load(Ordering::Relaxed), tick_order,
); );
self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed);
} }
pub async fn schedule_fluid_tick( pub async fn schedule_fluid_tick(
@ -867,6 +839,7 @@ impl Level {
priority: TickPriority, priority: TickPriority,
) { ) {
let chunk = self.get_chunk(block_pos.chunk_position()).await; let chunk = self.get_chunk(block_pos.chunk_position()).await;
let tick_order = self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed);
let mut chunk = chunk.write().await; let mut chunk = chunk.write().await;
chunk.fluid_ticks.schedule_tick( chunk.fluid_ticks.schedule_tick(
&ScheduledTick { &ScheduledTick {
@ -875,9 +848,8 @@ impl Level {
priority, priority,
value: unsafe { &*(fluid as *const Fluid) }, value: unsafe { &*(fluid as *const Fluid) },
}, },
self.schedule_tick_counts.load(Ordering::Relaxed), tick_order,
); );
self.schedule_tick_counts.fetch_add(1, Ordering::Relaxed);
} }
pub async fn is_block_tick_scheduled( pub async fn is_block_tick_scheduled(

View File

@ -1,6 +1,7 @@
use crate::block::{ use crate::block::{
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs, BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
}; };
use pumpkin_data::block_properties::is_air;
use pumpkin_data::tag::{RegistryKey, get_tag_values}; use pumpkin_data::tag::{RegistryKey, get_tag_values};
use pumpkin_macros::{pumpkin_block, pumpkin_block_from_tag}; use pumpkin_macros::{pumpkin_block, pumpkin_block_from_tag};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -108,8 +109,5 @@ impl BlockBehaviour for PaleMossCarpetBlock {
} }
async fn can_place_at(block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool { async fn can_place_at(block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool {
!block_accessor !is_air(block_accessor.get_block_state_id(&block_pos.down()).await)
.get_block_state(&block_pos.down())
.await
.is_air()
} }

View File

@ -206,23 +206,6 @@ impl ChunkManager {
let view_distance_i32 = i32::from(view_distance); let view_distance_i32 = i32::from(view_distance);
let mut chunks_to_watch = Vec::new();
let mut chunks_to_unwatch = Vec::new();
for pos in &self.chunk_sent {
if (pos.x - center.x).abs().max((pos.y - center.y).abs()) > view_distance_i32 {
chunks_to_unwatch.push(*pos);
}
}
let level_clone = level.clone();
let chunks_to_unwatch_clone = chunks_to_unwatch.clone();
tokio::spawn(async move {
level_clone
.mark_chunks_as_not_watched(&chunks_to_unwatch_clone)
.await;
});
self.chunk_sent.retain(|pos| { self.chunk_sent.retain(|pos| {
(pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance_i32 (pos.x - center.x).abs().max((pos.y - center.y).abs()) <= view_distance_i32
}); });
@ -242,20 +225,13 @@ impl ChunkManager {
for dx in (-view_distance_i32)..=view_distance_i32 { for dx in (-view_distance_i32)..=view_distance_i32 {
for dy in (-view_distance_i32)..=view_distance_i32 { for dy in (-view_distance_i32)..=view_distance_i32 {
let new_pos = center.add_raw(dx, dy); let new_pos = center.add_raw(dx, dy);
if !self.chunk_sent.contains(&new_pos) { if !self.chunk_sent.contains(&new_pos)
chunks_to_watch.push(new_pos); && let Some(chunk) = level.loaded_chunks.get(&new_pos)
if let Some(chunk) = level.loaded_chunks.get(&new_pos) { {
self.push_chunk(new_pos, chunk.value().clone()); self.push_chunk(new_pos, chunk.value().clone());
}
} }
} }
} }
let level_clone = level.clone();
tokio::spawn(async move {
level_clone
.mark_chunks_as_newly_watched(&chunks_to_watch)
.await;
});
} }
pub fn clean_up(&mut self, level: &Arc<Level>) { pub fn clean_up(&mut self, level: &Arc<Level>) {

View File

@ -20,7 +20,7 @@ use rustyline::Editor;
use rustyline::history::FileHistory; use rustyline::history::FileHistory;
use rustyline::{Config, error::ReadlineError}; use rustyline::{Config, error::ReadlineError};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{Cursor, IsTerminal, stdin}; use std::io::{Cursor, ErrorKind, IsTerminal, stdin};
use std::str::FromStr; use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
@ -222,10 +222,36 @@ impl PumpkinServer {
let mut tcp_listener = None; let mut tcp_listener = None;
if server.basic_config.java_edition { if server.basic_config.java_edition {
let address = server.basic_config.java_edition_address;
// Setup the TCP server socket. // Setup the TCP server socket.
let listener = tokio::net::TcpListener::bind(server.basic_config.java_edition_address) let listener = match TcpListener::bind(address).await {
.await Ok(l) => l,
.expect("Failed to start `TcpListener`"); Err(e) => match e.kind() {
ErrorKind::AddrInUse => {
log::error!("Error: Address {} is already in use.", address);
log::error!(
"Make sure another instance of the server isn't already running"
);
std::process::exit(1);
}
ErrorKind::PermissionDenied => {
log::error!("Error: Permission denied when binding to {}.", address);
log::error!("You might need sudo/admin privileges to use ports below 1024");
std::process::exit(1);
}
ErrorKind::AddrNotAvailable => {
log::error!(
"Error: The address {} is not available on this machine",
address
);
std::process::exit(1);
}
_ => {
log::error!("Failed to start TcpListener on {}: {}", address, e);
std::process::exit(1);
}
},
};
// In the event the user puts 0 for their port, this will allow us to know what port it is running on // In the event the user puts 0 for their port, this will allow us to know what port it is running on
let addr = listener let addr = listener
.local_addr() .local_addr()

View File

@ -90,7 +90,9 @@ impl JavaClient {
let verify_token: [u8; 4] = rand::random(); let verify_token: [u8; 4] = rand::random();
// Wait until we have sent the encryption packet to the client // Wait until we have sent the encryption packet to the client
self.send_packet_now( self.send_packet_now(
&server.encryption_request(&verify_token, server.basic_config.online_mode), &server
.encryption_request(&verify_token, server.basic_config.online_mode)
.await,
) )
.await; .await;
} else { } else {
@ -107,7 +109,10 @@ impl JavaClient {
encryption_response: SEncryptionResponse, encryption_response: SEncryptionResponse,
) { ) {
log::debug!("Handling encryption"); log::debug!("Handling encryption");
let shared_secret = server.decrypt(&encryption_response.shared_secret).unwrap(); let shared_secret = server
.decrypt(&encryption_response.shared_secret)
.await
.unwrap();
if let Err(error) = self.set_encryption(&shared_secret).await { if let Err(error) = self.set_encryption(&shared_secret).await {
self.kick(TextComponent::text(error.to_string())).await; self.kick(TextComponent::text(error.to_string())).await;
@ -207,7 +212,7 @@ impl JavaClient {
shared_secret: &[u8], shared_secret: &[u8],
username: &str, username: &str,
) -> Result<GameProfile, AuthError> { ) -> Result<GameProfile, AuthError> {
let hash = server.digest_secret(shared_secret); let hash = server.digest_secret(shared_secret).await;
let ip = self.address.lock().await.ip(); let ip = self.address.lock().await.ip();
let profile = authentication::authenticate( let profile = authentication::authenticate(
username, username,

View File

@ -1,3 +1,5 @@
use std::time::Instant;
use num_bigint::BigInt; use num_bigint::BigInt;
use pkcs8::EncodePublicKey; use pkcs8::EncodePublicKey;
use pumpkin_protocol::java::client::login::CEncryptionRequest; use pumpkin_protocol::java::client::login::CEncryptionRequest;
@ -15,6 +17,7 @@ pub struct KeyStore {
impl KeyStore { impl KeyStore {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
let instant = Instant::now();
log::debug!("Creating encryption keys..."); log::debug!("Creating encryption keys...");
let private_key = Self::generate_private_key(); let private_key = Self::generate_private_key();
@ -27,6 +30,8 @@ impl KeyStore {
.to_vec() .to_vec()
.into_boxed_slice(); .into_boxed_slice();
log::debug!("Created RSA keys, took {}ms", instant.elapsed().as_millis());
Self { Self {
private_key, private_key,
public_key_der, public_key_der,

View File

@ -39,7 +39,7 @@ use std::net::IpAddr;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32}; use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32};
use std::{future::Future, sync::atomic::Ordering, time::Duration}; use std::{future::Future, sync::atomic::Ordering, time::Duration};
use tokio::sync::{Mutex, RwLock}; use tokio::sync::{Mutex, OnceCell, RwLock};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tokio_util::task::TaskTracker; use tokio_util::task::TaskTracker;
@ -59,7 +59,7 @@ pub struct Server {
pub advanced_config: AdvancedConfiguration, pub advanced_config: AdvancedConfiguration,
/// Handles cryptographic keys for secure communication. /// Handles cryptographic keys for secure communication.
key_store: KeyStore, key_store: OnceCell<Arc<KeyStore>>,
/// Manages server status information. /// Manages server status information.
listing: Mutex<CachedStatus>, listing: Mutex<CachedStatus>,
/// Saves server branding information. /// Saves server branding information.
@ -190,7 +190,7 @@ impl Server {
command_dispatcher, command_dispatcher,
block_registry: block_registry.clone(), block_registry: block_registry.clone(),
item_registry: super::item::items::default_registry(), item_registry: super::item::items::default_registry(),
key_store: KeyStore::new(), key_store: OnceCell::new(),
listing, listing,
branding: CachedBranding::new(), branding: CachedBranding::new(),
bossbars: Mutex::new(CustomBossbars::new()), bossbars: Mutex::new(CustomBossbars::new()),
@ -208,59 +208,86 @@ impl Server {
level_info: level_info.clone(), level_info: level_info.clone(),
_locker: Arc::new(locker), _locker: Arc::new(locker),
}; };
let server = Arc::new(server); let server = Arc::new(server);
let weak = Arc::downgrade(&server); let level_config = Arc::new(server.advanced_config.world.clone());
let level_config = &server.advanced_config.world;
let server_clone = server.clone();
tokio::spawn(async move {
server_clone
.key_store
.get_or_init(|| async { Arc::new(KeyStore::new()) })
.await;
});
let weak_server = Arc::downgrade(&server);
log::info!("Loading Overworld: {seed}"); log::info!("Loading Overworld: {seed}");
let overworld = World::load( let overworld_task = tokio::task::spawn_blocking({
into_level( let path = world_path.clone();
Dimension::OVERWORLD, let registry = block_registry.clone();
level_config, let level_info = level_info.clone();
world_path.clone(), let weak = weak_server.clone();
block_registry.clone(), let config = level_config.clone();
seed, move || {
), World::load(
level_info.clone(), into_level(Dimension::OVERWORLD, &config, path, registry.clone(), seed),
Dimension::OVERWORLD, level_info,
block_registry.clone(), Dimension::OVERWORLD,
weak.clone(), registry,
); weak,
log::info!("Loading Nether: {seed}"); )
let nether = World::load( }
into_level( });
Dimension::THE_NETHER,
level_config, let nether_task = tokio::task::spawn_blocking({
world_path.clone(), let path = world_path.clone();
block_registry.clone(), let registry = block_registry.clone();
seed, let level_info = level_info.clone();
), let weak = weak_server.clone();
level_info.clone(), let config = level_config.clone();
Dimension::THE_NETHER, move || {
block_registry.clone(), World::load(
weak.clone(), into_level(Dimension::THE_NETHER, &config, path, registry.clone(), seed),
); level_info,
log::info!("Loading End: {seed}"); Dimension::THE_NETHER,
let end = World::load( registry,
into_level( weak,
Dimension::THE_END, )
level_config, }
world_path, });
block_registry.clone(),
seed, let end_task = tokio::task::spawn_blocking({
), let path = world_path.clone();
level_info, let registry = block_registry.clone();
Dimension::THE_END, let level_info = level_info.clone();
block_registry, let weak = weak_server.clone();
weak, let config = level_config.clone();
); move || {
*server World::load(
.worlds into_level(Dimension::THE_END, &config, path, registry.clone(), seed),
.try_write() level_info,
.expect("Nothing should hold a lock of worlds before server startup") = Dimension::THE_END,
// vec![overworld.into()]; registry,
vec![overworld.into(), nether.into(), end.into()]; weak,
)
}
});
let (overworld_res, nether_res, end_res) =
tokio::join!(overworld_task, nether_task, end_task);
let overworld = overworld_res.expect("Overworld load panicked");
let nether = nether_res.expect("Nether load panicked");
let end = end_res.expect("End load panicked");
{
let mut worlds = server.worlds.write().await;
worlds.push(overworld.into());
worlds.push(nether.into());
worlds.push(end.into());
};
log::info!("All worlds loaded successfully.");
server server
} }
@ -623,21 +650,29 @@ impl Server {
&self.listing &self.listing
} }
pub fn encryption_request<'a>( pub async fn encryption_request<'a>(
&'a self, &'a self,
verification_token: &'a [u8; 4], verification_token: &'a [u8; 4],
should_authenticate: bool, should_authenticate: bool,
) -> CEncryptionRequest<'a> { ) -> CEncryptionRequest<'a> {
self.key_store self.key_store
.get_or_init(|| async { Arc::new(KeyStore::new()) })
.await
.encryption_request("", verification_token, should_authenticate) .encryption_request("", verification_token, should_authenticate)
} }
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> { pub async fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
self.key_store.decrypt(data) self.key_store
.get_or_init(|| async { Arc::new(KeyStore::new()) })
.await
.decrypt(data)
} }
pub fn digest_secret(&self, secret: &[u8]) -> String { pub async fn digest_secret(&self, secret: &[u8]) -> String {
self.key_store.get_digest(secret) self.key_store
.get_or_init(|| async { Arc::new(KeyStore::new()) })
.await
.get_digest(secret)
} }
/// Main server tick method. This now handles both player/network ticking (which always runs) /// Main server tick method. This now handles both player/network ticking (which always runs)
@ -711,6 +746,22 @@ impl Server {
self.aggregated_tick_times_nanos.load(Ordering::Relaxed) / sample_size as i64 self.aggregated_tick_times_nanos.load(Ordering::Relaxed) / sample_size as i64
} }
/// Returns the average Milliseconds Per Tick (MSPT).
pub fn get_mspt(&self) -> f64 {
let avg_nanos = self.get_average_tick_time_nanos();
// Convert nanoseconds to decimal milliseconds
avg_nanos as f64 / 1_000_000.0
}
/// Returns the Ticks Per Second (TPS).
pub fn get_tps(&self) -> f64 {
let mspt = self.get_mspt();
if mspt <= 0.0 {
return 0.0;
}
1000.0 / mspt
}
/// Returns a copy of the last 100 tick times. /// Returns a copy of the last 100 tick times.
pub async fn get_tick_times_nanos_copy(&self) -> [i64; 100] { pub async fn get_tick_times_nanos_copy(&self) -> [i64; 100] {
*self.tick_times_nanos.lock().await *self.tick_times_nanos.lock().await

View File

@ -22,74 +22,77 @@ pub async fn get_view_distance(player: &Player) -> NonZeroU8 {
pub async fn update_position(player: &Arc<Player>) { pub async fn update_position(player: &Arc<Player>) {
let entity = &player.living_entity.entity; let entity = &player.living_entity.entity;
let new_chunk_center = entity.chunk_pos.load();
let old_cylindrical = player.watched_section.load();
if old_cylindrical.center == new_chunk_center {
return;
}
let view_distance = get_view_distance(player).await; let view_distance = get_view_distance(player).await;
let new_chunk_center = entity.chunk_pos.load();
let old_cylindrical = player.watched_section.load();
let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance); let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance);
if old_cylindrical != new_cylindrical { if old_cylindrical == new_cylindrical {
match &player.client { return;
ClientPlatform::Java(java_client) => { }
java_client
.send_packet_now(&CCenterChunk { match &player.client {
chunk_x: new_chunk_center.x.into(), ClientPlatform::Java(java_client) => {
chunk_z: new_chunk_center.y.into(), java_client
}) .send_packet_now(&CCenterChunk {
.await; chunk_x: new_chunk_center.x.into(),
} chunk_z: new_chunk_center.y.into(),
ClientPlatform::Bedrock(bedrock_client) => { })
bedrock_client .await;
.send_game_packet(&CNetworkChunkPublisherUpdate::new(
player.get_entity().block_pos.load(),
u32::from(view_distance.get()) * 16,
))
.await;
}
} }
let mut loading_chunks = Vec::new(); ClientPlatform::Bedrock(bedrock_client) => {
let mut unloading_chunks = Vec::new(); bedrock_client
Cylindrical::for_each_changed_chunk( .send_game_packet(&CNetworkChunkPublisherUpdate::new(
old_cylindrical, player.get_entity().block_pos.load(),
new_cylindrical, u32::from(view_distance.get()) * 16,
&mut loading_chunks, ))
&mut unloading_chunks, .await;
);
// Make sure the watched section and the chunk watcher updates are async atomic. We want to
// ensure what we unload when the player disconnects is correct.
let level = &entity.world.level;
level.mark_chunks_as_newly_watched(&loading_chunks).await;
let chunks_to_clean = level.mark_chunks_as_not_watched(&unloading_chunks).await;
{
let mut chunk_manager = player.chunk_manager.lock().await;
chunk_manager.update_center_and_view_distance(
new_chunk_center,
view_distance.into(),
level,
);
};
player.watched_section.store(new_cylindrical);
if !chunks_to_clean.is_empty() {
// level.clean_chunks(&chunks_to_clean).await;
for chunk in unloading_chunks {
player
.client
.enqueue_packet(&CUnloadChunk::new(chunk.x, chunk.y))
.await;
}
}
if !loading_chunks.is_empty() {
entity.world.spawn_world_entity_chunks(
player.clone(),
loading_chunks,
new_chunk_center,
);
} }
} }
let mut loading_chunks = Vec::new();
let mut unloading_chunks = Vec::new();
Cylindrical::for_each_changed_chunk(
old_cylindrical,
new_cylindrical,
&mut loading_chunks,
&mut unloading_chunks,
);
let level = &entity.world.level;
{
let mut chunk_manager = player.chunk_manager.lock().await;
chunk_manager.update_center_and_view_distance(
new_chunk_center,
view_distance.into(),
level,
);
};
player.watched_section.store(new_cylindrical);
// Make sure the watched section and the chunk watcher updates are async atomic. We want to
// ensure what we unload when the player disconnects is correct.
level.mark_chunks_as_newly_watched(&loading_chunks).await;
level.mark_chunks_as_not_watched(&unloading_chunks).await;
if let ClientPlatform::Java(_) = &player.client {
for chunk in &unloading_chunks {
player
.client
.enqueue_packet(&CUnloadChunk::new(chunk.x, chunk.y))
.await;
}
}
if !loading_chunks.is_empty() {
entity
.world
.spawn_world_entity_chunks(player.clone(), loading_chunks, new_chunk_center);
}
} }

View File

@ -38,6 +38,7 @@ use bytes::BufMut;
use crossbeam::queue::SegQueue; use crossbeam::queue::SegQueue;
use explosion::Explosion; use explosion::Explosion;
use pumpkin_config::BasicConfiguration; use pumpkin_config::BasicConfiguration;
use pumpkin_data::block_properties::is_air;
use pumpkin_data::data_component_impl::EquipmentSlot; use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_data::entity::MobCategory; use pumpkin_data::entity::MobCategory;
@ -1874,10 +1875,6 @@ impl World {
chunks: Vec<Vector2<i32>>, chunks: Vec<Vector2<i32>>,
center_chunk: Vector2<i32>, center_chunk: Vector2<i32>,
) { ) {
if player.client.closed() {
log::info!("The connection has closed before world chunks were spawned");
return;
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let inst = std::time::Instant::now(); let inst = std::time::Instant::now();
@ -3054,7 +3051,7 @@ impl World {
if new_state_id != block_state_id { if new_state_id != block_state_id {
let flags = flags & !BlockFlags::SKIP_DROPS; let flags = flags & !BlockFlags::SKIP_DROPS;
if BlockState::from_id(new_state_id).is_air() { if is_air(new_state_id) {
self.break_block(block_pos, None, flags).await; self.break_block(block_pos, None, flags).await;
} else { } else {
self.set_block_state(block_pos, new_state_id, flags).await; self.set_block_state(block_pos, new_state_id, flags).await;