mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
i am being trolled by rust
for some reason some stuff is really slow for literally no reason and it makes no sense i am going insane
This commit is contained in:
parent
d41d6480ea
commit
2d1fd83f9f
10 changed files with 285 additions and 122 deletions
|
@ -35,12 +35,14 @@ use azalea_world::{
|
|||
WeakWorld, WeakWorldContainer, World,
|
||||
};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use parking_lot::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Debug,
|
||||
io::{self, Cursor},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::{
|
||||
|
@ -106,7 +108,7 @@ pub struct Client {
|
|||
pub write_conn: Arc<tokio::sync::Mutex<WriteConnection<ServerboundGamePacket>>>,
|
||||
pub entity_id: Arc<RwLock<u32>>,
|
||||
/// The world that this client has access to. This supports shared worlds.
|
||||
pub world: Arc<RwLock<World>>,
|
||||
pub world: Arc<Mutex<World>>,
|
||||
/// A container of world names to worlds. If we're not using a shared world
|
||||
/// (i.e. not a swarm), then this will only contain data about the world
|
||||
/// we're currently in.
|
||||
|
@ -188,7 +190,7 @@ impl Client {
|
|||
write_conn,
|
||||
// default our id to 0, it'll be set later
|
||||
entity_id: Arc::new(RwLock::new(0)),
|
||||
world: Arc::new(RwLock::new(World::default())),
|
||||
world: Arc::new(Mutex::new(World::default())),
|
||||
world_container: world_container
|
||||
.unwrap_or_else(|| Arc::new(RwLock::new(WeakWorldContainer::new()))),
|
||||
world_name: Arc::new(RwLock::new(None)),
|
||||
|
@ -489,10 +491,11 @@ impl Client {
|
|||
.insert(p.dimension.clone(), height, min_y);
|
||||
// set the loaded_world to an empty world
|
||||
// (when we add chunks or entities those will be in the world_container)
|
||||
let mut world_lock = client.world.write();
|
||||
let mut world_lock = client.world.lock();
|
||||
*world_lock = World::new(
|
||||
client.client_information.read().view_distance.into(),
|
||||
weak_world,
|
||||
p.player_id,
|
||||
);
|
||||
|
||||
let entity = EntityData::new(
|
||||
|
@ -500,6 +503,7 @@ impl Client {
|
|||
Vec3::default(),
|
||||
EntityMetadata::Player(metadata::Player::default()),
|
||||
);
|
||||
// make it so other entities don't update this entity in a shared world
|
||||
world_lock.add_entity(p.player_id, entity);
|
||||
|
||||
*client.entity_id.write() = p.player_id;
|
||||
|
@ -569,11 +573,9 @@ impl Client {
|
|||
let (new_pos, y_rot, x_rot) = {
|
||||
let player_entity_id = *client.entity_id.read();
|
||||
|
||||
let mut world_lock = client.world.write();
|
||||
let mut world_lock = client.world.lock();
|
||||
|
||||
let mut player_entity = world_lock
|
||||
.entity_mut(player_entity_id)
|
||||
.expect("Player entity doesn't exist");
|
||||
let mut player_entity = world_lock.entity_mut(player_entity_id).unwrap();
|
||||
|
||||
let delta_movement = player_entity.delta;
|
||||
|
||||
|
@ -747,7 +749,7 @@ impl Client {
|
|||
debug!("Got chunk cache center packet {:?}", p);
|
||||
client
|
||||
.world
|
||||
.write()
|
||||
.lock()
|
||||
.update_view_center(&ChunkPos::new(p.x, p.z));
|
||||
}
|
||||
ClientboundGamePacket::LevelChunkWithLight(p) => {
|
||||
|
@ -759,10 +761,10 @@ impl Client {
|
|||
// parse it again. This is only used when we have a shared
|
||||
// world, since we check that the chunk isn't currently owned
|
||||
// by this client.
|
||||
let shared_has_chunk = client.world.read().get_chunk(&pos).is_some();
|
||||
let shared_has_chunk = client.world.lock().get_chunk(&pos).is_some();
|
||||
let this_client_has_chunk = client
|
||||
.world
|
||||
.read()
|
||||
.lock()
|
||||
.chunk_storage
|
||||
.limited_get(&pos)
|
||||
.is_some();
|
||||
|
@ -778,7 +780,7 @@ impl Client {
|
|||
// debug("chunk {:?}")
|
||||
if let Err(e) = client
|
||||
.world
|
||||
.write()
|
||||
.lock()
|
||||
.replace_with_packet_data(&pos, &mut Cursor::new(&p.chunk_data.data))
|
||||
{
|
||||
error!("Couldn't set chunk data: {}", e);
|
||||
|
@ -790,15 +792,15 @@ impl Client {
|
|||
ClientboundGamePacket::AddEntity(p) => {
|
||||
debug!("Got add entity packet {:?}", p);
|
||||
let entity = EntityData::from(p);
|
||||
client.world.write().add_entity(p.id, entity);
|
||||
client.world.lock().add_entity(p.id, entity);
|
||||
}
|
||||
ClientboundGamePacket::SetEntityData(p) => {
|
||||
debug!("Got set entity data packet {:?}", p);
|
||||
let mut world = client.world.write();
|
||||
let mut world = client.world.lock();
|
||||
if let Some(mut entity) = world.entity_mut(p.id) {
|
||||
entity.apply_metadata(&p.packed_items.0);
|
||||
} else {
|
||||
warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
|
||||
// warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::UpdateAttributes(_p) => {
|
||||
|
@ -813,13 +815,13 @@ impl Client {
|
|||
ClientboundGamePacket::AddPlayer(p) => {
|
||||
debug!("Got add player packet {:?}", p);
|
||||
let entity = EntityData::from(p);
|
||||
client.world.write().add_entity(p.id, entity);
|
||||
client.world.lock().add_entity(p.id, entity);
|
||||
}
|
||||
ClientboundGamePacket::InitializeBorder(p) => {
|
||||
debug!("Got initialize border packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetTime(p) => {
|
||||
debug!("Got set time packet {:?}", p);
|
||||
// debug!("Got set time packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
|
||||
debug!("Got set default spawn position packet {:?}", p);
|
||||
|
@ -841,18 +843,26 @@ impl Client {
|
|||
debug!("Got set experience packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::TeleportEntity(p) => {
|
||||
let mut world_lock = client.world.write();
|
||||
let a_start = Instant::now();
|
||||
let mut world_lock = client.world.lock();
|
||||
let a_elapsed = a_start.elapsed();
|
||||
let b_start = Instant::now();
|
||||
let _ = world_lock.set_entity_pos(
|
||||
p.id,
|
||||
Vec3 {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
z: p.z,
|
||||
},
|
||||
);
|
||||
let b_elapsed = b_start.elapsed();
|
||||
|
||||
world_lock
|
||||
.set_entity_pos(
|
||||
p.id,
|
||||
Vec3 {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
z: p.z,
|
||||
},
|
||||
)
|
||||
.map_err(|e| HandleError::Other(e.into()))?;
|
||||
if a_start.elapsed() > Duration::from_millis(1) {
|
||||
warn!(
|
||||
"Set entity pos took too long: {:?} {:?}",
|
||||
a_elapsed, b_elapsed
|
||||
);
|
||||
}
|
||||
}
|
||||
ClientboundGamePacket::UpdateAdvancements(p) => {
|
||||
debug!("Got update advancements packet {:?}", p);
|
||||
|
@ -861,18 +871,14 @@ impl Client {
|
|||
// debug!("Got rotate head packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityPos(p) => {
|
||||
let mut world_lock = client.world.write();
|
||||
let mut world_lock = client.world.lock();
|
||||
|
||||
world_lock
|
||||
.move_entity_with_delta(p.entity_id, &p.delta)
|
||||
.map_err(|e| HandleError::Other(e.into()))?;
|
||||
let _ = world_lock.move_entity_with_delta(p.entity_id, &p.delta);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityPosRot(p) => {
|
||||
let mut world_lock = client.world.write();
|
||||
let mut world_lock = client.world.lock();
|
||||
|
||||
world_lock
|
||||
.move_entity_with_delta(p.entity_id, &p.delta)
|
||||
.map_err(|e| HandleError::Other(e.into()))?;
|
||||
let _ = world_lock.move_entity_with_delta(p.entity_id, &p.delta);
|
||||
}
|
||||
ClientboundGamePacket::MoveEntityRot(_p) => {
|
||||
// debug!("Got move entity rot packet {:?}", p);
|
||||
|
@ -887,7 +893,7 @@ impl Client {
|
|||
debug!("Got remove entities packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::PlayerChat(p) => {
|
||||
// debug!("Got player chat packet {:?}", p);
|
||||
debug!("Got player chat packet {:?}", p);
|
||||
tx.send(Event::Chat(ChatPacket::Player(Box::new(p.clone()))))
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -896,14 +902,14 @@ impl Client {
|
|||
tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap();
|
||||
}
|
||||
ClientboundGamePacket::Sound(p) => {
|
||||
debug!("Got sound packet {:?}", p);
|
||||
// debug!("Got sound packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::LevelEvent(p) => {
|
||||
debug!("Got level event packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::BlockUpdate(p) => {
|
||||
debug!("Got block update packet {:?}", p);
|
||||
let mut world = client.world.write();
|
||||
let mut world = client.world.lock();
|
||||
world.set_block_state(&p.pos, p.block_state);
|
||||
}
|
||||
ClientboundGamePacket::Animate(p) => {
|
||||
|
@ -911,7 +917,7 @@ impl Client {
|
|||
}
|
||||
ClientboundGamePacket::SectionBlocksUpdate(p) => {
|
||||
debug!("Got section blocks update packet {:?}", p);
|
||||
let mut world = client.world.write();
|
||||
let mut world = client.world.lock();
|
||||
for state in &p.states {
|
||||
world.set_block_state(&(p.section_pos + state.pos.clone()), state.state);
|
||||
}
|
||||
|
@ -1015,36 +1021,75 @@ impl Client {
|
|||
game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
|
||||
loop {
|
||||
game_tick_interval.tick().await;
|
||||
let start = Instant::now();
|
||||
Self::game_tick(&mut client, &tx).await;
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed > time::Duration::from_millis(50) {
|
||||
warn!("Game tick took too long: {:?}", elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs every 50 milliseconds.
|
||||
async fn game_tick(client: &mut Client, tx: &UnboundedSender<Event>) {
|
||||
// return if there's no chunk at the player's position
|
||||
let game_tick_start = Instant::now();
|
||||
|
||||
{
|
||||
let world_lock = client.world.write();
|
||||
let a_start = Instant::now();
|
||||
let world_lock = client.world.lock();
|
||||
let a_elapsed = a_start.elapsed();
|
||||
|
||||
let b_start = Instant::now();
|
||||
let player_entity_id = *client.entity_id.read();
|
||||
let b_elapsed = b_start.elapsed();
|
||||
|
||||
let c_start = Instant::now();
|
||||
let player_entity = world_lock.entity(player_entity_id);
|
||||
let player_entity = if let Some(player_entity) = player_entity {
|
||||
player_entity
|
||||
} else {
|
||||
let c_elapsed = c_start.elapsed();
|
||||
|
||||
let d_start = Instant::now();
|
||||
let Some(player_entity) = player_entity else {
|
||||
return;
|
||||
};
|
||||
let player_chunk_pos: ChunkPos = player_entity.pos().into();
|
||||
if world_lock.get_chunk(&player_chunk_pos).is_none() {
|
||||
return;
|
||||
}
|
||||
let d_elapsed = d_start.elapsed();
|
||||
|
||||
if a_start.elapsed() > time::Duration::from_millis(20) {
|
||||
warn!("a_elapsed: {:?}", a_elapsed);
|
||||
warn!("b_elapsed: {:?}", b_elapsed);
|
||||
warn!("c_elapsed: {:?}", c_elapsed);
|
||||
warn!("d_elapsed: {:?}", d_elapsed);
|
||||
}
|
||||
}
|
||||
let check_chunk_elapsed = game_tick_start.elapsed();
|
||||
|
||||
tx.send(Event::Tick).unwrap();
|
||||
|
||||
// TODO: if we're a passenger, send the required packets
|
||||
|
||||
let send_position_start = Instant::now();
|
||||
if let Err(e) = client.send_position().await {
|
||||
warn!("Error sending position: {:?}", e);
|
||||
}
|
||||
let send_position_elapsed = send_position_start.elapsed();
|
||||
let ai_step_start = Instant::now();
|
||||
client.ai_step();
|
||||
let ai_step_elapsed = ai_step_start.elapsed();
|
||||
|
||||
let game_tick_elapsed = game_tick_start.elapsed();
|
||||
if game_tick_elapsed > time::Duration::from_millis(50) {
|
||||
warn!(
|
||||
"(internal) game tick took too long: {:?}",
|
||||
game_tick_elapsed
|
||||
);
|
||||
warn!("check_chunk_elapsed: {:?}", check_chunk_elapsed);
|
||||
warn!("send_position_elapsed: {:?}", send_position_elapsed);
|
||||
warn!("ai_step_elapsed: {:?}", ai_step_elapsed);
|
||||
}
|
||||
|
||||
// TODO: minecraft does ambient sounds here
|
||||
}
|
||||
|
@ -1069,10 +1114,10 @@ impl Client {
|
|||
}
|
||||
|
||||
/// Returns the entity associated to the player.
|
||||
pub fn entity_mut(&self) -> Entity<RwLockWriteGuard<World>> {
|
||||
pub fn entity_mut(&self) -> Entity<MutexGuard<World>> {
|
||||
let entity_id = *self.entity_id.read();
|
||||
|
||||
let world = self.world.write();
|
||||
let world = self.world.lock();
|
||||
|
||||
let entity_data = world
|
||||
.entity_storage
|
||||
|
@ -1082,10 +1127,9 @@ impl Client {
|
|||
Entity::new(world, entity_id, entity_ptr)
|
||||
}
|
||||
/// Returns the entity associated to the player.
|
||||
pub fn entity(&self) -> Entity<RwLockReadGuard<World>> {
|
||||
pub fn entity(&self) -> Entity<MutexGuard<World>> {
|
||||
let entity_id = *self.entity_id.read();
|
||||
|
||||
let world = self.world.read();
|
||||
let world = self.world.lock();
|
||||
|
||||
let entity_data = world
|
||||
.entity_storage
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(trait_upcasting)]
|
||||
#![feature(error_generic_member_access)]
|
||||
#![feature(provide_any)]
|
||||
|
||||
mod account;
|
||||
mod chat;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::backtrace::Backtrace;
|
||||
|
||||
use crate::Client;
|
||||
use azalea_core::Vec3;
|
||||
use azalea_physics::collision::{MovableEntity, MoverType};
|
||||
|
@ -15,7 +17,7 @@ use thiserror::Error;
|
|||
#[derive(Error, Debug)]
|
||||
pub enum MovePlayerError {
|
||||
#[error("Player is not in world")]
|
||||
PlayerNotInWorld,
|
||||
PlayerNotInWorld(Backtrace),
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
@ -23,7 +25,9 @@ pub enum MovePlayerError {
|
|||
impl From<MoveEntityError> for MovePlayerError {
|
||||
fn from(err: MoveEntityError) -> Self {
|
||||
match err {
|
||||
MoveEntityError::EntityDoesNotExist => MovePlayerError::PlayerNotInWorld,
|
||||
MoveEntityError::EntityDoesNotExist(backtrace) => {
|
||||
MovePlayerError::PlayerNotInWorld(backtrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,9 +156,9 @@ impl Client {
|
|||
}
|
||||
|
||||
// Set our current position to the provided Vec3, potentially clipping through blocks.
|
||||
pub async fn set_pos(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> {
|
||||
pub async fn set_position(&mut self, new_pos: Vec3) -> Result<(), MovePlayerError> {
|
||||
let player_entity_id = *self.entity_id.read();
|
||||
let mut world_lock = self.world.write();
|
||||
let mut world_lock = self.world.lock();
|
||||
|
||||
world_lock.set_entity_pos(player_entity_id, new_pos)?;
|
||||
|
||||
|
@ -162,12 +166,12 @@ impl Client {
|
|||
}
|
||||
|
||||
pub async fn move_entity(&mut self, movement: &Vec3) -> Result<(), MovePlayerError> {
|
||||
let mut world_lock = self.world.write();
|
||||
let mut world_lock = self.world.lock();
|
||||
let player_entity_id = *self.entity_id.read();
|
||||
|
||||
let mut entity = world_lock
|
||||
.entity_mut(player_entity_id)
|
||||
.ok_or(MovePlayerError::PlayerNotInWorld)?;
|
||||
.ok_or(MovePlayerError::PlayerNotInWorld(Backtrace::capture()))?;
|
||||
log::trace!(
|
||||
"move entity bounding box: {} {:?}",
|
||||
entity.id,
|
||||
|
|
|
@ -22,7 +22,7 @@ const SECTION_HEIGHT: u32 = 16;
|
|||
/// distance. This has support for using a shared [`WeakChunkStorage`]. If you
|
||||
/// have an infinite render distance (like a server), you should use
|
||||
/// [`ChunkStorage`] instead.
|
||||
pub struct LimitedChunkStorage {
|
||||
pub struct PartialChunkStorage {
|
||||
/// Chunk storage that can be shared by clients.
|
||||
shared: Arc<RwLock<WeakChunkStorage>>,
|
||||
|
||||
|
@ -84,10 +84,10 @@ impl Default for Chunk {
|
|||
}
|
||||
}
|
||||
|
||||
impl LimitedChunkStorage {
|
||||
impl PartialChunkStorage {
|
||||
pub fn new(chunk_radius: u32, shared: Arc<RwLock<WeakChunkStorage>>) -> Self {
|
||||
let view_range = chunk_radius * 2 + 1;
|
||||
LimitedChunkStorage {
|
||||
PartialChunkStorage {
|
||||
shared,
|
||||
view_center: ChunkPos::new(0, 0),
|
||||
chunk_radius,
|
||||
|
@ -157,7 +157,7 @@ impl LimitedChunkStorage {
|
|||
}
|
||||
|
||||
/// Get a [`Chunk`] within render distance, or `None` if it's not loaded.
|
||||
/// Use [`LimitedChunkStorage::get`] to get a chunk from the shared storage.
|
||||
/// Use [`PartialChunkStorage::get`] to get a chunk from the shared storage.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the chunk is not in the render distance.
|
||||
|
@ -166,7 +166,7 @@ impl LimitedChunkStorage {
|
|||
&self.chunks[index]
|
||||
}
|
||||
/// Get a mutable reference to a [`Chunk`] within render distance, or
|
||||
/// `None` if it's not loaded. Use [`LimitedChunkStorage::get`] to get
|
||||
/// `None` if it's not loaded. Use [`PartialChunkStorage::get`] to get
|
||||
/// a chunk from the shared storage.
|
||||
///
|
||||
/// # Panics
|
||||
|
@ -293,7 +293,7 @@ impl McBufWritable for Chunk {
|
|||
}
|
||||
}
|
||||
|
||||
impl Debug for LimitedChunkStorage {
|
||||
impl Debug for PartialChunkStorage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ChunkStorage")
|
||||
.field("view_center", &self.view_center)
|
||||
|
@ -371,7 +371,7 @@ impl Section {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for LimitedChunkStorage {
|
||||
impl Default for PartialChunkStorage {
|
||||
fn default() -> Self {
|
||||
Self::new(8, Arc::new(RwLock::new(WeakChunkStorage::default())))
|
||||
}
|
||||
|
@ -401,7 +401,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_out_of_bounds_y() {
|
||||
let mut chunk_storage = LimitedChunkStorage::default();
|
||||
let mut chunk_storage = PartialChunkStorage::default();
|
||||
chunk_storage.set(
|
||||
&ChunkPos { x: 0, z: 0 },
|
||||
Some(Arc::new(Mutex::new(Chunk::default()))),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{WeakWorld, World};
|
||||
use crate::WeakWorld;
|
||||
use azalea_core::ResourceLocation;
|
||||
use log::error;
|
||||
use std::{
|
||||
|
|
|
@ -6,63 +6,101 @@ use parking_lot::RwLock;
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Weak},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Store a map of entities by ID. To get an iterator over all entities, use `storage.shared.read().entities`
|
||||
/// [`WeakEntityStorage::entities`]
|
||||
// How entity updates are processed (to avoid issues with shared worlds)
|
||||
// - each bot contains a map of { entity id: updates received }
|
||||
// - the shared world also contains a canonical "true" updates received for each entity
|
||||
// - when a client loads an entity, its "updates received" is set to the same as the global "updates received"
|
||||
// - when the shared world sees an entity for the first time, the "updates received" is set to 1.
|
||||
// - clients can force the shared "updates received" to 0 to make it so certain entities (i.e. other bots in our swarm) don't get confused and updated by other bots
|
||||
// - when a client gets an update to an entity, we check if our "updates received" is the same as the shared world's "updates received":
|
||||
// if it is, then process the update and increment the client's and shared world's "updates received"
|
||||
// if not, then we simply increment our local "updates received" and do nothing else
|
||||
|
||||
/// Store a map of entities by ID. To get an iterator over all entities, use
|
||||
/// `storage.shared.read().entities` [`WeakEntityStorage::entities`].
|
||||
///
|
||||
/// This is meant to be used with shared worlds.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EntityStorage {
|
||||
pub struct PartialEntityStorage {
|
||||
pub shared: Arc<RwLock<WeakEntityStorage>>,
|
||||
|
||||
/// The entity id of the player that owns this struct.
|
||||
pub owner_entity_id: u32,
|
||||
pub updates_received: IntMap<u32, u32>,
|
||||
/// Strong references to the entities we have loaded.
|
||||
_data_by_id: IntMap<u32, Arc<EntityData>>,
|
||||
data_by_id: IntMap<u32, Arc<EntityData>>,
|
||||
}
|
||||
|
||||
/// Weakly store entities in a world. If the entities aren't being referenced
|
||||
/// by anything else (like an [`EntityStorage`]), they'll be forgotten.
|
||||
/// by anything else (like an [`PartialEntityStorage`]), they'll be forgotten.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WeakEntityStorage {
|
||||
data_by_id: IntMap<u32, Weak<EntityData>>,
|
||||
/// An index of all the entity ids we know are in a chunk
|
||||
ids_by_chunk: HashMap<ChunkPos, IntSet<u32>>,
|
||||
/// An index of entity ids by their UUIDs
|
||||
id_by_uuid: HashMap<Uuid, u32>,
|
||||
|
||||
pub updates_received: IntMap<u32, u32>,
|
||||
}
|
||||
|
||||
impl EntityStorage {
|
||||
pub fn new(shared: Arc<RwLock<WeakEntityStorage>>) -> Self {
|
||||
impl PartialEntityStorage {
|
||||
pub fn new(shared: Arc<RwLock<WeakEntityStorage>>, owner_entity_id: u32) -> Self {
|
||||
shared.write().updates_received.insert(owner_entity_id, 0);
|
||||
Self {
|
||||
shared,
|
||||
_data_by_id: IntMap::default(),
|
||||
owner_entity_id,
|
||||
updates_received: IntMap::default(),
|
||||
data_by_id: IntMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an entity to the storage.
|
||||
#[inline]
|
||||
pub fn insert(&mut self, id: u32, entity: EntityData) {
|
||||
self.shared
|
||||
.write()
|
||||
// if the entity is already in the shared world, we don't need to do anything
|
||||
if self.shared.read().data_by_id.contains_key(&id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add the entity to the "indexes"
|
||||
let mut shared = self.shared.write();
|
||||
shared
|
||||
.ids_by_chunk
|
||||
.entry(ChunkPos::from(entity.pos()))
|
||||
.or_default()
|
||||
.insert(id);
|
||||
self.shared.write().id_by_uuid.insert(entity.uuid, id);
|
||||
shared.id_by_uuid.insert(entity.uuid, id);
|
||||
|
||||
// now store the actual entity data
|
||||
let entity = Arc::new(entity);
|
||||
|
||||
self.shared
|
||||
.write()
|
||||
.data_by_id
|
||||
.insert(id, Arc::downgrade(&entity));
|
||||
self._data_by_id.insert(id, entity);
|
||||
shared.data_by_id.insert(id, Arc::downgrade(&entity));
|
||||
self.data_by_id.insert(id, entity);
|
||||
// set our updates_received to the shared updates_received, unless it's
|
||||
// not there in which case set both to 1
|
||||
if let Some(&shared_updates_received) = shared.updates_received.get(&id) {
|
||||
// 0 means we're never tracking updates for this entity
|
||||
if shared_updates_received != 0 || id == self.owner_entity_id {
|
||||
self.updates_received.insert(id, 1);
|
||||
}
|
||||
} else {
|
||||
shared.updates_received.insert(id, 1);
|
||||
self.updates_received.insert(id, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an entity from this storage by its id. It will only be removed
|
||||
/// from the shared storage if there are no other references to it.
|
||||
#[inline]
|
||||
pub fn remove_by_id(&mut self, id: u32) {
|
||||
if let Some(entity) = self._data_by_id.remove(&id) {
|
||||
if let Some(entity) = self.data_by_id.remove(&id) {
|
||||
let chunk = ChunkPos::from(entity.pos());
|
||||
let uuid = entity.uuid;
|
||||
self.updates_received.remove(&id);
|
||||
drop(entity);
|
||||
// maybe remove it from the storage
|
||||
self.shared.write().remove_entity_if_unused(id, uuid, chunk);
|
||||
|
@ -76,7 +114,7 @@ impl EntityStorage {
|
|||
/// [`WeakEntityStorage::contains_id`].
|
||||
#[inline]
|
||||
pub fn limited_contains_id(&self, id: &u32) -> bool {
|
||||
self._data_by_id.contains_key(id)
|
||||
self.data_by_id.contains_key(id)
|
||||
}
|
||||
|
||||
/// Whether the entity with the given id is in the shared storage (i.e.
|
||||
|
@ -91,14 +129,36 @@ impl EntityStorage {
|
|||
/// Get a reference to an entity by its id, if it's being loaded by this storage.
|
||||
#[inline]
|
||||
pub fn limited_get_by_id(&self, id: u32) -> Option<&Arc<EntityData>> {
|
||||
self._data_by_id.get(&id)
|
||||
self.data_by_id.get(&id)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to an entity by its id, if it's being loaded by
|
||||
/// this storage.
|
||||
#[inline]
|
||||
pub fn limited_get_mut_by_id(&mut self, id: u32) -> Option<&mut Arc<EntityData>> {
|
||||
self._data_by_id.get_mut(&id)
|
||||
self.data_by_id.get_mut(&id)
|
||||
}
|
||||
|
||||
/// Returns whether we're allowed to update this entity (to prevent two clients in
|
||||
/// a shared world updating it twice), and acknowleges that we WILL update
|
||||
/// it if it's true. Don't call this unless you actually got an entity
|
||||
/// update that all other clients within render distance will get too.
|
||||
pub fn maybe_update(&mut self, id: u32) -> bool {
|
||||
let this_client_updates_received = self.updates_received.get(&id).copied();
|
||||
let shared_updates_received = self.shared.read().updates_received.get(&id).copied();
|
||||
|
||||
let can_update = this_client_updates_received == shared_updates_received;
|
||||
if can_update {
|
||||
let new_updates_received = this_client_updates_received.unwrap_or(0) + 1;
|
||||
self.updates_received.insert(id, new_updates_received);
|
||||
self.shared
|
||||
.write()
|
||||
.updates_received
|
||||
.insert(id, new_updates_received);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an entity in the shared storage by its id, if it exists.
|
||||
|
@ -119,7 +179,7 @@ impl EntityStorage {
|
|||
.read()
|
||||
.id_by_uuid
|
||||
.get(uuid)
|
||||
.and_then(|id| self._data_by_id.get(id))
|
||||
.and_then(|id| self.data_by_id.get(id))
|
||||
}
|
||||
|
||||
/// Get a mutable reference to an entity by its UUID, if it's being loaded
|
||||
|
@ -130,7 +190,7 @@ impl EntityStorage {
|
|||
.read()
|
||||
.id_by_uuid
|
||||
.get(uuid)
|
||||
.and_then(|id| self._data_by_id.get_mut(id))
|
||||
.and_then(|id| self.data_by_id.get_mut(id))
|
||||
}
|
||||
|
||||
/// Get an entity in the shared storage by its UUID, if it exists.
|
||||
|
@ -150,7 +210,7 @@ impl EntityStorage {
|
|||
pub fn clear_chunk(&mut self, chunk: &ChunkPos) {
|
||||
if let Some(entities) = self.shared.read().ids_by_chunk.get(chunk) {
|
||||
for id in entities.iter() {
|
||||
if let Some(entity) = self._data_by_id.remove(id) {
|
||||
if let Some(entity) = self.data_by_id.remove(id) {
|
||||
let uuid = entity.uuid;
|
||||
drop(entity);
|
||||
// maybe remove it from the storage
|
||||
|
@ -222,6 +282,7 @@ impl WeakEntityStorage {
|
|||
data_by_id: IntMap::default(),
|
||||
ids_by_chunk: HashMap::default(),
|
||||
id_by_uuid: HashMap::default(),
|
||||
updates_received: IntMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,6 +302,12 @@ impl WeakEntityStorage {
|
|||
"Tried to remove entity with id {id} from uuid {uuid:?} but it was not found."
|
||||
);
|
||||
}
|
||||
if self.updates_received.remove(&id).is_none() {
|
||||
// if this happens it means we weren't tracking the updates_received for the client (bad)
|
||||
warn!(
|
||||
"Tried to remove entity with id {id} from updates_received but it was not found."
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -290,7 +357,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_store_entity() {
|
||||
let mut storage = EntityStorage::default();
|
||||
let mut storage = PartialEntityStorage::default();
|
||||
assert!(storage.get_by_id(0).is_none());
|
||||
|
||||
let uuid = Uuid::from_u128(100);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#![feature(int_roundings)]
|
||||
#![feature(error_generic_member_access)]
|
||||
#![feature(provide_any)]
|
||||
|
||||
mod bit_storage;
|
||||
mod chunk_storage;
|
||||
|
@ -8,15 +10,17 @@ mod entity_storage;
|
|||
mod palette;
|
||||
mod world;
|
||||
|
||||
use std::backtrace::Backtrace;
|
||||
|
||||
pub use bit_storage::BitStorage;
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, LimitedChunkStorage, WeakChunkStorage};
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, WeakChunkStorage};
|
||||
pub use container::*;
|
||||
pub use entity_storage::{EntityStorage, WeakEntityStorage};
|
||||
pub use entity_storage::{PartialEntityStorage, WeakEntityStorage};
|
||||
use thiserror::Error;
|
||||
pub use world::*;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MoveEntityError {
|
||||
#[error("Entity doesn't exist")]
|
||||
EntityDoesNotExist,
|
||||
EntityDoesNotExist(Backtrace),
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
use crate::{
|
||||
entity::{Entity, EntityData},
|
||||
Chunk, EntityStorage, LimitedChunkStorage, MoveEntityError, WeakChunkStorage,
|
||||
Chunk, MoveEntityError, PartialChunkStorage, PartialEntityStorage, WeakChunkStorage,
|
||||
WeakEntityStorage,
|
||||
};
|
||||
use azalea_block::BlockState;
|
||||
use azalea_buf::BufReadError;
|
||||
use azalea_core::{BlockPos, ChunkPos, PositionDelta8, Vec3};
|
||||
use log::warn;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::fmt::Debug;
|
||||
use std::{
|
||||
backtrace::Backtrace,
|
||||
fmt::Debug,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{fmt::Formatter, io::Cursor, sync::Arc};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -18,8 +23,8 @@ pub struct World {
|
|||
// dropped, we don't need to do anything with it
|
||||
_shared: Arc<WeakWorld>,
|
||||
|
||||
pub chunk_storage: LimitedChunkStorage,
|
||||
pub entity_storage: EntityStorage,
|
||||
pub chunk_storage: PartialChunkStorage,
|
||||
pub entity_storage: PartialEntityStorage,
|
||||
}
|
||||
|
||||
/// A world where the chunks are stored as weak pointers. This is used for shared worlds.
|
||||
|
@ -30,11 +35,14 @@ pub struct WeakWorld {
|
|||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(chunk_radius: u32, shared: Arc<WeakWorld>) -> Self {
|
||||
pub fn new(chunk_radius: u32, shared: Arc<WeakWorld>, owner_entity_id: u32) -> Self {
|
||||
World {
|
||||
_shared: shared.clone(),
|
||||
chunk_storage: LimitedChunkStorage::new(chunk_radius, shared.chunk_storage.clone()),
|
||||
entity_storage: EntityStorage::new(shared.entity_storage.clone()),
|
||||
chunk_storage: PartialChunkStorage::new(chunk_radius, shared.chunk_storage.clone()),
|
||||
entity_storage: PartialEntityStorage::new(
|
||||
shared.entity_storage.clone(),
|
||||
owner_entity_id,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,18 +77,30 @@ impl World {
|
|||
}
|
||||
|
||||
pub fn set_entity_pos(&mut self, entity_id: u32, new_pos: Vec3) -> Result<(), MoveEntityError> {
|
||||
let mut entity = self
|
||||
.entity_mut(entity_id)
|
||||
.ok_or(MoveEntityError::EntityDoesNotExist)?;
|
||||
let a_start = Instant::now();
|
||||
let mut entity = self.entity_mut(entity_id).ok_or_else(|| {
|
||||
warn!("!!! a_elapsed: {:?}", a_start.elapsed());
|
||||
MoveEntityError::EntityDoesNotExist(Backtrace::capture())
|
||||
})?;
|
||||
let a_elapsed = a_start.elapsed();
|
||||
|
||||
let b_start = Instant::now();
|
||||
let old_chunk = ChunkPos::from(entity.pos());
|
||||
let new_chunk = ChunkPos::from(&new_pos);
|
||||
// this is fine because we update the chunk below
|
||||
unsafe { entity.move_unchecked(new_pos) };
|
||||
let b_elapsed = b_start.elapsed();
|
||||
let c_start = Instant::now();
|
||||
if old_chunk != new_chunk {
|
||||
self.entity_storage
|
||||
.update_entity_chunk(entity_id, &old_chunk, &new_chunk);
|
||||
}
|
||||
let c_elapsed = c_start.elapsed();
|
||||
|
||||
warn!(
|
||||
"!!! a_elapsed: {:?}, b_elapsed: {:?}, c_elapsed: {:?}",
|
||||
a_elapsed, b_elapsed, c_elapsed
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -89,9 +109,15 @@ impl World {
|
|||
entity_id: u32,
|
||||
delta: &PositionDelta8,
|
||||
) -> Result<(), MoveEntityError> {
|
||||
let owner_entity_id = self.entity_storage.owner_entity_id;
|
||||
let mut entity = self
|
||||
.entity_mut(entity_id)
|
||||
.ok_or(MoveEntityError::EntityDoesNotExist)?;
|
||||
.ok_or_else(|| MoveEntityError::EntityDoesNotExist(Backtrace::capture()))?;
|
||||
if entity_id == owner_entity_id {
|
||||
println!("moving entity {} (self)", entity_id);
|
||||
} else {
|
||||
println!("moving entity {}", entity_id);
|
||||
}
|
||||
let new_pos = entity.pos().with_delta(delta);
|
||||
|
||||
let old_chunk = ChunkPos::from(entity.pos());
|
||||
|
@ -128,7 +154,13 @@ impl World {
|
|||
Some(Entity::new(self, id, entity_ptr))
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the entity with the given ID.
|
||||
pub fn entity_mut(&mut self, id: u32) -> Option<Entity<'_, &mut World>> {
|
||||
// no entity for you (we're processing this entity somewhere else)
|
||||
if id != self.entity_storage.owner_entity_id && !self.entity_storage.maybe_update(id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entity_data = self.entity_storage.get_by_id(id)?;
|
||||
let entity_ptr = unsafe { entity_data.as_ptr() };
|
||||
Some(Entity::new(self, id, entity_ptr))
|
||||
|
|
|
@ -80,7 +80,7 @@ impl Trait for azalea_client::Client {
|
|||
let successors = |node: &Node| {
|
||||
let mut edges = Vec::new();
|
||||
|
||||
let world = self.world.read();
|
||||
let world = self.world.lock();
|
||||
for possible_move in possible_moves.iter() {
|
||||
edges.push(Edge {
|
||||
target: possible_move.next_node(node),
|
||||
|
@ -131,7 +131,7 @@ fn tick_execute_path(bot: &mut Client, path: &mut VecDeque<Node>) {
|
|||
}
|
||||
|
||||
if target.is_reached(&bot.entity()) {
|
||||
println!("ok target {target:?} reached");
|
||||
// println!("ok target {target:?} reached");
|
||||
path.pop_front();
|
||||
if path.is_empty() {
|
||||
bot.walk(WalkDirection::None);
|
||||
|
@ -169,13 +169,13 @@ impl Node {
|
|||
/// Returns whether the entity is at the node and should start going to the
|
||||
/// next node.
|
||||
pub fn is_reached(&self, entity: &EntityData) -> bool {
|
||||
println!(
|
||||
"entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}",
|
||||
entity.delta.y,
|
||||
BlockPos::from(entity.pos()),
|
||||
self.pos,
|
||||
self.vertical_vel
|
||||
);
|
||||
// println!(
|
||||
// "entity.delta.y: {} {:?}=={:?}, self.vertical_vel={:?}",
|
||||
// entity.delta.y,
|
||||
// BlockPos::from(entity.pos()),
|
||||
// self.pos,
|
||||
// self.vertical_vel
|
||||
// );
|
||||
BlockPos::from(entity.pos()) == self.pos
|
||||
&& match self.vertical_vel {
|
||||
VerticalVel::NoneMidair => entity.delta.y > -0.1 && entity.delta.y < 0.1,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use azalea::pathfinder::BlockPosGoal;
|
||||
use azalea::{prelude::*, BlockPos, Swarm, SwarmEvent};
|
||||
use azalea::{prelude::*, BlockPos, Swarm, SwarmEvent, WalkDirection};
|
||||
use azalea::{Account, Client, Event};
|
||||
use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket;
|
||||
use std::time::Duration;
|
||||
|
@ -69,7 +69,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
|
||||
async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
|
||||
match event {
|
||||
Event::Login => {
|
||||
bot.chat("Hello world").await?;
|
||||
|
@ -80,17 +80,28 @@ async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()>
|
|||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
bot.disconnect().await?;
|
||||
}
|
||||
if m.message().to_string() == "<py5> goto" {
|
||||
let entity = bot
|
||||
.world
|
||||
.read()
|
||||
.entity_by_uuid(&uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd"));
|
||||
println!("entity: {:?}", entity);
|
||||
if let Some(entity) = entity {
|
||||
let entity = bot
|
||||
.world
|
||||
.lock()
|
||||
.entity_by_uuid(&uuid::uuid!("6536bfed-8695-48fd-83a1-ecd24cf2a0fd"));
|
||||
if let Some(entity) = entity {
|
||||
if m.content() == "goto" {
|
||||
let target_pos_vec3 = entity.pos();
|
||||
let target_pos: BlockPos = target_pos_vec3.into();
|
||||
println!("target_pos: {:?}", target_pos);
|
||||
bot.goto(BlockPosGoal::from(target_pos));
|
||||
} else if m.content() == "look" {
|
||||
let target_pos_vec3 = entity.pos();
|
||||
let target_pos: BlockPos = target_pos_vec3.into();
|
||||
println!("target_pos: {:?}", target_pos);
|
||||
bot.look_at(&target_pos.center());
|
||||
} else if m.content() == "jump" {
|
||||
bot.set_jumping(true);
|
||||
} else if m.content() == "walk" {
|
||||
bot.walk(WalkDirection::Forward);
|
||||
} else if m.content() == "stop" {
|
||||
bot.set_jumping(false);
|
||||
bot.walk(WalkDirection::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +133,6 @@ async fn swarm_handle(
|
|||
SwarmEvent::Chat(m) => {
|
||||
println!("swarm chat message: {}", m.message().to_ansi(None));
|
||||
if m.message().to_string() == "<py5> world" {
|
||||
let worlds = swarm.worlds.read();
|
||||
for (name, world) in &swarm.worlds.read().worlds {
|
||||
println!("world name: {}", name);
|
||||
if let Some(w) = world.upgrade() {
|
||||
|
|
Loading…
Add table
Reference in a new issue