Fix dead locks, vanilla chunk loading and packet issues

This commit is contained in:
Alexander Medvedev 2025-07-07 19:36:45 +02:00
parent c61572d776
commit b9f2de33f4
10 changed files with 114 additions and 63 deletions

View File

@ -1,8 +1,5 @@
use std::vec::IntoIter;
use crate::*; use crate::*;
use io::Read; use io::Read;
use serde::de::value::SeqDeserializer;
use serde::de::{self, DeserializeSeed, IntoDeserializer, MapAccess, SeqAccess, Visitor}; use serde::de::{self, DeserializeSeed, IntoDeserializer, MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, forward_to_deserialize_any}; use serde::{Deserialize, forward_to_deserialize_any};
@ -116,6 +113,8 @@ pub struct Deserializer<R: Read> {
// Yes, this breaks with recursion. Just an attempt at a sanity check // Yes, this breaks with recursion. Just an attempt at a sanity check
in_list: bool, in_list: bool,
is_named: bool, is_named: bool,
// For debugging
key_stack: Vec<String>,
} }
impl<R: Read> Deserializer<R> { impl<R: Read> Deserializer<R> {
@ -125,6 +124,7 @@ impl<R: Read> Deserializer<R> {
tag_to_deserialize_stack: Vec::new(), tag_to_deserialize_stack: Vec::new(),
in_list: false, in_list: false,
is_named, is_named,
key_stack: Vec::new(),
} }
} }
} }
@ -181,21 +181,28 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer<R> {
END_ID => Err(Error::SerdeError( END_ID => Err(Error::SerdeError(
"Trying to deserialize an END tag!".to_string(), "Trying to deserialize an END tag!".to_string(),
)), )),
LIST_ID => { LIST_ID | INT_ARRAY_ID | LONG_ARRAY_ID | BYTE_ARRAY_ID => {
let list_type = self.input.get_u8_be()?; let list_type = match tag_to_deserialize {
LIST_ID => self.input.get_u8_be()?,
INT_ARRAY_ID => INT_ID,
LONG_ARRAY_ID => LONG_ID,
BYTE_ARRAY_ID => BYTE_ID,
_ => unreachable!(),
};
let remaining_values = self.input.get_i32_be()?; let remaining_values = self.input.get_i32_be()?;
if remaining_values < 0 { if remaining_values < 0 {
return Err(Error::NegativeLength(remaining_values)); return Err(Error::NegativeLength(remaining_values));
} }
visitor.visit_seq(ListAccess { let result = visitor.visit_seq(ListAccess {
de: self, de: self,
list_type, list_type,
remaining_values: remaining_values as usize, remaining_values: remaining_values as usize,
}) })?;
Ok(result)
} }
COMPOUND_ID => self.deserialize_map(visitor), COMPOUND_ID => visitor.visit_map(CompoundAccess { de: self }),
_ => { _ => {
let result = match NbtTag::deserialize_data(&mut self.input, tag_to_deserialize)? { let result = match NbtTag::deserialize_data(&mut self.input, tag_to_deserialize)? {
NbtTag::Byte(value) => visitor.visit_i8::<Error>(value)?, NbtTag::Byte(value) => visitor.visit_i8::<Error>(value)?,
@ -205,22 +212,6 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer<R> {
NbtTag::Float(value) => visitor.visit_f32::<Error>(value)?, NbtTag::Float(value) => visitor.visit_f32::<Error>(value)?,
NbtTag::Double(value) => visitor.visit_f64::<Error>(value)?, NbtTag::Double(value) => visitor.visit_f64::<Error>(value)?,
NbtTag::String(value) => visitor.visit_string::<Error>(value)?, NbtTag::String(value) => visitor.visit_string::<Error>(value)?,
NbtTag::LongArray(value) => visitor
.visit_seq::<SeqDeserializer<IntoIter<i64>, Error>>(
value.into_deserializer(),
)?,
NbtTag::IntArray(value) => visitor
.visit_seq::<SeqDeserializer<IntoIter<i32>, Error>>(
value.into_deserializer(),
)?,
NbtTag::ByteArray(value) => {
// For compatibility, we serialize byte arrays as Vec<i8>
// It could be probably changed in the future
let array: Vec<_> = value.iter().map(|&byte| byte as i8).collect();
visitor.visit_seq::<SeqDeserializer<IntoIter<i8>, Error>>(
array.into_deserializer(),
)?
}
_ => unreachable!(), _ => unreachable!(),
}; };
Ok(result) Ok(result)
@ -277,12 +268,22 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer<R> {
if *tag_id == BYTE_ID { if *tag_id == BYTE_ID {
let value = self.input.get_u8_be()?; let value = self.input.get_u8_be()?;
if value != 0 { if value != 0 {
return visitor.visit_bool(true); visitor.visit_bool(true)
} } else {
}
}
visitor.visit_bool(false) visitor.visit_bool(false)
} }
} else {
Err(Error::UnsupportedType(format!(
"Non-byte bool (found type {tag_id})"
)))
}
} else {
Err(Error::SerdeError(
"Wanted to deserialize a bool, but there was no type hint on the stack!"
.to_string(),
))
}
}
fn deserialize_enum<V>( fn deserialize_enum<V>(
self, self,
@ -312,7 +313,12 @@ impl<'de, R: Read> de::Deserializer<'de> for &mut Deserializer<R> {
if let Some(tag_id) = self.tag_to_deserialize_stack.pop() { if let Some(tag_id) = self.tag_to_deserialize_stack.pop() {
if tag_id != COMPOUND_ID { if tag_id != COMPOUND_ID {
return Err(Error::SerdeError(format!( return Err(Error::SerdeError(format!(
"Trying to deserialize a map without a compound ID (with id {tag_id})" "Trying to deserialize a map without a compound ID ({} with id {})",
self.key_stack
.last()
.cloned()
.unwrap_or_else(|| "compound root".to_string()),
tag_id
))); )));
} }
} else { } else {
@ -381,7 +387,9 @@ impl<'de, R: Read> MapAccess<'de> for CompoundAccess<'_, R> {
where where
V: DeserializeSeed<'de>, V: DeserializeSeed<'de>,
{ {
seed.deserialize(&mut *self.de) let result = seed.deserialize(&mut *self.de);
self.de.key_stack.pop();
result
} }
} }
@ -397,6 +405,7 @@ impl<'de, R: Read> de::Deserializer<'de> for MapKey<'_, R> {
V: de::Visitor<'de>, V: de::Visitor<'de>,
{ {
let key = get_nbt_string(&mut self.de.input)?; let key = get_nbt_string(&mut self.de.input)?;
self.de.key_stack.push(key.clone());
visitor.visit_string(key) visitor.visit_string(key)
} }

View File

@ -287,8 +287,7 @@ where
error!("Error reading the data before write: {err}"); error!("Error reading the data before write: {err}");
Err(ChunkWritingError::IoError(err)) Err(ChunkWritingError::IoError(err))
} }
Err(err) => { Err(_) => {
error!("Error reading the data before write: {err:?}");
Err(ChunkWritingError::IoError(std::io::ErrorKind::Other)) Err(ChunkWritingError::IoError(std::io::ErrorKind::Other))
} }
}?; }?;

