Made everything thread safe

- Also improved gamemode command
- We now can use Server in commands
This commit is contained in:
Snowiiii 2024-08-25 13:20:49 +02:00
parent ddd3f1e781
commit 83aad86005
16 changed files with 114 additions and 68 deletions

View File

@ -37,8 +37,8 @@ impl<'a> ClientPacket for CPlayerInfoUpdate<'a> {
});
}
PlayerAction::InitializeChat(_) => todo!(),
PlayerAction::UpdateGameMode(_) => todo!(),
PlayerAction::UpdateListed { listed } => p.put_bool(*listed),
PlayerAction::UpdateGameMode(gamemode) => p.put_var_int(gamemode),
PlayerAction::UpdateListed(listed) => p.put_bool(*listed),
PlayerAction::UpdateLatency(_) => todo!(),
PlayerAction::UpdateDisplayName(_) => todo!(),
}

View File

@ -1,4 +1,4 @@
use crate::Property;
use crate::{Property, VarInt};
pub enum PlayerAction {
AddPlayer {
@ -6,10 +6,10 @@ pub enum PlayerAction {
properties: Vec<Property>,
},
InitializeChat(u8),
UpdateGameMode(u8),
UpdateListed {
listed: bool,
},
/// Gamemode ?
UpdateGameMode(VarInt),
/// Listed ?
UpdateListed(bool),
UpdateLatency(u8),
UpdateDisplayName(u8),
}

View File

@ -2,6 +2,7 @@ use std::{
io::{self, Write},
net::SocketAddr,
rc::Rc,
sync::Arc,
};
use crate::{
@ -70,7 +71,7 @@ pub struct Client {
pub connection_state: ConnectionState,
pub encryption: bool,
pub closed: bool,
pub token: Rc<Token>,
pub token: Arc<Token>,
pub connection: TcpStream,
pub address: SocketAddr,
enc: PacketEncoder,
@ -81,7 +82,7 @@ pub struct Client {
}
impl Client {
pub fn new(token: Rc<Token>, connection: TcpStream, address: SocketAddr) -> Self {
pub fn new(token: Arc<Token>, connection: TcpStream, address: SocketAddr) -> Self {
Self {
protocol_version: 0,
gameprofile: None,

View File

@ -174,8 +174,8 @@ impl Player {
server.broadcast_packet(self, &CHeadRot::new(entity_id.into(), yaw as u8));
}
pub fn handle_chat_command(&mut self, _server: &mut Server, command: SChatCommand) {
handle_command(&mut CommandSender::Player(self), &command.command);
pub fn handle_chat_command(&mut self, server: &mut Server, command: SChatCommand) {
handle_command(&mut CommandSender::Player(self), server, &command.command);
}
pub fn handle_player_ground(&mut self, _server: &mut Server, ground: SSetPlayerGround) {

View File

@ -60,12 +60,12 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 2).with_child(
argument(ARG_GAMEMODE, consume_arg_gamemode)
.with_child(
require(&|sender| sender.is_player()).execute(&|sender, args| {
.with_child(require(&|sender| sender.is_player()).execute(
&|sender, server, args| {
let gamemode = parse_arg_gamemode(args)?;
return if let Player(target) = sender {
target.set_gamemode(gamemode);
target.set_gamemode(server, gamemode);
target.send_system_message(TextComponent::text(&format!(
"Game mode was set to {:?}",
gamemode
@ -75,22 +75,22 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
} else {
Err(InvalidRequirementError)
};
}),
)
.with_child(
argument(ARG_TARGET, consume_arg_player).execute(&|sender, args| {
},
))
.with_child(argument(ARG_TARGET, consume_arg_player).execute(
&|sender, server, args| {
let gamemode = parse_arg_gamemode(args)?;
let target = parse_arg_player(sender, ARG_TARGET, args)?;
target.set_gamemode(gamemode);
target.set_gamemode(server, gamemode);
target.send_system_message(TextComponent::text(&format!(
"Set own game mode to {:?}",
gamemode
)));
Ok(())
}),
),
},
)),
),
)
}

View File

@ -35,7 +35,7 @@ fn parse_arg_command<'a>(
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION)
.with_child(
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, args| {
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, _, args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
let tree = parse_arg_command(args, dispatcher)?;
@ -50,7 +50,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
Ok(())
}),
)
.execute(&|sender, _args| {
.execute(&|sender, _, _args| {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
let mut keys: Vec<&str> = dispatcher.commands.keys().copied().collect();

View File

@ -9,7 +9,7 @@ const NAMES: [&str; 1] = ["pumpkin"];
const DESCRIPTION: &str = "Display information about Pumpkin.";
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, _| {
CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, _, _| {
let version = env!("CARGO_PKG_VERSION");
let description = env!("CARGO_PKG_DESCRIPTION");

View File

@ -10,7 +10,7 @@ const DESCRIPTION: &str = "Stop the server.";
pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 4).execute(&|sender, _args| {
require(&|sender| sender.permission_lvl() >= 4).execute(&|sender, _, _args| {
sender
.send_message(TextComponent::text("Stopping Server").color_named(NamedColor::Red));
std::process::exit(0)

View File

@ -3,6 +3,7 @@ use crate::commands::dispatcher::InvalidTreeError::{
};
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, NodeType, RawArgs};
use crate::commands::CommandSender;
use crate::server::Server;
use std::collections::HashMap;
#[derive(Debug)]
@ -23,7 +24,12 @@ pub(crate) struct CommandDispatcher<'a> {
/// Stores registered [CommandTree]s and dispatches commands to them.
impl<'a> CommandDispatcher<'a> {
/// Execute a command using its corresponding [CommandTree].
pub(crate) fn dispatch(&'a self, src: &mut CommandSender, cmd: &str) -> Result<(), String> {
pub(crate) fn dispatch(
&'a self,
src: &mut CommandSender,
server: &mut Server,
cmd: &str,
) -> Result<(), String> {
let mut parts = cmd.split_ascii_whitespace();
let key = parts.next().ok_or("Empty Command")?;
let raw_args: Vec<&str> = parts.rev().collect();
@ -32,7 +38,7 @@ impl<'a> CommandDispatcher<'a> {
// try paths until fitting path is found
for path in tree.iter_paths() {
match Self::try_is_fitting_path(src, path, tree, raw_args.clone()) {
match Self::try_is_fitting_path(src, server, path, tree, raw_args.clone()) {
Err(InvalidConsumptionError(s)) => {
println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed");
return Err("Internal Error (See logs for details)".into());
@ -69,6 +75,7 @@ impl<'a> CommandDispatcher<'a> {
fn try_is_fitting_path(
src: &mut CommandSender,
server: &mut Server,
path: Vec<usize>,
tree: &CommandTree,
mut raw_args: RawArgs,
@ -79,7 +86,7 @@ impl<'a> CommandDispatcher<'a> {
match node.node_type {
NodeType::ExecuteLeaf { run } => {
return if raw_args.is_empty() {
run(src, &parsed_args)?;
run(src, server, &parsed_args)?;
Ok(true)
} else {
Ok(false)

View File

@ -4,6 +4,7 @@ use std::sync::OnceLock;
use crate::commands::dispatcher::CommandDispatcher;
use crate::entity::player::Player;
use crate::server::Server;
mod arg_player;
mod cmd_gamemode;
mod cmd_help;
@ -83,10 +84,10 @@ fn dispatcher_init<'a>() -> CommandDispatcher<'a> {
dispatcher
}
pub fn handle_command(sender: &mut CommandSender, cmd: &str) {
pub fn handle_command(sender: &mut CommandSender, server: &mut Server, cmd: &str) {
let dispatcher = DISPATCHER.get_or_init(dispatcher_init);
if let Err(err) = dispatcher.dispatch(sender, cmd) {
if let Err(err) = dispatcher.dispatch(sender, server, cmd) {
sender.send_message(
TextComponent::text(&err).color_named(pumpkin_core::text::color::NamedColor::Red),
)

View File

@ -2,6 +2,7 @@ use std::collections::{HashMap, VecDeque};
use crate::commands::dispatcher::InvalidTreeError;
use crate::commands::CommandSender;
use crate::server::Server;
/// see [crate::commands::tree_builder::argument]
pub(crate) type RawArgs<'a> = Vec<&'a str>;
@ -19,7 +20,8 @@ pub(crate) struct Node<'a> {
pub(crate) enum NodeType<'a> {
ExecuteLeaf {
run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync),
run: &'a (dyn Fn(&mut CommandSender, &mut Server, &ConsumedArgs) -> Result<(), InvalidTreeError>
+ Sync),
},
#[allow(dead_code)] // todo: remove (so far no commands requiring this are implemented)
Literal { string: &'a str },

View File

@ -1,6 +1,7 @@
use crate::commands::dispatcher::InvalidTreeError;
use crate::commands::tree::{ArgumentConsumer, CommandTree, ConsumedArgs, Node, NodeType};
use crate::commands::CommandSender;
use crate::server::Server;
impl<'a> CommandTree<'a> {
/// Add a child [Node] to the root of this [CommandTree].
@ -41,7 +42,8 @@ impl<'a> CommandTree<'a> {
/// Also see [NonLeafNodeBuilder::execute].
pub fn execute(
mut self,
run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync),
run: &'a (dyn Fn(&mut CommandSender, &mut Server, &ConsumedArgs) -> Result<(), InvalidTreeError>
+ Sync),
) -> Self {
let node = Node {
node_type: NodeType::ExecuteLeaf { run },
@ -117,7 +119,8 @@ impl<'a> NonLeafNodeBuilder<'a> {
/// Also see [CommandTree::execute].
pub fn execute(
mut self,
run: &'a (dyn Fn(&mut CommandSender, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync),
run: &'a (dyn Fn(&mut CommandSender, &mut Server, &ConsumedArgs) -> Result<(), InvalidTreeError>
+ Sync),
) -> Self {
self.leaf_nodes.push(LeafNodeBuilder {
node_type: NodeType::ExecuteLeaf { run },

View File

@ -7,7 +7,10 @@ use pumpkin_entity::{entity_type::EntityType, Entity, EntityId};
use pumpkin_inventory::player::PlayerInventory;
use pumpkin_protocol::{
bytebuf::{packet_id::Packet, DeserializerError},
client::play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage},
client::play::{
CGameEvent, CPlayDisconnect, CPlayerInfoUpdate, CSyncPlayerPosition, CSystemChatMessage,
PlayerAction,
},
server::play::{
SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract,
SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation,
@ -139,8 +142,18 @@ impl Player {
self.food_saturation = food_saturation;
}
pub fn set_gamemode(&mut self, gamemode: GameMode) {
pub fn set_gamemode(&mut self, server: &mut Server, gamemode: GameMode) {
self.gamemode = gamemode;
server.broadcast_packet(
self,
&CPlayerInfoUpdate::new(
0x04,
&[pumpkin_protocol::client::play::Player {
uuid: self.client.gameprofile.as_ref().unwrap().id,
actions: vec![PlayerAction::UpdateGameMode((self.gamemode as i32).into())],
}],
),
);
self.client
.send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap()));
}

View File

@ -31,6 +31,8 @@ static ALLOC: dhat::Alloc = dhat::Alloc;
#[cfg(not(target_os = "wasi"))]
fn main() -> io::Result<()> {
use std::{borrow::BorrowMut, sync::{Arc, Mutex}};
use entity::player::Player;
use pumpkin_core::text::{color::NamedColor, TextComponent};
@ -95,12 +97,16 @@ fn main() -> io::Result<()> {
let rcon = advanced_configuration.rcon.clone();
let mut clients: HashMap<Token, Client> = HashMap::new();
let mut players: HashMap<Rc<Token>, Rc<RefCell<Player>>> = HashMap::new();
let mut players: HashMap<Arc<Token>, Arc<Mutex<Player>>> = HashMap::new();
let mut server = Server::new((basic_config, advanced_configuration));
let mut server = Arc::new(Mutex::new(Server::new((
basic_config,
advanced_configuration,
))));
log::info!("Started Server took {}ms", time.elapsed().as_millis());
log::info!("You now can connect to the server, Listening on {}", addr);
let server1 = server.clone();
if use_console {
thread::spawn(move || {
let stdin = std::io::stdin();
@ -111,15 +117,17 @@ fn main() -> io::Result<()> {
.expect("Failed to read console line");
if !out.is_empty() {
handle_command(&mut commands::CommandSender::Console, &out);
let mut server = server1.lock().unwrap();
handle_command(&mut commands::CommandSender::Console, &mut server, &out);
}
}
});
}
if rcon.enabled {
tokio::spawn(async move {
RCONServer::new(&rcon).await.unwrap();
});
let server = server.clone();
// tokio::spawn(async move {
// RCONServer::new(&rcon, &server).await.unwrap();
// });
}
loop {
if let Err(err) = poll.poll(&mut events, None) {
@ -161,16 +169,17 @@ fn main() -> io::Result<()> {
token,
Interest::READABLE.add(Interest::WRITABLE),
)?;
let rc_token = Rc::new(token);
let client = Client::new(Rc::clone(&rc_token), connection, addr);
let rc_token = Arc::new(token);
let client = Client::new(Arc::clone(&rc_token), connection, addr);
clients.insert(token, client);
},
token => {
// Poll Players
let done = if let Some(player) = players.get_mut(&token) {
let mut player = player.borrow_mut();
let mut player = player.lock().unwrap();
player.client.poll(event).await;
let mut server = server.lock().unwrap();
player.process_packets(&mut server);
player.client.closed
} else {
@ -179,8 +188,9 @@ fn main() -> io::Result<()> {
if done {
if let Some(player) = players.remove(&token) {
let mut server = server.lock().unwrap();
server.remove_player(&token);
let mut player = player.borrow_mut();
let mut player = player.lock().unwrap();
poll.registry().deregister(&mut player.client.connection)?;
}
}
@ -189,6 +199,7 @@ fn main() -> io::Result<()> {
// Maybe received an event for a TCP connection.
let (done, make_player) = if let Some(client) = clients.get_mut(&token) {
client.poll(event).await;
let mut server = server.lock().unwrap();
client.process_packets(&mut server).await;
(client.closed, client.make_player)
} else {
@ -201,9 +212,10 @@ fn main() -> io::Result<()> {
poll.registry().deregister(&mut client.connection)?;
} else if make_player {
let token = client.token.clone();
let mut server = server.lock().unwrap();
let player = server.add_player(token.clone(), client);
players.insert(token, player.clone());
let mut player = player.borrow_mut();
let mut player = player.lock().unwrap();
server.spawn_player(&mut player).await;
}
}

View File

@ -1,6 +1,7 @@
use std::{
collections::HashMap,
io::{self, Read},
sync::{Arc, Mutex},
};
use mio::{
@ -10,7 +11,7 @@ use mio::{
use packet::{Packet, PacketError, PacketType};
use thiserror::Error;
use crate::{commands::handle_command, config::RCONConfig};
use crate::{commands::handle_command, config::RCONConfig, server::Server};
mod packet;
@ -29,7 +30,7 @@ const SERVER: Token = Token(0);
pub struct RCONServer {}
impl RCONServer {
pub async fn new(config: &RCONConfig) -> Result<Self, io::Error> {
pub async fn new(config: &RCONConfig, server: &Arc<Mutex<Server>>) -> Result<Self, io::Error> {
assert!(config.enabled, "RCON is not enabled");
let addr = format!("{}:{}", config.ip, config.port)
.parse()
@ -87,7 +88,7 @@ impl RCONServer {
token => {
let done = if let Some(client) = connections.get_mut(&token) {
client.handle(&password).await
client.handle(&server, &password).await
} else {
false
};
@ -126,7 +127,7 @@ impl RCONClient {
}
}
pub async fn handle(&mut self, password: &str) -> bool {
pub async fn handle(&mut self, server: &Arc<Mutex<Server>>, password: &str) -> bool {
if !self.closed {
loop {
match self.read_bytes() {
@ -141,7 +142,7 @@ impl RCONClient {
}
}
// If we get a close here, we might have a reply, which we still want to write.
match self.poll(password).await {
match self.poll(server, password).await {
Ok(()) => {}
Err(e) => {
log::error!("rcon error: {e}");
@ -152,7 +153,11 @@ impl RCONClient {
self.closed
}
async fn poll(&mut self, password: &str) -> Result<(), PacketError> {
async fn poll(
&mut self,
server: &Arc<Mutex<Server>>,
password: &str,
) -> Result<(), PacketError> {
loop {
let packet = match self.receive_packet().await? {
Some(p) => p,
@ -183,8 +188,10 @@ impl RCONClient {
PacketType::ExecCommand => {
if self.logged_in {
let mut output = Vec::new();
let mut server = server.lock().unwrap();
handle_command(
&mut crate::commands::CommandSender::Rcon(&mut output),
&mut server,
packet.get_body(),
);
for line in output {

View File

@ -6,7 +6,7 @@ use std::{
rc::Rc,
sync::{
atomic::{AtomicI32, Ordering},
Arc,
Arc, Mutex, MutexGuard,
},
time::Duration,
};
@ -33,7 +33,7 @@ use pumpkin_world::{dimension::Dimension, radial_chunk_iterator::RadialIterator,
use pumpkin_registry::Registry;
use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use serde::{Deserialize, Serialize};
use tokio::sync::{mpsc, Mutex};
use tokio::sync::mpsc;
use crate::{
client::Client,
@ -48,7 +48,7 @@ pub struct Server {
pub private_key: RsaPrivateKey,
pub public_key_der: Box<[u8]>,
pub world: Arc<Mutex<World>>,
pub world: Arc<tokio::sync::Mutex<World>>,
pub status_response: StatusResponse,
// We cache the json response here so we don't parse it every time someone makes a Status request.
// Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves
@ -60,7 +60,7 @@ pub struct Server {
/// Cache the registry so we don't have to parse it every time a player joins
pub cached_registry: Vec<Registry>,
pub current_players: HashMap<Rc<Token>, Rc<RefCell<Player>>>,
pub current_players: HashMap<Arc<Token>, Arc<Mutex<Player>>>,
// TODO: replace with HashMap <World, Player>
entity_id: AtomicI32, // TODO: place this into every world
@ -108,7 +108,7 @@ impl Server {
cached_registry: Registry::get_static(),
// 0 is invalid
entity_id: 2.into(),
world: Arc::new(Mutex::new(world)),
world: Arc::new(tokio::sync::Mutex::new(world)),
public_key,
cached_server_brand,
private_key,
@ -122,20 +122,20 @@ impl Server {
}
}
pub fn add_player(&mut self, token: Rc<Token>, client: Client) -> Rc<RefCell<Player>> {
pub fn add_player(&mut self, token: Arc<Token>, client: Client) -> Arc<Mutex<Player>> {
let entity_id = self.new_entity_id();
let gamemode = match self.base_config.default_gamemode {
GameMode::Undefined => GameMode::Survival,
game_mode => game_mode,
};
let player = Rc::new(RefCell::new(Player::new(client, entity_id, gamemode)));
let player = Arc::new(Mutex::new(Player::new(client, entity_id, gamemode)));
self.current_players.insert(token, player.clone());
player
}
pub fn remove_player(&mut self, token: &Token) {
let player = self.current_players.remove(token).unwrap();
let player = player.as_ref().borrow();
let player = player.as_ref().lock().unwrap();
// despawn the player
// todo: put this into the entitiy struct
let id = player.entity_id();
@ -204,7 +204,7 @@ impl Server {
name: gameprofile.name.clone(),
properties: gameprofile.properties.clone(),
},
PlayerAction::UpdateListed { listed: true },
PlayerAction::UpdateListed(true),
],
}],
),
@ -217,7 +217,7 @@ impl Server {
.iter()
.filter(|c| c.0 != &player.client.token)
{
let playerr = playerr.as_ref().borrow();
let playerr = playerr.as_ref().lock().unwrap();
let gameprofile = &playerr.client.gameprofile.as_ref().unwrap();
entries.push(pumpkin_protocol::client::play::Player {
uuid: gameprofile.id,
@ -226,7 +226,7 @@ impl Server {
name: gameprofile.name.clone(),
properties: gameprofile.properties.clone(),
},
PlayerAction::UpdateListed { listed: true },
PlayerAction::UpdateListed(true),
],
})
}
@ -262,7 +262,7 @@ impl Server {
// spawn players for our client
let token = player.client.token.clone();
for (_, existing_player) in self.current_players.iter().filter(|c| c.0 != &token) {
let existing_player = existing_player.as_ref().borrow();
let existing_player = existing_player.as_ref().lock().unwrap();
let entity = &existing_player.entity;
let gameprofile = existing_player.client.gameprofile.as_ref().unwrap();
player.client.send_packet(&CSpawnEntity::new(
@ -297,13 +297,13 @@ impl Server {
}
/// TODO: This definitly should be in world
pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option<RefMut<Player>> {
pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option<MutexGuard<Player>> {
for (_, player) in self
.current_players
.iter()
.filter(|c| c.0 != &from.client.token)
{
let player = player.borrow_mut();
let player = player.lock().unwrap();
if player.entity_id() == id {
return Some(player);
}
@ -323,7 +323,7 @@ impl Server {
.iter()
.filter(|c| c.0 != &from.client.token)
{
let mut player = player.borrow_mut();
let mut player = player.lock().unwrap();
player.client.send_packet(packet);
}
}
@ -338,7 +338,7 @@ impl Server {
.iter()
.filter(|c| !from.contains(&c.0.as_ref()))
{
let mut player = player.borrow_mut();
let mut player = player.lock().unwrap();
player.client.send_packet(packet);
}
}