mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
chore: this should speed things up
This commit is contained in:
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::chunk::ChunkConfig;
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
#[derive(Deserialize, Serialize, Default, Clone)]
|
||||
pub struct LevelConfig {
|
||||
pub chunk: ChunkConfig,
|
||||
// TODO: More options
|
||||
|
||||
@@ -428,12 +428,17 @@ impl PistonBehavior {
|
||||
}
|
||||
|
||||
impl BlockState {
|
||||
const IS_AIR: u16 = 1 << 0;
|
||||
const HAS_RANDOM_TICKS: u16 = 1 << 9;
|
||||
|
||||
fn has_random_ticks(&self) -> bool {
|
||||
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 {
|
||||
let mut tokens = TokenStream::new();
|
||||
let id = LitInt::new(&self.id.to_string(), Span::call_site());
|
||||
@@ -659,6 +664,8 @@ pub(crate) fn build() -> TokenStream {
|
||||
.collect();
|
||||
|
||||
let mut random_tick_states = Vec::new();
|
||||
let mut air_states = Vec::new();
|
||||
|
||||
let mut constants_list = Vec::new();
|
||||
let mut block_from_name_entries = 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());
|
||||
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();
|
||||
@@ -817,6 +828,8 @@ pub(crate) fn build() -> TokenStream {
|
||||
.iter()
|
||||
.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 properties = property_enums.values().map(|prop| prop.to_token_stream());
|
||||
|
||||
@@ -908,6 +921,11 @@ pub(crate) fn build() -> TokenStream {
|
||||
#(#block_entity_types),*
|
||||
];
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_air(state_id: u16) -> bool {
|
||||
matches!(state_id, #air_state_ids)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn has_random_ticks(state_id: u16) -> bool {
|
||||
#mod_ident::#contains_ident(state_id)
|
||||
|
||||
@@ -77,6 +77,14 @@ pub(crate) fn build() -> TokenStream {
|
||||
}
|
||||
.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
|
||||
let mut tag_dicts = 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 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 dict_name = format_ident!("{}_TAGS", key.to_pascal_case().to_uppercase());
|
||||
|
||||
// Create a BTreeMap to store tag name -> index mapping
|
||||
let mut tag_values = Vec::new();
|
||||
let mut tag_entries = Vec::new();
|
||||
let mut tag_map_entries = Vec::new();
|
||||
|
||||
// Collect all unique tags
|
||||
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! {
|
||||
#[allow(non_snake_case)]
|
||||
pub mod #key_pascal {
|
||||
use crate::tag::Tag;
|
||||
|
||||
#(#tag_array_entries)*
|
||||
use super::Tag;
|
||||
#(#tag_entries)*
|
||||
}
|
||||
static #dict_name: phf::Map<&str, &'static Tag> = phf::phf_map! {
|
||||
#(#tag_array_entries_map),*
|
||||
static #dict_name: phf::Map<&'static str, &'static Tag> = phf::phf_map! {
|
||||
#(#tag_map_entries),*
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -27,6 +27,18 @@ pub struct Block {
|
||||
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 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
|
||||
@@ -18,4 +18,9 @@ impl RawBlockState {
|
||||
pub fn to_block(&self) -> &'static Block {
|
||||
Block::from_state_id(self.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_block_id(&self) -> u16 {
|
||||
Block::get_raw_id_from_state_id(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::ErrorKind,
|
||||
ops::{AddAssign, SubAssign},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -162,62 +161,60 @@ where
|
||||
{
|
||||
type Data = Arc<RwLock<S::Data>>;
|
||||
|
||||
// Changed: Return BoxFuture<()>
|
||||
fn watch_chunks<'a>(
|
||||
&'a self,
|
||||
folder: &'a LevelFolder,
|
||||
chunks: &'a [Vector2<i32>],
|
||||
) -> BoxFuture<'a, ()> {
|
||||
let paths: Vec<_> = chunks
|
||||
.iter()
|
||||
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)))
|
||||
.collect();
|
||||
|
||||
Box::pin(async move {
|
||||
// It is intentional that regions are watched multiple times (once per chunk)
|
||||
let mut watchers = self.watchers.write().await;
|
||||
for chunk in chunks {
|
||||
let key = S::get_chunk_key(chunk);
|
||||
let map_key = P::file_path(folder, &key);
|
||||
match watchers.entry(map_key) {
|
||||
std::collections::btree_map::Entry::Vacant(vacant) => {
|
||||
let _ = vacant.insert(1);
|
||||
}
|
||||
std::collections::btree_map::Entry::Occupied(mut occupied) => {
|
||||
occupied.get_mut().add_assign(1);
|
||||
}
|
||||
}
|
||||
for path in paths {
|
||||
watchers
|
||||
.entry(path)
|
||||
.and_modify(|count| *count += 1)
|
||||
.or_insert(1);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Changed: Return BoxFuture<()>
|
||||
fn unwatch_chunks<'a>(
|
||||
&'a self,
|
||||
folder: &'a LevelFolder,
|
||||
chunks: &'a [Vector2<i32>],
|
||||
) -> BoxFuture<'a, ()> {
|
||||
let paths: Vec<_> = chunks
|
||||
.iter()
|
||||
.map(|chunk| P::file_path(folder, &S::get_chunk_key(chunk)))
|
||||
.collect();
|
||||
Box::pin(async move {
|
||||
let mut watchers = self.watchers.write().await;
|
||||
for chunk in chunks {
|
||||
let key = S::get_chunk_key(chunk);
|
||||
let map_key = P::file_path(folder, &key);
|
||||
match watchers.entry(map_key) {
|
||||
std::collections::btree_map::Entry::Vacant(_vacant) => {}
|
||||
std::collections::btree_map::Entry::Occupied(mut occupied) => {
|
||||
occupied.get_mut().sub_assign(1);
|
||||
if occupied.get().is_zero() {
|
||||
occupied.remove_entry();
|
||||
}
|
||||
|
||||
for path in paths {
|
||||
if let std::collections::btree_map::Entry::Occupied(mut occupied) =
|
||||
watchers.entry(path)
|
||||
{
|
||||
let count = occupied.get_mut();
|
||||
*count = count.saturating_sub(1);
|
||||
|
||||
if *count == 0 {
|
||||
occupied.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Changed: Return BoxFuture<()>
|
||||
fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.watchers.write().await.clear();
|
||||
})
|
||||
}
|
||||
|
||||
// Changed: Return BoxFuture<()>
|
||||
fn fetch_chunks<'a>(
|
||||
&'a self,
|
||||
folder: &'a LevelFolder,
|
||||
@@ -284,7 +281,6 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
// Changed: Return BoxFuture<Result<(), ChunkWritingError>>
|
||||
fn save_chunks<'a>(
|
||||
&'a self,
|
||||
folder: &'a LevelFolder,
|
||||
@@ -374,8 +370,6 @@ where
|
||||
// Decrement strong count
|
||||
drop(chunk_serializer);
|
||||
|
||||
// If there are still no watchers, drop from the locks
|
||||
let mut locks = self.file_locks.write().await;
|
||||
|
||||
if self
|
||||
.watchers
|
||||
@@ -384,6 +378,8 @@ where
|
||||
.get(&path)
|
||||
.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) {
|
||||
loader.can_remove().await
|
||||
} else {
|
||||
@@ -410,7 +406,6 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
// Changed: Return BoxFuture<()>
|
||||
fn clean_up_log(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let locks = self.file_locks.read().await;
|
||||
@@ -418,11 +413,10 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
// Changed: Return BoxFuture<()>
|
||||
fn block_and_await_ongoing_tasks(&self) -> BoxFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
//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
|
||||
let tasks = serializer_cache
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::block::entities::BlockEntity;
|
||||
use crate::chunk::format::LightContainer;
|
||||
use crate::tick::scheduler::ChunkTickScheduler;
|
||||
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::fluid::Fluid;
|
||||
use pumpkin_data::tag::Block::MINECRAFT_LEAVES;
|
||||
@@ -438,7 +438,7 @@ impl ChunkSections {
|
||||
let mut y = first_y;
|
||||
while y >= self.min_y {
|
||||
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);
|
||||
}
|
||||
@@ -568,12 +568,14 @@ impl ChunkData {
|
||||
}
|
||||
|
||||
pub fn get_highest_non_empty_subchunk(&self) -> usize {
|
||||
for (i, sub_chunk) in self.section.sections.iter().enumerate().rev() {
|
||||
if sub_chunk.block_states.non_air_block_count() != 0 {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
0
|
||||
self.section
|
||||
.sections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.position(|(_, sub)| !sub.block_states.has_only_air())
|
||||
.map(|p| self.section.sections.len() - 1 - p)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 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 {
|
||||
match self {
|
||||
Self::Homogeneous(registry_id) => {
|
||||
if !BlockState::from_id(*registry_id).is_air() {
|
||||
if !is_air(*registry_id) {
|
||||
Self::VOLUME as u16
|
||||
} else {
|
||||
0
|
||||
@@ -489,7 +497,7 @@ impl BlockPalette {
|
||||
.iter()
|
||||
.zip(data.counts.iter())
|
||||
.filter_map(|(registry_id, count)| {
|
||||
if !BlockState::from_id(*registry_id).is_air() {
|
||||
if !is_air(*registry_id) {
|
||||
Some(*count)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -9,6 +9,7 @@ TODO
|
||||
use crate::block::RawBlockState;
|
||||
use crate::chunk::io::LoadedData::Loaded;
|
||||
use crate::chunk::{ChunkData, ChunkHeightmapType, ChunkLight, ChunkSections, SubChunk};
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use std::default::Default;
|
||||
use std::pin::Pin;
|
||||
@@ -825,6 +826,7 @@ impl Chunk {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut chunk = ChunkData {
|
||||
light_engine: ChunkLight {
|
||||
sky_light: (0..sections.sections.len())
|
||||
@@ -1140,9 +1142,7 @@ impl GenerationCache for Cache {
|
||||
}
|
||||
|
||||
fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
|
||||
GenerationCache::get_block_state(self, local_pos)
|
||||
.to_state()
|
||||
.is_air()
|
||||
is_air(GenerationCache::get_block_state(self, local_pos).0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::{Block, BlockDirection};
|
||||
use pumpkin_util::{HeightMap, include_json_static};
|
||||
use serde::Deserialize;
|
||||
@@ -366,8 +367,8 @@ impl CountOnEveryLayerPlacementModifier {
|
||||
}
|
||||
|
||||
fn blocks_spawn(state: &RawBlockState) -> bool {
|
||||
let block = state.to_block();
|
||||
state.to_state().is_air() || block == &Block::WATER || block == &Block::LAVA
|
||||
let block = state.to_block_id();
|
||||
is_air(state.0) || block == Block::WATER || block == Block::LAVA
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::pin::Pin;
|
||||
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::fluid::{Fluid, FluidState};
|
||||
use pumpkin_data::tag;
|
||||
@@ -430,8 +431,7 @@ impl ProtoChunk {
|
||||
|
||||
#[inline]
|
||||
pub fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
|
||||
let state = self.get_block_state(local_pos).to_state();
|
||||
state.is_air()
|
||||
is_air(self.get_block_state(local_pos).0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -815,12 +815,10 @@ impl ProtoChunk {
|
||||
|
||||
let state = self
|
||||
.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?
|
||||
if !(state != &AIR_BLOCK
|
||||
&& state != &WATER_BLOCK
|
||||
&& state != &LAVA_BLOCK)
|
||||
if !(state != AIR_BLOCK && state != WATER_BLOCK && state != LAVA_BLOCK)
|
||||
{
|
||||
min = search_y + 1;
|
||||
break;
|
||||
|
||||
@@ -152,12 +152,12 @@ impl SurfaceTerrainBuilder {
|
||||
if surface_y <= elevation_y {
|
||||
for y in (chunk.bottom_y() as i32..=elevation_y).rev() {
|
||||
let pos = Vector3::new(global_x, y, global_z);
|
||||
let block_state = chunk.get_block_state(&pos).to_block();
|
||||
if block_state == Block::from_state_id(chunk.default_block.id) {
|
||||
let block_state = chunk.get_block_state(&pos).to_block_id();
|
||||
if block_state == Block::get_raw_id_from_state_id(chunk.default_block.id) {
|
||||
break;
|
||||
}
|
||||
|
||||
if block_state == &WATER_BLOCK {
|
||||
if block_state == WATER_BLOCK {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
world::BlockRegistryExt,
|
||||
};
|
||||
use crossbeam::channel::Sender;
|
||||
use dashmap::{DashMap, Entry};
|
||||
use dashmap::DashMap;
|
||||
use log::trace;
|
||||
use num_traits::Zero;
|
||||
use pumpkin_config::{chunk::ChunkConfig, world::LevelConfig};
|
||||
@@ -35,7 +35,6 @@ use std::{
|
||||
thread,
|
||||
};
|
||||
// use tokio::runtime::Handle;
|
||||
use tokio::time::Instant;
|
||||
use tokio::{
|
||||
select,
|
||||
sync::{
|
||||
@@ -134,45 +133,40 @@ impl Level {
|
||||
seed: i64,
|
||||
dimension: Dimension,
|
||||
) -> 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");
|
||||
if !region_folder.exists() {
|
||||
std::fs::create_dir_all(®ion_folder).expect("Failed to create Region folder");
|
||||
}
|
||||
let entities_folder = root_folder.join("entities");
|
||||
if !entities_folder.exists() {
|
||||
std::fs::create_dir_all(®ion_folder).expect("Failed to create Entities folder");
|
||||
}
|
||||
|
||||
std::fs::create_dir_all(®ion_folder).expect("Failed to create Region folder");
|
||||
std::fs::create_dir_all(&entities_folder).expect("Failed to create Entities folder");
|
||||
|
||||
let level_folder = LevelFolder {
|
||||
root_folder,
|
||||
region_folder,
|
||||
entities_folder,
|
||||
};
|
||||
|
||||
// TODO: Load info correctly based on world format type
|
||||
let seed = Seed(seed as u64);
|
||||
let world_gen = get_world_gen(seed, dimension).into();
|
||||
|
||||
let chunk_saver: Arc<dyn FileIO<Data = SyncChunk>> = match &level_config.chunk {
|
||||
ChunkConfig::Linear(chunk_config) => Arc::new(
|
||||
ChunkFileManager::<LinearFile<ChunkData>>::new(chunk_config.clone()),
|
||||
ChunkConfig::Linear(config) => Arc::new(
|
||||
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 {
|
||||
ChunkConfig::Linear(chunk_config) => Arc::new(ChunkFileManager::<
|
||||
LinearFile<ChunkEntityData>,
|
||||
>::new(chunk_config.clone())),
|
||||
ChunkConfig::Anvil(chunk_config) => Arc::new(ChunkFileManager::<
|
||||
ChunkConfig::Linear(config) => Arc::new(
|
||||
ChunkFileManager::<LinearFile<ChunkEntityData>>::new(config.clone()),
|
||||
),
|
||||
ChunkConfig::Anvil(config) => Arc::new(ChunkFileManager::<
|
||||
AnvilChunkFile<ChunkEntityData>,
|
||||
>::new(chunk_config.clone())),
|
||||
>::new(config.clone())),
|
||||
};
|
||||
|
||||
let (gen_entity_request_tx, gen_entity_request_rx) = crossbeam::channel::unbounded();
|
||||
let pending_entity_generations = Arc::new(DashMap::new());
|
||||
|
||||
let level_channel = Arc::new(LevelChannel::new());
|
||||
let thread_tracker = Mutex::new(Vec::new());
|
||||
let listener = Arc::new(ChunkListener::new());
|
||||
@@ -214,8 +208,6 @@ impl Level {
|
||||
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) {
|
||||
let level_clone = level_ref.clone();
|
||||
let pending_clone = pending_entity_generations.clone();
|
||||
@@ -223,7 +215,7 @@ impl Level {
|
||||
|
||||
let builder =
|
||||
thread::Builder::new().name(format!("Entity Chunk Generation Thread {thread_id}"));
|
||||
// tracker.push( TODO
|
||||
|
||||
builder
|
||||
.spawn(move || {
|
||||
while let Ok(pos) = rx.recv() {
|
||||
@@ -231,11 +223,6 @@ impl Level {
|
||||
break;
|
||||
}
|
||||
|
||||
// log::debug!(
|
||||
// "Generating entity chunk {pos:?}, worker thread {thread_id:?}, queue length {}",
|
||||
// rx.len()
|
||||
// );
|
||||
|
||||
let chunk = ChunkEntityData {
|
||||
x: pos.x,
|
||||
z: pos.y,
|
||||
@@ -248,25 +235,15 @@ impl Level {
|
||||
.loaded_entity_chunks
|
||||
.insert(pos, arc_chunk.clone());
|
||||
|
||||
if let Some(waiters) = pending_clone.remove(&pos) {
|
||||
for tx in waiters.1 {
|
||||
if let Some((_, waiters)) = pending_clone.remove(&pos) {
|
||||
for tx in waiters {
|
||||
let _ = tx.send(arc_chunk.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
// );
|
||||
}
|
||||
// drop(tracker);
|
||||
// level_ref
|
||||
// .chunk_loading
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .add_ticket(
|
||||
// Vector2::<i32>::new(0, 0),
|
||||
// ChunkLoading::FULL_CHUNK_LEVEL - 1,
|
||||
// );
|
||||
level_ref
|
||||
}
|
||||
|
||||
@@ -385,21 +362,16 @@ impl Level {
|
||||
/// before
|
||||
pub async fn mark_chunks_as_newly_watched(&self, chunks: &[Vector2<i32>]) {
|
||||
for chunk in chunks {
|
||||
log::trace!("{chunk:?} marked as newly watched");
|
||||
match self.chunk_watchers.entry(*chunk) {
|
||||
Entry::Occupied(mut occupied) => {
|
||||
let value = occupied.get_mut();
|
||||
self.chunk_watchers
|
||||
.entry(*chunk)
|
||||
.and_modify(|value| {
|
||||
if let Some(new_value) = value.checked_add(1) {
|
||||
*value = new_value;
|
||||
//log::debug!("Watch value for {:?}: {}", chunk, value);
|
||||
} else {
|
||||
log::error!("Watching overflow on chunk {chunk:?}");
|
||||
log::error!("Watching overflow on chunk {:?}", chunk);
|
||||
}
|
||||
}
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.or_insert(1);
|
||||
}
|
||||
|
||||
// self.chunk_saver
|
||||
@@ -416,22 +388,14 @@ impl Level {
|
||||
let mut chunks_to_clean = Vec::new();
|
||||
|
||||
for chunk in chunks {
|
||||
log::trace!("{chunk:?} marked as no longer watched");
|
||||
match self.chunk_watchers.entry(*chunk) {
|
||||
Entry::Occupied(mut occupied) => {
|
||||
let value = occupied.get_mut();
|
||||
*value = value.saturating_sub(1);
|
||||
if let Some(mut count) = self.chunk_watchers.get_mut(chunk) {
|
||||
let value = count.value_mut();
|
||||
*value = value.saturating_sub(1);
|
||||
|
||||
if *value == 0 {
|
||||
occupied.remove_entry();
|
||||
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
|
||||
if *value == 0 {
|
||||
drop(count);
|
||||
self.chunk_watchers.remove(chunk);
|
||||
chunks_to_clean.push(*chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,13 +470,10 @@ impl Level {
|
||||
.iter()
|
||||
.map(|x| x.value().clone())
|
||||
.collect::<Vec<_>>();
|
||||
for chunk in chunks {
|
||||
let mut chunk = chunk.write().await;
|
||||
ticks.block_ticks.append(&mut chunk.block_ticks.step_tick());
|
||||
ticks.fluid_ticks.append(&mut chunk.fluid_ticks.step_tick());
|
||||
|
||||
let chunk = chunk.downgrade();
|
||||
|
||||
for chunk_sync in chunks {
|
||||
// Try to get a READ lock first.
|
||||
// Most chunks won't have pending ticks, so this is very fast.
|
||||
let chunk = chunk_sync.read().await;
|
||||
let chunk_x_base = chunk.x * 16;
|
||||
let chunk_z_base = chunk.z * 16;
|
||||
|
||||
@@ -544,21 +505,38 @@ impl Level {
|
||||
}
|
||||
|
||||
for section_data in section_blocks {
|
||||
for (random_pos, block_state_id) in section_data {
|
||||
if has_random_ticks(block_state_id) {
|
||||
ticks.random_ticks.push(ScheduledTick {
|
||||
position: random_pos,
|
||||
delay: 0,
|
||||
priority: TickPriority::Normal,
|
||||
value: (),
|
||||
});
|
||||
}
|
||||
}
|
||||
ticks
|
||||
.random_ticks
|
||||
.extend(
|
||||
section_data
|
||||
.into_iter()
|
||||
.filter_map(|(random_pos, block_state_id)| {
|
||||
if has_random_ticks(block_state_id) {
|
||||
Some(ScheduledTick {
|
||||
position: random_pos,
|
||||
delay: 0,
|
||||
priority: TickPriority::Normal,
|
||||
value: (),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ticks
|
||||
.block_entities
|
||||
.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();
|
||||
@@ -603,8 +581,6 @@ impl Level {
|
||||
return chunk.clone();
|
||||
}
|
||||
|
||||
log::debug!("Missing Chunk {pos:?}. Fetching.");
|
||||
let clock = Instant::now();
|
||||
let recv = self.chunk_listener.add_single_chunk_listener(pos);
|
||||
|
||||
{
|
||||
@@ -613,16 +589,12 @@ impl Level {
|
||||
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()
|
||||
} else {
|
||||
recv.await
|
||||
.expect("Chunk listener dropped without sending chunk")
|
||||
};
|
||||
|
||||
log::debug!("Chunk {pos:?} received after {:?}.", Instant::now() - clock);
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_single_entity_chunk(
|
||||
@@ -846,6 +818,7 @@ impl Level {
|
||||
priority: TickPriority,
|
||||
) {
|
||||
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 {
|
||||
@@ -854,9 +827,8 @@ impl Level {
|
||||
priority,
|
||||
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(
|
||||
@@ -867,6 +839,7 @@ impl Level {
|
||||
priority: TickPriority,
|
||||
) {
|
||||
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 {
|
||||
@@ -875,9 +848,8 @@ impl Level {
|
||||
priority,
|
||||
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(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, CanPlaceAtArgs, GetStateForNeighborUpdateArgs, OnScheduledTickArgs,
|
||||
};
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::tag::{RegistryKey, get_tag_values};
|
||||
use pumpkin_macros::{pumpkin_block, pumpkin_block_from_tag};
|
||||
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 {
|
||||
!block_accessor
|
||||
.get_block_state(&block_pos.down())
|
||||
.await
|
||||
.is_air()
|
||||
!is_air(block_accessor.get_block_state_id(&block_pos.down()).await)
|
||||
}
|
||||
|
||||
@@ -206,23 +206,6 @@ impl ChunkManager {
|
||||
|
||||
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| {
|
||||
(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 dy in (-view_distance_i32)..=view_distance_i32 {
|
||||
let new_pos = center.add_raw(dx, dy);
|
||||
if !self.chunk_sent.contains(&new_pos) {
|
||||
chunks_to_watch.push(new_pos);
|
||||
if let Some(chunk) = level.loaded_chunks.get(&new_pos) {
|
||||
self.push_chunk(new_pos, chunk.value().clone());
|
||||
}
|
||||
if !self.chunk_sent.contains(&new_pos)
|
||||
&& let Some(chunk) = level.loaded_chunks.get(&new_pos)
|
||||
{
|
||||
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>) {
|
||||
|
||||
@@ -20,7 +20,7 @@ use rustyline::Editor;
|
||||
use rustyline::history::FileHistory;
|
||||
use rustyline::{Config, error::ReadlineError};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Cursor, IsTerminal, stdin};
|
||||
use std::io::{Cursor, ErrorKind, IsTerminal, stdin};
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
@@ -222,10 +222,36 @@ impl PumpkinServer {
|
||||
let mut tcp_listener = None;
|
||||
|
||||
if server.basic_config.java_edition {
|
||||
let address = server.basic_config.java_edition_address;
|
||||
// Setup the TCP server socket.
|
||||
let listener = tokio::net::TcpListener::bind(server.basic_config.java_edition_address)
|
||||
.await
|
||||
.expect("Failed to start `TcpListener`");
|
||||
let listener = match TcpListener::bind(address).await {
|
||||
Ok(l) => l,
|
||||
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
|
||||
let addr = listener
|
||||
.local_addr()
|
||||
|
||||
@@ -90,7 +90,9 @@ impl JavaClient {
|
||||
let verify_token: [u8; 4] = rand::random();
|
||||
// Wait until we have sent the encryption packet to the client
|
||||
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;
|
||||
} else {
|
||||
@@ -107,7 +109,10 @@ impl JavaClient {
|
||||
encryption_response: SEncryptionResponse,
|
||||
) {
|
||||
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 {
|
||||
self.kick(TextComponent::text(error.to_string())).await;
|
||||
@@ -207,7 +212,7 @@ impl JavaClient {
|
||||
shared_secret: &[u8],
|
||||
username: &str,
|
||||
) -> 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 profile = authentication::authenticate(
|
||||
username,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use pkcs8::EncodePublicKey;
|
||||
use pumpkin_protocol::java::client::login::CEncryptionRequest;
|
||||
@@ -15,6 +17,7 @@ pub struct KeyStore {
|
||||
impl KeyStore {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
let instant = Instant::now();
|
||||
log::debug!("Creating encryption keys...");
|
||||
let private_key = Self::generate_private_key();
|
||||
|
||||
@@ -27,6 +30,8 @@ impl KeyStore {
|
||||
.to_vec()
|
||||
.into_boxed_slice();
|
||||
|
||||
log::debug!("Created RSA keys, took {}ms", instant.elapsed().as_millis());
|
||||
|
||||
Self {
|
||||
private_key,
|
||||
public_key_der,
|
||||
|
||||
@@ -39,7 +39,7 @@ use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32};
|
||||
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_util::task::TaskTracker;
|
||||
|
||||
@@ -59,7 +59,7 @@ pub struct Server {
|
||||
pub advanced_config: AdvancedConfiguration,
|
||||
|
||||
/// Handles cryptographic keys for secure communication.
|
||||
key_store: KeyStore,
|
||||
key_store: OnceCell<Arc<KeyStore>>,
|
||||
/// Manages server status information.
|
||||
listing: Mutex<CachedStatus>,
|
||||
/// Saves server branding information.
|
||||
@@ -190,7 +190,7 @@ impl Server {
|
||||
command_dispatcher,
|
||||
block_registry: block_registry.clone(),
|
||||
item_registry: super::item::items::default_registry(),
|
||||
key_store: KeyStore::new(),
|
||||
key_store: OnceCell::new(),
|
||||
listing,
|
||||
branding: CachedBranding::new(),
|
||||
bossbars: Mutex::new(CustomBossbars::new()),
|
||||
@@ -208,59 +208,86 @@ impl Server {
|
||||
level_info: level_info.clone(),
|
||||
_locker: Arc::new(locker),
|
||||
};
|
||||
|
||||
let server = Arc::new(server);
|
||||
let weak = Arc::downgrade(&server);
|
||||
let level_config = &server.advanced_config.world;
|
||||
let level_config = Arc::new(server.advanced_config.world.clone());
|
||||
|
||||
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}");
|
||||
let overworld = World::load(
|
||||
into_level(
|
||||
Dimension::OVERWORLD,
|
||||
level_config,
|
||||
world_path.clone(),
|
||||
block_registry.clone(),
|
||||
seed,
|
||||
),
|
||||
level_info.clone(),
|
||||
Dimension::OVERWORLD,
|
||||
block_registry.clone(),
|
||||
weak.clone(),
|
||||
);
|
||||
log::info!("Loading Nether: {seed}");
|
||||
let nether = World::load(
|
||||
into_level(
|
||||
Dimension::THE_NETHER,
|
||||
level_config,
|
||||
world_path.clone(),
|
||||
block_registry.clone(),
|
||||
seed,
|
||||
),
|
||||
level_info.clone(),
|
||||
Dimension::THE_NETHER,
|
||||
block_registry.clone(),
|
||||
weak.clone(),
|
||||
);
|
||||
log::info!("Loading End: {seed}");
|
||||
let end = World::load(
|
||||
into_level(
|
||||
Dimension::THE_END,
|
||||
level_config,
|
||||
world_path,
|
||||
block_registry.clone(),
|
||||
seed,
|
||||
),
|
||||
level_info,
|
||||
Dimension::THE_END,
|
||||
block_registry,
|
||||
weak,
|
||||
);
|
||||
*server
|
||||
.worlds
|
||||
.try_write()
|
||||
.expect("Nothing should hold a lock of worlds before server startup") =
|
||||
// vec![overworld.into()];
|
||||
vec![overworld.into(), nether.into(), end.into()];
|
||||
let overworld_task = tokio::task::spawn_blocking({
|
||||
let path = world_path.clone();
|
||||
let registry = block_registry.clone();
|
||||
let level_info = level_info.clone();
|
||||
let weak = weak_server.clone();
|
||||
let config = level_config.clone();
|
||||
move || {
|
||||
World::load(
|
||||
into_level(Dimension::OVERWORLD, &config, path, registry.clone(), seed),
|
||||
level_info,
|
||||
Dimension::OVERWORLD,
|
||||
registry,
|
||||
weak,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let nether_task = tokio::task::spawn_blocking({
|
||||
let path = world_path.clone();
|
||||
let registry = block_registry.clone();
|
||||
let level_info = level_info.clone();
|
||||
let weak = weak_server.clone();
|
||||
let config = level_config.clone();
|
||||
move || {
|
||||
World::load(
|
||||
into_level(Dimension::THE_NETHER, &config, path, registry.clone(), seed),
|
||||
level_info,
|
||||
Dimension::THE_NETHER,
|
||||
registry,
|
||||
weak,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let end_task = tokio::task::spawn_blocking({
|
||||
let path = world_path.clone();
|
||||
let registry = block_registry.clone();
|
||||
let level_info = level_info.clone();
|
||||
let weak = weak_server.clone();
|
||||
let config = level_config.clone();
|
||||
move || {
|
||||
World::load(
|
||||
into_level(Dimension::THE_END, &config, path, registry.clone(), seed),
|
||||
level_info,
|
||||
Dimension::THE_END,
|
||||
registry,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -623,21 +650,29 @@ impl Server {
|
||||
&self.listing
|
||||
}
|
||||
|
||||
pub fn encryption_request<'a>(
|
||||
pub async fn encryption_request<'a>(
|
||||
&'a self,
|
||||
verification_token: &'a [u8; 4],
|
||||
should_authenticate: bool,
|
||||
) -> CEncryptionRequest<'a> {
|
||||
self.key_store
|
||||
.get_or_init(|| async { Arc::new(KeyStore::new()) })
|
||||
.await
|
||||
.encryption_request("", verification_token, should_authenticate)
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
|
||||
self.key_store.decrypt(data)
|
||||
pub async fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
|
||||
self.key_store
|
||||
.get_or_init(|| async { Arc::new(KeyStore::new()) })
|
||||
.await
|
||||
.decrypt(data)
|
||||
}
|
||||
|
||||
pub fn digest_secret(&self, secret: &[u8]) -> String {
|
||||
self.key_store.get_digest(secret)
|
||||
pub async fn digest_secret(&self, secret: &[u8]) -> String {
|
||||
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)
|
||||
@@ -711,6 +746,22 @@ impl Server {
|
||||
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.
|
||||
pub async fn get_tick_times_nanos_copy(&self) -> [i64; 100] {
|
||||
*self.tick_times_nanos.lock().await
|
||||
|
||||
@@ -22,74 +22,77 @@ pub async fn get_view_distance(player: &Player) -> NonZeroU8 {
|
||||
|
||||
pub async fn update_position(player: &Arc<Player>) {
|
||||
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 new_chunk_center = entity.chunk_pos.load();
|
||||
|
||||
let old_cylindrical = player.watched_section.load();
|
||||
let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance);
|
||||
|
||||
if old_cylindrical != new_cylindrical {
|
||||
match &player.client {
|
||||
ClientPlatform::Java(java_client) => {
|
||||
java_client
|
||||
.send_packet_now(&CCenterChunk {
|
||||
chunk_x: new_chunk_center.x.into(),
|
||||
chunk_z: new_chunk_center.y.into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
ClientPlatform::Bedrock(bedrock_client) => {
|
||||
bedrock_client
|
||||
.send_game_packet(&CNetworkChunkPublisherUpdate::new(
|
||||
player.get_entity().block_pos.load(),
|
||||
u32::from(view_distance.get()) * 16,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
if old_cylindrical == new_cylindrical {
|
||||
return;
|
||||
}
|
||||
|
||||
match &player.client {
|
||||
ClientPlatform::Java(java_client) => {
|
||||
java_client
|
||||
.send_packet_now(&CCenterChunk {
|
||||
chunk_x: new_chunk_center.x.into(),
|
||||
chunk_z: new_chunk_center.y.into(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
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,
|
||||
);
|
||||
|
||||
// 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,
|
||||
);
|
||||
ClientPlatform::Bedrock(bedrock_client) => {
|
||||
bedrock_client
|
||||
.send_game_packet(&CNetworkChunkPublisherUpdate::new(
|
||||
player.get_entity().block_pos.load(),
|
||||
u32::from(view_distance.get()) * 16,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ use bytes::BufMut;
|
||||
use crossbeam::queue::SegQueue;
|
||||
use explosion::Explosion;
|
||||
use pumpkin_config::BasicConfiguration;
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::entity::MobCategory;
|
||||
@@ -1874,10 +1875,6 @@ impl World {
|
||||
chunks: Vec<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)]
|
||||
let inst = std::time::Instant::now();
|
||||
|
||||
@@ -3054,7 +3051,7 @@ impl World {
|
||||
|
||||
if new_state_id != block_state_id {
|
||||
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;
|
||||
} else {
|
||||
self.set_block_state(block_pos, new_state_id, flags).await;
|
||||
|
||||
Reference in New Issue
Block a user