From 163ae45ae1da04820be573233fd2929206db0566 Mon Sep 17 00:00:00 2001 From: Andrew Brower Date: Thu, 28 Nov 2024 15:20:29 -0500 Subject: [PATCH] fix(player): only open window on interaction with container --- src/main.rs | 4 +- src/net/cache.rs | 70 +++++++++++++++++++++++++++++----- src/net/io.rs | 7 +--- src/net/player.rs | 34 ++++++++++++----- src/protocol/datatypes/slot.rs | 3 ++ src/protocol/mod.rs | 2 +- src/server/mod.rs | 38 ++++++++++++------ src/server/ticker.rs | 4 +- src/state.rs | 19 ++++++++- src/world/container.rs | 23 +++++++++++ src/world/mod.rs | 2 + 11 files changed, 164 insertions(+), 42 deletions(-) create mode 100644 src/world/container.rs diff --git a/src/main.rs b/src/main.rs index 1e7f6ee..62eeb97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use std::{fs::OpenOptions, sync::Arc}; use args::Args; use clap::Parser; use color_eyre::eyre::Result; -use net::cache::WorldCache; +use net::cache::{RegistryCache, WorldCache}; use server::Server; use tracing_subscriber::{layer::SubscriberExt, prelude::*, EnvFilter}; use world::read_world; @@ -77,7 +77,7 @@ async fn main() -> Result<()> { let state = Arc::new(state::State::new(VERSION, VERSION_NUM, args)); info!("Generating world chunk packets"); - let world_cache = WorldCache::from_anvil(state.clone(), world); + let world_cache = WorldCache::from_anvil(state.clone(), &world); info!("Done."); #[cfg(feature = "lan")] diff --git a/src/net/cache.rs b/src/net/cache.rs index db24d00..896ea0b 100644 --- a/src/net/cache.rs +++ b/src/net/cache.rs @@ -17,7 +17,7 @@ * . */ -use std::cmp::Ordering; +use std::{cmp::Ordering, collections::HashMap}; use rayon::prelude::*; @@ -30,17 +30,18 @@ use crate::{ }, Encoder, }, - world::{blocks::Blocks, World}, + world::{blocks::Blocks, BlockEntity, Container, World}, CrawlState, }; #[derive(Debug)] pub struct WorldCache { pub encoded: Vec>, + pub containers: HashMap<(i32, i32, i32), Container>, } impl WorldCache { - pub fn from_anvil(crawlstate: CrawlState, world: World) -> Self { + pub fn from_anvil(crawlstate: CrawlState, world: &World) -> Self { let mut chunks = world.0.iter().collect::>(); chunks.sort_by(|((ax, az), _), ((bx, bz), _)| { @@ -53,23 +54,72 @@ impl WorldCache { let block_states = Blocks::new(); - let chunks = chunks - .par_iter() - .map(|(_, c)| ChunkDataUpdateLightC::new(crawlstate.clone(), c, &block_states)) - .collect::>>(); + let containers = chunks + .iter() + .map(|(_, c)| { + c.block_entities + .iter() + .filter_map(|block_entity| { + // TODO: cache this somewhere so block entities aren't parsed twice on startup + let block_entity = BlockEntity::try_parse((*block_entity).clone()) + .map_or_else( + |why| { + warn!( + "Failed to parse block entity: {why}, ignoring in container cache for ({}, {})", + c.x_pos, + c.z_pos, + ); + None + }, + |e| match e.keep_packed { + true => None, + false => Some(e), + }, + ); + + let Some(block_entity) = block_entity else { + return None; + }; + + match block_entity.id.as_str() { + "minecraft:chest" | "minecraft:trapped_chest" | "minecraft:barrel" => { + Some(block_entity) + } + _ => None, + } + }) + .map(|container| { + ( + (container.x, container.y, container.z), + Container(Vec::new()), + ) + }) + .collect::>() + }) + .flatten() + .collect(); + + info!("Containers: {:?}", containers); let encoded = chunks .par_iter() - .map(|chunk| { + .map(|(_, chunk)| { let mut encoder = Encoder::new(); encoder - .append_packet(chunk) + .append_packet(&ChunkDataUpdateLightC::new( + crawlstate.clone(), + chunk, + &block_states, + )) .expect("Failed to append packet to encoder"); encoder.take().to_vec() }) .collect(); - Self { encoded } + Self { + encoded, + containers, + } } } diff --git a/src/net/io.rs b/src/net/io.rs index 91669ac..5619c09 100644 --- a/src/net/io.rs +++ b/src/net/io.rs @@ -38,7 +38,6 @@ pub struct NetIo { pub connected: RwLock, read_half: Mutex, write_half: Mutex, - frame: Mutex, decoder: Mutex, encoder: Mutex, } @@ -67,10 +66,6 @@ impl NetIo { connected: RwLock::new(true), read_half: Mutex::new(read_half), write_half: Mutex::new(write_half), - frame: Mutex::new(Frame { - id: -1, - body: BytesMut::new(), - }), decoder: Mutex::new(protocol::Decoder::new()), encoder: Mutex::new(protocol::Encoder::new()), } @@ -100,6 +95,8 @@ impl NetIo { continue; } + // TODO: decode here, rather than forcing the consumer to do it. + // probably need to box frame data? idk enough rust for this return Ok(frame); }; diff --git a/src/net/player.rs b/src/net/player.rs index ef88929..bbb8bb7 100644 --- a/src/net/player.rs +++ b/src/net/player.rs @@ -558,19 +558,33 @@ impl SharedPlayer { } async fn handle_use_item(&self, packet: UseItemOnS) -> Result<()> { - let id = self.0.next_window_id.fetch_add(1, Ordering::Relaxed); + let crawlstate = self.0.crawlstate.clone(); + let server = crawlstate.get_server().await; - let window = Window { - id, - kind: WindowType::Generic9x3, - title: "Hi".into(), - }; + let x = packet.location.x as i32; + let y = packet.location.y as i32; + let z = packet.location.z as i32; - self.0.io.tx(&OpenScreenC::from(&window)).await?; + debug!("Player {} clicked at {}, {}, {}", self.id(), x, y, z); - { - let mut sw = self.0.window.write().await; - *sw = Some(window); + match server.get_container(x, y, z) { + None => (), + Some(container) => { + let id = self.0.next_window_id.fetch_add(1, Ordering::Relaxed); + + let window = Window { + id, + kind: WindowType::Generic9x3, + title: "Hi".into(), + }; + + self.0.io.tx(&OpenScreenC::from(&window)).await?; + + { + let mut sw = self.0.window.write().await; + *sw = Some(window); + } + } } Ok(()) diff --git a/src/protocol/datatypes/slot.rs b/src/protocol/datatypes/slot.rs index 367c4e5..f1053b6 100644 --- a/src/protocol/datatypes/slot.rs +++ b/src/protocol/datatypes/slot.rs @@ -16,3 +16,6 @@ * License along with Crawlspace. If not, see * . */ + +#[derive(Debug, Clone)] +pub struct Slot; diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 52479b8..6a6848c 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -27,7 +27,7 @@ pub mod datatypes { pub use impls::*; pub use position::*; - + pub use slot::*; pub use string::*; pub use text_component::*; pub use variable::*; diff --git a/src/server/mod.rs b/src/server/mod.rs index 4151ab5..1a48d25 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -23,9 +23,13 @@ pub mod window; use std::{ collections::{HashMap, HashSet}, sync::Arc, + time::Duration, }; use color_eyre::eyre::Result; + +use tokio::sync::Mutex; + #[cfg(feature = "timings")] use tokio::time::Instant; @@ -34,6 +38,7 @@ use crate::{ cache::WorldCache, player::{SharedPlayer, TeleportError}, }, + world::{Container, World}, CrawlState, }; @@ -44,37 +49,45 @@ pub struct Server { pub ticker: Ticker, world_cache: Arc, - players: HashMap, + players: Mutex>, crawlstate: CrawlState, } impl Server { #[must_use] - pub fn new(state: CrawlState, world_cache: WorldCache, tick_rate: u8) -> Self { - Server { + pub fn new(state: CrawlState, world_cache: WorldCache, tick_rate: u8) -> Arc { + let server = Arc::new(Server { ticker: Ticker::new(tick_rate), world_cache: Arc::new(world_cache), - players: HashMap::new(), - crawlstate: state, - } + players: Mutex::new(HashMap::new()), + crawlstate: state.clone(), + }); + + let state_server = server.clone(); + tokio::spawn(async move { + state.set_server(state_server).await; + }); + + server } - async fn tick(&mut self) { + async fn tick(&self) { #[cfg(feature = "timings")] let run_start = Instant::now(); let state = self.crawlstate.clone(); let mut player_recv = state.player_recv.lock().await; + let mut players = self.players.lock().await; while let Ok(p) = player_recv.try_recv() { - self.players.insert(p.0.id, p.clone()); + players.insert(p.0.id, p.clone()); tokio::spawn(Self::send_world_to(p.clone(), self.world_cache.clone())); } let mut invalid_players: HashSet = HashSet::new(); - for (id, player) in &self.players { + for (id, player) in &*players { let _ = player.keepalive().await; match player.handle_all_packets().await { @@ -103,13 +116,12 @@ impl Server { for id in invalid_players { // TODO: kick player properly - self.players.remove(&id); + players.remove(&id); } #[cfg(feature = "timings")] { let run_end = Instant::now(); - debug!("Tick took {}ms", (run_start - run_end).as_millis()); debug!("Tick took {}ms", (run_end - run_start).as_millis()); } } @@ -121,4 +133,8 @@ impl Server { Ok(()) } + + pub fn get_container(&self, x: i32, y: i32, z: i32) -> Option { + self.world_cache.containers.get(&(x, y, z)).cloned() + } } diff --git a/src/server/ticker.rs b/src/server/ticker.rs index cd0ca5a..03b905a 100644 --- a/src/server/ticker.rs +++ b/src/server/ticker.rs @@ -17,7 +17,7 @@ * . */ -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use tokio::time::{sleep, Instant}; @@ -36,7 +36,7 @@ impl Ticker { } } - pub async fn run(&mut self, mut server: super::Server) { + pub async fn run(&mut self, server: Arc) { loop { let now = Instant::now(); let elapsed = now - self.last_tick; diff --git a/src/state.rs b/src/state.rs index 55580b8..46abb4e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -19,13 +19,14 @@ use std::sync::{atomic::AtomicUsize, Arc}; -use tokio::sync::{mpsc, Mutex, Semaphore}; +use tokio::sync::{mpsc, Mutex, RwLock, Semaphore}; use tokio_util::sync::CancellationToken; use crate::{ args::Args, net::{cache::RegistryCache, player::SharedPlayer}, protocol::packets::login::registry::ALL_REGISTRIES, + server::Server, }; #[derive(Debug)] @@ -49,6 +50,8 @@ pub struct State { pub spawnpoint: (f64, f64, f64), pub border_radius: i32, + + server: RwLock>>, } impl State { @@ -83,6 +86,20 @@ impl State { spawnpoint: (args.spawn_x, args.spawn_y, args.spawn_z), border_radius: args.border_radius, + + server: RwLock::new(None), } } + + pub async fn set_server(&self, server: Arc) { + let mut write = self.server.write().await; + *write = Some(server); + } + + pub async fn get_server(&self) -> Arc { + let server = self.server.read().await; + server + .clone() + .expect("state.get_server called before server initialized") + } } diff --git a/src/world/container.rs b/src/world/container.rs new file mode 100644 index 0000000..57d98ca --- /dev/null +++ b/src/world/container.rs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Andrew Brower. + * This file is part of Crawlspace. + * + * Crawlspace is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * Crawlspace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with Crawlspace. If not, see + * . + */ + +use crate::protocol::datatypes::Slot; + +#[derive(Debug, Clone)] +pub struct Container(pub Vec); diff --git a/src/world/mod.rs b/src/world/mod.rs index 3d41777..f8e5269 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -26,8 +26,10 @@ use serde::Deserialize; pub mod block_entity; pub mod blocks; +pub mod container; pub use block_entity::*; +pub use container::*; #[derive(Clone, Debug)] pub struct World(pub HashMap<(i32, i32), Chunk>);