View File

@ -881,13 +881,14 @@ impl Level {
match error { match error {
// this is expected, and is not an error // this is expected, and is not an error
ChunkReadingError::ChunkNotExist ChunkReadingError::ChunkNotExist
| ChunkReadingError::InvalidHeader
| ChunkReadingError::ParsingError( | ChunkReadingError::ParsingError(
ChunkParsingError::ChunkNotGenerated, ChunkParsingError::ChunkNotGenerated,
) => {} ) => {}
// this is an error, and we should log it // this is an error, and we should log it
error => { error => {
log::error!( log::error!(
"Failed to load chunk at {pos:?}: {error} (regenerating)" "Failed to load a Entity chunk at {pos:?}: {error} (regenerating)"
); );
} }
}; };

View File

@ -0,0 +1 @@
pub mod painting;

View File

@ -0,0 +1,34 @@
use std::sync::atomic::Ordering;
use async_trait::async_trait;
use crate::entity::{Entity, EntityBase, living::LivingEntity};
pub struct PaintingEntity {
entity: Entity,
}
impl PaintingEntity {
pub fn new(entity: Entity) -> Self {
Self { entity }
}
}
#[async_trait]
impl EntityBase for PaintingEntity {
fn get_entity(&self) -> &Entity {
&self.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {
nbt.put_byte("facing", self.entity.data.load(Ordering::Relaxed) as i8);
}
async fn read_nbt(&self, _nbt: &pumpkin_nbt::compound::NbtCompound) {
// TODO
self.entity.data.store(3, Ordering::Relaxed);
}
}

View File

@ -43,6 +43,7 @@ use tokio::sync::{Mutex, RwLock};
use crate::world::World; use crate::world::World;
pub mod ai; pub mod ai;
pub mod decoration;
pub mod effect; pub mod effect;
pub mod experience_orb; pub mod experience_orb;
pub mod hunger; pub mod hunger;
@ -174,6 +175,9 @@ pub struct Entity {
pub portal_cooldown: AtomicU32, pub portal_cooldown: AtomicU32,
pub portal_manager: Mutex<Option<Mutex<PortalManager>>>, pub portal_manager: Mutex<Option<Mutex<PortalManager>>>,
/// The data send in the Entity Spawn packet
pub data: AtomicI32,
} }
impl Entity { impl Entity {
@ -221,6 +225,7 @@ impl Entity {
bounding_box_size: AtomicCell::new(bounding_box_size), bounding_box_size: AtomicCell::new(bounding_box_size),
invulnerable: AtomicBool::new(invulnerable), invulnerable: AtomicBool::new(invulnerable),
damage_immunities: Vec::new(), damage_immunities: Vec::new(),
data: AtomicI32::new(0),
fire_ticks: AtomicI32::new(-1), fire_ticks: AtomicI32::new(-1),
has_visual_fire: AtomicBool::new(false), has_visual_fire: AtomicBool::new(false),
portal_cooldown: AtomicU32::new(0), portal_cooldown: AtomicU32::new(0),
@ -439,7 +444,7 @@ impl Entity {
self.pitch.load(), self.pitch.load(),
self.yaw.load(), self.yaw.load(),
self.head_yaw.load(), // todo: head_yaw and yaw are swapped, find out why self.head_yaw.load(), // todo: head_yaw and yaw are swapped, find out why
0.into(), self.data.load(Relaxed).into(),
entity_vel, entity_vel,
) )
} }

View File

@ -9,6 +9,7 @@ use crate::{
entity::{ entity::{
Entity, EntityBase, Entity, EntityBase,
ai::path::Navigator, ai::path::Navigator,
decoration::painting::PaintingEntity,
living::LivingEntity, living::LivingEntity,
mob::{MobEntity, zombie::Zombie}, mob::{MobEntity, zombie::Zombie},
}, },
@ -23,15 +24,15 @@ pub fn from_type(
) -> Arc<dyn EntityBase> { ) -> Arc<dyn EntityBase> {
let entity = Entity::new(uuid, world.clone(), position, entity_type, false); let entity = Entity::new(uuid, world.clone(), position, entity_type, false);
#[allow(clippy::single_match)] let base: Arc<dyn EntityBase> = match entity_type {
let mob = match entity_type { EntityType::ZOMBIE => Arc::new(Zombie::make(entity)),
EntityType::ZOMBIE => Zombie::make(entity), EntityType::PAINTING => Arc::new(PaintingEntity::new(entity)),
// TODO // TODO
_ => MobEntity { _ => Arc::new(MobEntity {
living_entity: LivingEntity::new(entity), living_entity: LivingEntity::new(entity),
goals: Mutex::new(vec![]), goals: Mutex::new(vec![]),
navigator: Mutex::new(Navigator::default()), navigator: Mutex::new(Navigator::default()),
}, }),
}; };
Arc::new(mob) base
} }

