chore: this should speed things up
This commit is contained in:
parent
d737b56cbc
commit
afc4cb0cd3
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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),*
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(®ion_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(®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 {
|
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) => {
|
|
||||||
vacant.insert(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.or_insert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// self.chunk_saver
|
// self.chunk_saver
|
||||||
@ -416,24 +388,16 @@ 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) => {
|
|
||||||
let value = occupied.get_mut();
|
|
||||||
*value = value.saturating_sub(1);
|
*value = value.saturating_sub(1);
|
||||||
|
|
||||||
if *value == 0 {
|
if *value == 0 {
|
||||||
occupied.remove_entry();
|
drop(count);
|
||||||
|
self.chunk_watchers.remove(chunk);
|
||||||
chunks_to_clean.push(*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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.entity_saver
|
self.entity_saver
|
||||||
.unwatch_chunks(&self.level_folder, chunks)
|
.unwatch_chunks(&self.level_folder, chunks)
|
||||||
@ -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
|
||||||
|
.random_ticks
|
||||||
|
.extend(
|
||||||
|
section_data
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(random_pos, block_state_id)| {
|
||||||
if has_random_ticks(block_state_id) {
|
if has_random_ticks(block_state_id) {
|
||||||
ticks.random_ticks.push(ScheduledTick {
|
Some(ScheduledTick {
|
||||||
position: random_pos,
|
position: random_pos,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
priority: TickPriority::Normal,
|
priority: TickPriority::Normal,
|
||||||
value: (),
|
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(
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,21 +225,14 @@ 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>) {
|
||||||
let mut lock = level.chunk_loading.lock().unwrap();
|
let mut lock = level.chunk_loading.lock().unwrap();
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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();
|
||||||
|
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,
|
Dimension::OVERWORLD,
|
||||||
level_config,
|
registry,
|
||||||
world_path.clone(),
|
weak,
|
||||||
block_registry.clone(),
|
)
|
||||||
seed,
|
}
|
||||||
),
|
});
|
||||||
level_info.clone(),
|
|
||||||
Dimension::OVERWORLD,
|
let nether_task = tokio::task::spawn_blocking({
|
||||||
block_registry.clone(),
|
let path = world_path.clone();
|
||||||
weak.clone(),
|
let registry = block_registry.clone();
|
||||||
);
|
let level_info = level_info.clone();
|
||||||
log::info!("Loading Nether: {seed}");
|
let weak = weak_server.clone();
|
||||||
let nether = World::load(
|
let config = level_config.clone();
|
||||||
into_level(
|
move || {
|
||||||
|
World::load(
|
||||||
|
into_level(Dimension::THE_NETHER, &config, path, registry.clone(), seed),
|
||||||
|
level_info,
|
||||||
Dimension::THE_NETHER,
|
Dimension::THE_NETHER,
|
||||||
level_config,
|
registry,
|
||||||
world_path.clone(),
|
weak,
|
||||||
block_registry.clone(),
|
)
|
||||||
seed,
|
}
|
||||||
),
|
});
|
||||||
level_info.clone(),
|
|
||||||
Dimension::THE_NETHER,
|
let end_task = tokio::task::spawn_blocking({
|
||||||
block_registry.clone(),
|
let path = world_path.clone();
|
||||||
weak.clone(),
|
let registry = block_registry.clone();
|
||||||
);
|
let level_info = level_info.clone();
|
||||||
log::info!("Loading End: {seed}");
|
let weak = weak_server.clone();
|
||||||
let end = World::load(
|
let config = level_config.clone();
|
||||||
into_level(
|
move || {
|
||||||
Dimension::THE_END,
|
World::load(
|
||||||
level_config,
|
into_level(Dimension::THE_END, &config, path, registry.clone(), seed),
|
||||||
world_path,
|
|
||||||
block_registry.clone(),
|
|
||||||
seed,
|
|
||||||
),
|
|
||||||
level_info,
|
level_info,
|
||||||
Dimension::THE_END,
|
Dimension::THE_END,
|
||||||
block_registry,
|
registry,
|
||||||
weak,
|
weak,
|
||||||
);
|
)
|
||||||
*server
|
}
|
||||||
.worlds
|
});
|
||||||
.try_write()
|
|
||||||
.expect("Nothing should hold a lock of worlds before server startup") =
|
let (overworld_res, nether_res, end_res) =
|
||||||
// vec![overworld.into()];
|
tokio::join!(overworld_task, nether_task, end_task);
|
||||||
vec![overworld.into(), nether.into(), end.into()];
|
|
||||||
|
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
|
||||||
|
|||||||
@ -22,14 +22,20 @@ 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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match &player.client {
|
match &player.client {
|
||||||
ClientPlatform::Java(java_client) => {
|
ClientPlatform::Java(java_client) => {
|
||||||
java_client
|
java_client
|
||||||
@ -57,11 +63,7 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
&mut unloading_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;
|
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;
|
let mut chunk_manager = player.chunk_manager.lock().await;
|
||||||
@ -74,9 +76,13 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
|
|
||||||
player.watched_section.store(new_cylindrical);
|
player.watched_section.store(new_cylindrical);
|
||||||
|
|
||||||
if !chunks_to_clean.is_empty() {
|
// Make sure the watched section and the chunk watcher updates are async atomic. We want to
|
||||||
// level.clean_chunks(&chunks_to_clean).await;
|
// ensure what we unload when the player disconnects is correct.
|
||||||
for chunk in unloading_chunks {
|
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
|
player
|
||||||
.client
|
.client
|
||||||
.enqueue_packet(&CUnloadChunk::new(chunk.x, chunk.y))
|
.enqueue_packet(&CUnloadChunk::new(chunk.x, chunk.y))
|
||||||
@ -85,11 +91,8 @@ pub async fn update_position(player: &Arc<Player>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !loading_chunks.is_empty() {
|
if !loading_chunks.is_empty() {
|
||||||
entity.world.spawn_world_entity_chunks(
|
entity
|
||||||
player.clone(),
|
.world
|
||||||
loading_chunks,
|
.spawn_world_entity_chunks(player.clone(), loading_chunks, new_chunk_center);
|
||||||
new_chunk_center,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user