View File

@ -325,7 +325,7 @@ impl PumpkinServer {
pub async fn unified_listener_task( pub async fn unified_listener_task(
&self, &self,
mut master_client_id_counter: u64, mut master_client_id_counter: u64,
_tasks: &Arc<TaskTracker>, tasks: &Arc<TaskTracker>,
bedrock_clients: &Arc<tokio::sync::Mutex<HashMap<SocketAddr, Arc<BedrockClientPlatform>>>>, bedrock_clients: &Arc<tokio::sync::Mutex<HashMap<SocketAddr, Arc<BedrockClientPlatform>>>>,
) -> bool { ) -> bool {
let mut udp_buf = vec![0; 4096]; // Buffer for UDP receive let mut udp_buf = vec![0; 4096]; // Buffer for UDP receive
@ -355,7 +355,7 @@ impl PumpkinServer {
let server_clone = self.server.clone(); let server_clone = self.server.clone();
tokio::spawn(async move { tasks.spawn(async move {
java_client.process_packets(&server_clone).await; java_client.process_packets(&server_clone).await;
java_client.close(); java_client.close();
java_client.await_tasks().await; java_client.await_tasks().await;
@ -411,7 +411,7 @@ impl PumpkinServer {
let reader = Cursor::new(received_data.to_vec()); let reader = Cursor::new(received_data.to_vec());
let client = client.clone(); let client = client.clone();
tokio::spawn(async move { tasks.spawn(async move {
client.process_packet(&server_clone, reader).await; client.process_packet(&server_clone, reader).await;
}); });
} }

View File

@ -14,11 +14,9 @@ use connection_cache::{CachedBranding, CachedStatus};
use key_store::KeyStore; use key_store::KeyStore;
use pumpkin_config::{BASIC_CONFIG, advanced_config}; use pumpkin_config::{BASIC_CONFIG, advanced_config};
use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_macros::send_cancellable; use pumpkin_macros::send_cancellable;
use pumpkin_protocol::java::client::login::CEncryptionRequest; use pumpkin_protocol::java::client::login::CEncryptionRequest;
use pumpkin_protocol::java::client::play::CChangeDifficulty; use pumpkin_protocol::java::client::play::CChangeDifficulty;
use pumpkin_protocol::java::client::play::CSetSelectedSlot;
use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage}; use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage};
use pumpkin_registry::{Registry, VanillaDimensionType}; use pumpkin_registry::{Registry, VanillaDimensionType};
use pumpkin_util::Difficulty; use pumpkin_util::Difficulty;
@ -353,10 +351,6 @@ impl Server {
} }
} }
player.enqueue_set_held_item_packet(&CSetSelectedSlot::new(
player.get_inventory().get_selected_slot() as i8,
)).await;
// Send tick rate information to the new player // Send tick rate information to the new player
if let ClientPlatform::Java(_) = &player.client { if let ClientPlatform::Java(_) = &player.client {
self.tick_rate_manager.update_joining_player(&player).await; self.tick_rate_manager.update_joining_player(&player).await;

View File

@ -49,7 +49,7 @@ use pumpkin_data::{
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
world::{RAW, WorldEvent}, world::{RAW, WorldEvent},
}; };
use pumpkin_inventory::equipment_slot::EquipmentSlot; use pumpkin_inventory::{equipment_slot::EquipmentSlot, screen_handler::InventoryPlayer};
use pumpkin_macros::send_cancellable; use pumpkin_macros::send_cancellable;
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed}; use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
use pumpkin_protocol::{ use pumpkin_protocol::{
@ -68,8 +68,8 @@ use pumpkin_protocol::{
client::play::{ client::play::{
CBlockEntityData, CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate, CBlockEntityData, CEntityStatus, CGameEvent, CLogin, CMultiBlockUpdate,
CPlayerChatMessage, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, CPlayerChatMessage, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo,
CSoundEffect, CSpawnEntity, FilterType, GameEvent, InitChat, PlayerAction, CSetSelectedSlot, CSoundEffect, CSpawnEntity, FilterType, GameEvent, InitChat,
PlayerInfoFlags, PlayerAction, PlayerInfoFlags,
}, },
server::play::SChatMessage, server::play::SChatMessage,
}, },
@ -1040,6 +1040,13 @@ impl World {
} }
player.send_client_information().await; player.send_client_information().await;
// Sync selected slot
player
.enqueue_set_held_item_packet(&CSetSelectedSlot::new(
player.get_inventory().get_selected_slot() as i8,
))
.await;
// Start waiting for level chunks. Sets the "Loading Terrain" screen // Start waiting for level chunks. Sets the "Loading Terrain" screen
log::debug!("Sending waiting chunks to {}", player.gameprofile.name); log::debug!("Sending waiting chunks to {}", player.gameprofile.name);
player player