diff options
| author | 1992414357@qq.com <1992414357@qq.com> | 2025-06-09 01:44:36 +0800 |
|---|---|---|
| committer | 1992414357@qq.com <1992414357@qq.com> | 2025-06-09 01:44:36 +0800 |
| commit | 49192dbb98e0ab1f2a66b4786fac86d63f69be64 (patch) | |
| tree | 7cdf27c7cab5fb6b1aabc2ac7c44b7b492558945 /core/src | |
| parent | c9dbba0d288becb7f05cebe526be25c76e5a850a (diff) | |
重构所有部分
Diffstat (limited to 'core/src')
53 files changed, 1966 insertions, 2196 deletions
diff --git a/core/src/data/controller/cli/cli_command.rs b/core/src/data/controller/cli/cli_command.rs new file mode 100644 index 0000000..b5c0414 --- /dev/null +++ b/core/src/data/controller/cli/cli_command.rs @@ -0,0 +1,48 @@ +use std::sync::{Arc, Mutex}; +use clap::{Parser, Subcommand}; +use nogamepads::entry_mutex; +use crate::data::controller::runtime::structs::ControllerRuntime; +use crate::data::message::enums::ControlMessage; +use crate::data::message::traits::MessageManager; +use crate::service::service_types::ServiceType::TCPConnection; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct ControllerCli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + + #[command(about = "Clean the screen")] + Clear, + + #[command(about = "Send a message")] + Message, + + #[command(about = "Close the controller")] + Close +} + +pub fn process_controller_cli(runtime: Arc<Mutex<ControllerRuntime>>, cmd: ControllerCli) { + + match cmd.command { + Commands::Clear => { + + } + + Commands::Message => { + entry_mutex!(runtime, |guard| { + guard.send(ControlMessage::Msg("fuck".to_string()), 0, TCPConnection); + }); + } + + Commands::Close => { + entry_mutex!(runtime, |guard| { + guard.close(); + }); + } + } +}
\ No newline at end of file diff --git a/core/src/data/controller/cli/mod.rs b/core/src/data/controller/cli/mod.rs new file mode 100644 index 0000000..043f7b8 --- /dev/null +++ b/core/src/data/controller/cli/mod.rs @@ -0,0 +1 @@ +pub mod cli_command;
\ No newline at end of file diff --git a/core/src/data/controller/implements.rs b/core/src/data/controller/implements.rs new file mode 100644 index 0000000..86a19de --- /dev/null +++ b/core/src/data/controller/implements.rs @@ -0,0 +1,21 @@ +use std::sync::{Arc, Mutex}; +use crate::data::controller::runtime::structs::ControllerRuntime; +use crate::data::controller::structs::ControllerData; +use crate::data::player::structs::Player; + +impl ControllerData { + + pub fn bind_player(&mut self, player: Player) -> &mut ControllerData { + self.player = player; + self + } + + /// Build the controller-side runtime using controller data + pub fn runtime(self) -> Arc<Mutex<ControllerRuntime>> { + let runtime = ControllerRuntime { + player: self.player, + ..Default::default() + }; + Arc::new(Mutex::new(runtime)) + } +}
\ No newline at end of file diff --git a/core/src/data/controller/mod.rs b/core/src/data/controller/mod.rs new file mode 100644 index 0000000..019ebcf --- /dev/null +++ b/core/src/data/controller/mod.rs @@ -0,0 +1,4 @@ +pub mod cli; +pub mod runtime; +pub mod implements; +pub mod structs;
\ No newline at end of file diff --git a/core/src/data/controller/runtime/implements.rs b/core/src/data/controller/runtime/implements.rs new file mode 100644 index 0000000..b79890e --- /dev/null +++ b/core/src/data/controller/runtime/implements.rs @@ -0,0 +1,29 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::Ordering::SeqCst; +use log::trace; +use crate::data::controller::runtime::structs::ControllerRuntime; +use crate::data::message::enums::{ControlMessage, GameMessage}; +use crate::data::message::traits::MessageManager; +use crate::service::service_types::ServiceType; + +/// Message manager for controller-side runtime +/// After the service starts, it can be accessed or relevant messages can be stored. +impl MessageManager<GameMessage, ControlMessage, u8> for ControllerRuntime { + fn borrow_received_list_mut(&mut self) -> &mut HashMap<(ServiceType, u8), VecDeque<GameMessage>> { + &mut self.received + } + + fn borrow_send_list_mut(&mut self) -> &mut HashMap<(ServiceType, u8), VecDeque<ControlMessage>> { + &mut self.send + } +} + +impl ControllerRuntime { + + pub fn close(&mut self) { + if !self.close.load(SeqCst) { + self.close.store(true, SeqCst); + trace!("[Controller Runtime] Closed."); + } + } +}
\ No newline at end of file diff --git a/core/src/data/controller/runtime/mod.rs b/core/src/data/controller/runtime/mod.rs new file mode 100644 index 0000000..0ff870f --- /dev/null +++ b/core/src/data/controller/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod implements; +pub mod structs;
\ No newline at end of file diff --git a/core/src/data/controller/runtime/structs.rs b/core/src/data/controller/runtime/structs.rs new file mode 100644 index 0000000..7be2cd6 --- /dev/null +++ b/core/src/data/controller/runtime/structs.rs @@ -0,0 +1,20 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::AtomicBool; +use crate::data::game::types::GameInfo; +use crate::data::message::enums::{ControlMessage, GameMessage}; +use crate::data::player::structs::Player; +use crate::service::service_types::ServiceType; + +/// Controller-side runtime +/// Stores all data involved in game pad_client interactions during runtime +#[derive(Default)] +pub struct ControllerRuntime { + + pub(crate) received: HashMap<(ServiceType, u8), VecDeque<GameMessage>>, + pub(crate) send: HashMap<(ServiceType, u8), VecDeque<ControlMessage>>, + + pub(crate) player: Player, + + pub game_info: GameInfo, + pub close: AtomicBool +}
\ No newline at end of file diff --git a/core/src/data/controller/structs.rs b/core/src/data/controller/structs.rs new file mode 100644 index 0000000..c6fdd31 --- /dev/null +++ b/core/src/data/controller/structs.rs @@ -0,0 +1,10 @@ +use crate::data::player::structs::Player; + +/// Controller-side Data +/// Describes the basic information of the controller side +#[derive(Default)] +pub struct ControllerData { + + /// Player bound to the controller side + pub(crate) player: Player +}
\ No newline at end of file diff --git a/core/src/data/game/cli/cli_command.rs b/core/src/data/game/cli/cli_command.rs new file mode 100644 index 0000000..1d79515 --- /dev/null +++ b/core/src/data/game/cli/cli_command.rs @@ -0,0 +1,35 @@ +use std::sync::{Arc, Mutex}; +use clap::{Parser, Subcommand}; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::GameRuntime; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct GameCli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + + #[command(about = "Clean the screen")] + Clear, + + #[command(about = "Close the game")] + Close +} + +pub fn process_game_cli(runtime: Arc<Mutex<GameRuntime>>, cmd: GameCli) { + match cmd.command { + Commands::Clear => { + + } + + Commands::Close => { + entry_mutex!(runtime, |guard| { + guard.close_game(); + }) + } + } +}
\ No newline at end of file diff --git a/core/src/data/game/cli/mod.rs b/core/src/data/game/cli/mod.rs new file mode 100644 index 0000000..043f7b8 --- /dev/null +++ b/core/src/data/game/cli/mod.rs @@ -0,0 +1 @@ +pub mod cli_command;
\ No newline at end of file diff --git a/core/src/data/game/implements.rs b/core/src/data/game/implements.rs new file mode 100644 index 0000000..ad86572 --- /dev/null +++ b/core/src/data/game/implements.rs @@ -0,0 +1,99 @@ +use std::sync::{Arc, Mutex}; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::{GameControlRuntime, GameRuntime, GameRuntimeData}; +use crate::data::game::structs::{GameControlData, GameData, GameRuntimeDataArchive}; +use crate::data::game::types::{GameInfo, Players}; +use crate::data::player::structs::Player; + +impl Default for GameData { + fn default() -> Self { + GameData::new() + } +} + +impl GameData { + + /// Create new game data + pub fn new() -> GameData { + let mut game = GameData { + info: GameInfo::default(), + control: GameControlData::default(), + archive: GameRuntimeDataArchive::default(), + }; + + game.name("Mini Hero".to_string()); + game.version(env!("PROJECT_VERSION").to_string()); + game + } + + /// Add or modify game name information + pub fn name(&mut self, name: String) -> &mut GameData { + self.info("Game_Name".to_string(), name); + self + } + + /// Add or modify game version information + pub fn version(&mut self, version: String) -> &mut GameData { + self.info("Version".to_string(), version); + self + } + + /// Add or modify information for a specific entry + pub fn info(&mut self, name: String, value: String) -> &mut GameData { + self.info.insert(name, value); + self + } + + /// Read game runtime archive data + pub fn load_data(&mut self, storage: GameRuntimeDataArchive) -> &mut GameData { + self.archive = storage; + self + } + + /// Build the game-side runtime using game data + pub fn runtime(self) -> Arc<Mutex<GameRuntime>> { + let runtime = GameRuntime { + info: self.info, + data: self.archive.into(), + control: GameControlRuntime { + keys: self.control, + ..Default::default() + }, + + writer_count: 0, + reader_count: 0, + }; + Arc::new(Mutex::new(runtime)) + } +} + +impl From<GameRuntimeDataArchive> for GameRuntimeData { + fn from(archive: GameRuntimeDataArchive) -> Self { + let banned_mutex = Players::default(); + entry_mutex!(banned_mutex, |guard| { + for account in archive.banned { + let player_info = Player::from(account.clone()); + guard.entry(account).or_insert_with(|| player_info); + } + }); + GameRuntimeData { + players_banned : banned_mutex, + ..Self::default() + } + } +} + +impl From<GameRuntimeData> for GameRuntimeDataArchive { + fn from(data: GameRuntimeData) -> Self { + let mut banned = Vec::new(); + entry_mutex!(data.players_online, |guard| { + for account in guard.keys().into_iter() { + banned.push(account.to_owned()); + } + }); + + GameRuntimeDataArchive { + banned + } + } +}
\ No newline at end of file diff --git a/core/src/data/game/mod.rs b/core/src/data/game/mod.rs new file mode 100644 index 0000000..4a0a3d4 --- /dev/null +++ b/core/src/data/game/mod.rs @@ -0,0 +1,6 @@ +pub mod cli; +pub mod runtime; + +pub mod implements; +pub mod structs; +pub mod types;
\ No newline at end of file diff --git a/core/src/data/game/runtime/implements.rs b/core/src/data/game/runtime/implements.rs new file mode 100644 index 0000000..62038ba --- /dev/null +++ b/core/src/data/game/runtime/implements.rs @@ -0,0 +1,324 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering::SeqCst; +use log::{info, trace, warn}; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::{GameControlRuntime, GameRuntime, GameRuntimeData}; +use crate::data::game::types::Players; +use crate::data::message::enums::{JoinFailedMessage, ControlMessage, ExitReason, GameMessage}; +use crate::data::message::enums::JoinFailedMessage::{ContainIdenticalPlayer, GameLocked, PlayerBanned}; +use crate::data::message::enums::ControlMessage::{Axis, Dir, Msg, Pressed, Released}; +use crate::data::message::enums::GameMessage::LetExit; +use crate::data::message::traits::MessageManager; +use crate::data::player::structs::{Account, Player}; +use crate::service::service_types::ServiceType; +use crate::service::service_types::ServiceType::TCPConnection; + +impl GameRuntime { + + /// Attempt to have the specified player join the game + pub fn try_join_player(&mut self, player: Player) -> Result<(), JoinFailedMessage> { + let join = self.can_join_game(&player.account); + match join { + Ok(_) => { + self.data.sign_player_online_status(&player, TCPConnection, true); + trace!("[Game Runtime] Player \"{}\" joined", player.account); + Ok(()) + } + Err(why) => { + warn!("[Game Runtime] Player \"{}\" join failed: {:?}", player.account, why); + Err(why) + } + } + } + + fn can_join_game(&self, account: &Account) -> Result<bool, JoinFailedMessage> { + + if self.is_game_locked() { + Err(GameLocked) + } else if self.data.is_account_banned(account) { + Err(PlayerBanned) + } else if self.data.is_account_online(account) { + Err(ContainIdenticalPlayer) + } else { + Ok(true) + } + } + + /// Request an account to exit + pub fn let_account_exit(&mut self, account: &Account, reason: ExitReason, service_type: ServiceType) { + // Send a leave message to the pad_client and wait for it to actively disconnect + if self.data.is_account_online(account) { + self.send((account.clone(), LetExit(reason)), account.clone(), service_type); + } + } + + /// Check if the game is locked + pub fn is_game_locked(&self) -> bool { + self.data.locked.load(SeqCst) + } + + /// Lock the game + pub fn lock_game(&self) { + if !self.data.locked.load(SeqCst) { + self.data.locked.store(true, SeqCst); + info!("[Game Runtime] Game locked!"); + } + } + + /// Unlock the game + pub fn unlock_game(&self) { + if self.data.locked.load(SeqCst) { + self.data.locked.store(false, SeqCst); + info!("[Game Runtime] Game unlocked!"); + } + } + + /// Close the Game + pub fn close_game(&self) { + if !self.data.close.load(SeqCst) { + self.data.close.store(true, SeqCst); + info!("[Game Runtime] Game closed!"); + } + } +} + +/// Message manager for game pad_client runtime +/// After the service starts, it can be accessed or relevant messages can be stored. +impl MessageManager<(Account, ControlMessage), (Account, GameMessage), Account> for GameRuntime { + fn borrow_received_list_mut(&mut self) -> &mut HashMap<(ServiceType, Account), VecDeque<(Account, ControlMessage)>> { + &mut self.data.received + } + + fn borrow_send_list_mut(&mut self) -> &mut HashMap<(ServiceType, Account), VecDeque<(Account, GameMessage)>> { + &mut self.data.send + } + + fn pop_from_send_list(&mut self, key: Account, service: ServiceType) -> Option<(Account, GameMessage)> { + let key = (service, key); + self.borrow_send_list_mut() + .entry(key) + .or_insert_with(VecDeque::new) + .pop_front() + } + + fn put_into_receive_list(&mut self, message: (Account, ControlMessage), _key: Account, _service: ServiceType) { + let result = self.control.process_control_message(&message.0, message.1); + if result.is_err() { + let result = result.unwrap_err(); + warn!("[Game Runtime] Can't process message: {:?}", result); + drop(result); + } + } +} + +impl Default for GameRuntimeData { + fn default() -> Self { + Self { + received: Default::default(), + send: Default::default(), + players_online: Players::default(), + players_banned: Players::default(), + + locked: AtomicBool::new(false), + close: AtomicBool::new(false) + } + } +} + +impl GameRuntimeData { + + /// Mark a player as online + pub fn sign_player_online_status(&mut self, player: &Player, service_type: ServiceType, value: bool) { + let online = self.is_account_online(&player.account); + if online && !value { + + // Remove player + entry_mutex!(self.players_online, |guard| { + guard.remove_entry(&player.account); + }); + + info!("[Game Runtime] Signed player \"{}\" is [OFFLINE]!", player.account); + + // Reset runtime + let key = (service_type, player.account.clone()); + let get_received = self.received.get_mut(&key); + let get_send = self.send.get_mut(&key); + if let Some(mut list) = get_received { + list.clear(); + } + if let Some(mut list) = get_send { + list.clear(); + } + + } else if !online & value { + + // Insert player + entry_mutex!(self.players_online, |guard| { + guard.entry(player.account.clone()) + .or_insert_with(|| player.clone()); + }); + + info!("[Game Runtime] Signed player \"{}\" is [ONLINE]!", player.account); + } + } + + /// Returns all online accounts + pub fn online_accounts(&self) -> Vec<Account> { + let mut vec = Vec::new(); + entry_mutex!(self.players_online, |guard| { + for account in guard.keys().into_iter() { + vec.push(account.clone()); + } + }); + vec + } + + /// Check if specified account is online + pub fn is_account_online(&self, account: &Account) -> bool { + entry_mutex!(self.players_online, |guard| { + if guard.contains_key(account) { + return true; + } + }); + false + } + + /// Returns all banned accounts + pub fn banned_accounts(&self) -> Vec<Account> { + let mut vec = Vec::new(); + entry_mutex!(self.players_banned, |guard| { + for account in guard.keys().into_iter() { + vec.push(account.clone()); + } + }); + vec + } + + /// Check if account is banned + pub fn is_account_banned(&self, account: &Account) -> bool { + entry_mutex!(self.players_banned, |guard| { + if guard.contains_key(account) { + true; + } + }); + false + } +} + +impl GameControlRuntime { + + /// Process a control message + fn process_control_message(&mut self, who: &Account, msg: ControlMessage) -> Result<(), ControlMessage> { + match msg { + Msg(_) => { + self.send_event(who, msg); + Ok(()) + } + + Pressed(button_key) => { + let key_valid = self.check_key(&self.keys.button_keys, &button_key); + if key_valid { + Self::change_value(&mut self.button, button_key, who, true); + self.send_event(who, msg); + trace!("[Control Runtime] Player \"{}\" pressed btn_{}", &who.id, button_key); + } else { + warn!("[Control Runtime] Key btn_{} not registered!", button_key); + } + Ok(()) + } + + Released(button_key) => { + if self.check_key(&self.keys.button_keys, &button_key) { + Self::change_value(&mut self.button, button_key, who, false); + self.send_event(who, msg); + trace!("[Control Runtime] Player \"{}\" released btn_{}", &who.id, button_key); + } else { + warn!("[Control Runtime] Key btn_{} not registered!", button_key); + } + Ok(()) + } + + Axis(axis_key, axis) => { + if self.check_key(&self.keys.button_keys, &axis_key) { + Self::change_value(&mut self.axes, axis_key, who, axis); + trace!("[Control Runtime] Player \"{}\" changed ax_{} to ({})", &who.id, axis_key, axis); + } else { + warn!("[Control Runtime] Key ax_{} not registered!", axis_key); + } + Ok(()) + } + + Dir(dir_key, dir) => { + if self.check_key(&self.keys.button_keys, &dir_key) { + Self::change_value(&mut self.directions, dir_key, who, dir); + trace!("[Control Runtime] Player \"{}\" changed dir_{} to ({}, {})", &who.id, dir_key, dir.0, dir.1); + } else { + warn!("[Control Runtime] Key dir_{} not registered!", dir_key); + } + Ok(()) + } + + _ => { + Err(msg) + } + } + } + + /// Pop an event message + pub fn pop_event(&mut self, game_runtime: &GameRuntime) -> Option<(Account, ControlMessage)> { + let pop = self.events.pop_front(); + if pop.is_some() { + let (account, msg) = pop.unwrap(); + if game_runtime.data.is_account_online(&account) { + trace!("[Control Runtime] Message: {:?} from \"{}\" ", &msg, account); + Some((account, msg)) + } else { + warn!("[Control Runtime] Invalid message: Player \"{}\" is not online!", account); + None + } + } else { + None + } + } + + /// Get specified player's direction value + pub fn get_direction(&self, who: &Account, key: &u8) -> Option<(f64, f64)> { + Self::get(&self.directions, who, key) + } + + /// Get specified player's axis value + pub fn get_axis(&self, who: &Account, key: &u8) -> Option<f64> { + Self::get(&self.axes, who, key) + } + + /// Get specified player's button status + pub fn get_button_status(&self, who: &Account, key: &u8) -> Option<bool> { + Self::get(&self.button, who, key) + } + + fn check_key(&self, map: &HashMap<u8, String>, key: &u8) -> bool { + map.contains_key(key) + } + + fn get<V: Clone>(map: &HashMap<u8, HashMap<Account, V>>, who: &Account, key: &u8) -> Option<V> { + let key = map.get(key); + if key.is_some() { + let value = key.unwrap().get(who); + if value.is_some() { + let result = value.unwrap(); + Some(result.clone()) + } else { None } + } else { None } + } + + fn change_value<T>(map: &mut HashMap<u8, HashMap<Account, T>>, key: u8, who: &Account, msg: T) { + map.entry(key) + .or_insert_with(HashMap::new) + .insert(who.clone(), msg); + } + + fn send_event(&mut self, who: &Account, msg: ControlMessage) { + self.events.push_back((who.clone(), msg)); + } +}
\ No newline at end of file diff --git a/core/src/data/game/runtime/mod.rs b/core/src/data/game/runtime/mod.rs new file mode 100644 index 0000000..0ff870f --- /dev/null +++ b/core/src/data/game/runtime/mod.rs @@ -0,0 +1,2 @@ +pub mod implements; +pub mod structs;
\ No newline at end of file diff --git a/core/src/data/game/runtime/structs.rs b/core/src/data/game/runtime/structs.rs new file mode 100644 index 0000000..bf68424 --- /dev/null +++ b/core/src/data/game/runtime/structs.rs @@ -0,0 +1,40 @@ +use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::AtomicBool; +use crate::data::game::structs::GameControlData; +use crate::data::game::types::{GameInfo, Players}; +use crate::data::message::enums::{ControlMessage, GameMessage}; +use crate::data::player::structs::Account; +use crate::service::service_types::ServiceType; + +/// Game pad_client runtime +/// Stores the game state, player information, and all data involved in controller-side interactions during runtime +pub struct GameRuntime { + + pub info: GameInfo, + pub data: GameRuntimeData, + pub control: GameControlRuntime, + + pub writer_count: i32, + pub reader_count: i32, +} + +pub struct GameRuntimeData { + + pub(crate) received: HashMap<(ServiceType, Account), VecDeque<(Account, ControlMessage)>>, + pub(crate) send: HashMap<(ServiceType, Account), VecDeque<(Account, GameMessage)>>, + + pub(crate) players_online: Players, + pub(crate) players_banned: Players, + + pub locked: AtomicBool, + pub close: AtomicBool, +} + +#[derive(Default)] +pub struct GameControlRuntime { + pub(crate) keys: GameControlData, + pub(crate) directions : HashMap<u8, HashMap<Account, (f64, f64)>>, + pub(crate) axes : HashMap<u8, HashMap<Account, f64>>, + pub(crate) button : HashMap<u8, HashMap<Account, bool>>, + pub(crate) events : VecDeque<(Account, ControlMessage)> +}
\ No newline at end of file diff --git a/core/src/data/game/structs.rs b/core/src/data/game/structs.rs new file mode 100644 index 0000000..52fc0f1 --- /dev/null +++ b/core/src/data/game/structs.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; +use crate::data::game::types::GameInfo; +use crate::data::player::structs::Account; + +/// Game pad_client data +/// Describes the basic information of the game pad_client +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GameData { + pub info: GameInfo, + pub control: GameControlData, + pub archive: GameRuntimeDataArchive +} + +/// Game control information +/// Describes the buttons, axes, and directions that can be controlled. +#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GameControlData { + pub direction_keys : HashMap<u8, String>, + pub axis_keys : HashMap<u8, String>, + pub button_keys : HashMap<u8, String>, +} + +/// Archive of game runtime data +/// The game pad_client can convert data into this structure for persistence. +#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct GameRuntimeDataArchive { + pub banned: Vec<Account> +}
\ No newline at end of file diff --git a/core/src/data/game/types.rs b/core/src/data/game/types.rs new file mode 100644 index 0000000..8af17d3 --- /dev/null +++ b/core/src/data/game/types.rs @@ -0,0 +1,7 @@ +use std::collections::HashMap; +use std::sync::Mutex; +use crate::data::player::structs::{Account, Player}; + +pub(crate) type GameInfo = HashMap<String, String>; + +pub(crate) type Players = Mutex<HashMap<Account, Player>>;
\ No newline at end of file diff --git a/core/src/data/message/enums.rs b/core/src/data/message/enums.rs new file mode 100644 index 0000000..4150b63 --- /dev/null +++ b/core/src/data/message/enums.rs @@ -0,0 +1,154 @@ +use crate::data::player::structs::Player; +use bincode::{Decode, Encode}; +use crate::data::game::types::GameInfo; + +/// Control messages. +/// Messages sent from controller to game pad_client after establishing persistent connection +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone)] +pub enum ControlMessage { + /// Plain message containing a string + /// The message will be handed over to the game for its own processing + Msg(String), + + /// Press event + /// Indicates that a button has been pressed + Pressed(u8), + + /// Release event + /// Indicates that a button has been released + Released(u8), + + /// Axis input + /// Indicates that the value of an axis has been changed + Axis(u8, f64), + + /// Directional input + /// Indicates that the value of a direction has been changed + Dir(u8, (f64, f64)), + + /// Exit command + /// Sends a disconnect request to the pad_server + Exit, + + #[default] + /// Error state + Err, + + /// Indicates the termination message, which is the final message in a long-lived connection. + End +} + +/// Game messages. +/// Messages sent from game pad_client to controller after establishing persistent connection +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone)] +pub enum GameMessage { + /// Event trigger + /// Sends an event to the controller; if skins are enabled, this will trigger corresponding animations, sounds, vibrations, etc. + EventTrigger(u8), + + /// Plain message containing a string + /// The message will be handed over to the controller for its own processing + Msg(String), + + /// Disconnect request + /// Notifies the pad_client that the connection will be terminated + LetExit(ExitReason), + + /// Error state + #[default] + Err, + + /// Indicates the termination message, which is the final message in a long-lived connection. + End +} + +/// Exit reasons. +/// Reason provided when requesting disconnection +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone)] +pub enum ExitReason { + /// Normal exit + /// No specific reason, simply requesting to disconnect + Exit, + + /// Game has ended + GameOver, + + /// Server shutdown (normal) + ServerClosed, + + /// Kicked by pad_server + YouAreKicked, + + /// Account banned + YouAreBanned, + + /// Error state + #[default] + Err +} + +/// Connection messages. +/// Messages sent by pad_client when requesting pad_server connection +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone)] +pub enum ConnectionMessage { + /// Requests to join the game + Join(Player), + + /// Request for game information + RequestGameInfos, + + /// Request for game layout configuration file + RequestLayoutConfigure, + + /// Request to download game skin assets + RequestSkinPackage, + + /// Ready state to establish persistent connection + Ready, + + /// Error state + #[default] + Err +} + +/// Connection Response. +/// Messages from pad_server responding to pad_client connection requests +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone)] +pub enum ConnectionResponseMessage { + /// Game information data + GameInfos(GameInfo), + + /// Rejection with reason + Deny(JoinFailedMessage), + + /// Failure with reason + Fail(JoinFailedMessage), + + /// Approval confirmation + Ok, + + /// Welcome acknowledgment + Welcome, + + /// Error state + #[default] + Err +} + +/// Game Join Failure Information. +/// Reason provided when pad_client fails to join +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone)] +pub enum JoinFailedMessage { + /// Game already contains identical player + ContainIdenticalPlayer, + + /// Player is banned + PlayerBanned, + + /// Game is locked, no further joins allowed + GameLocked, + + /// Unknown error + #[default] + UnknownError +}
\ No newline at end of file diff --git a/core/src/data/message/implements.rs b/core/src/data/message/implements.rs new file mode 100644 index 0000000..2d5bae8 --- /dev/null +++ b/core/src/data/message/implements.rs @@ -0,0 +1,15 @@ +use crate::data::message::enums::{ConnectionResponseMessage, JoinFailedMessage, ConnectionMessage, ControlMessage, ExitReason, GameMessage}; +use crate::data::message::traits::MessageEncoder; + +#[macro_export] +macro_rules! encoder { + ($($msg:ident),+) => { + $( + impl MessageEncoder<$msg> for $msg {} + )+ + }; +} + +encoder!( + ControlMessage, GameMessage, ExitReason, ConnectionMessage, ConnectionResponseMessage, JoinFailedMessage +);
\ No newline at end of file diff --git a/core/src/data/message/mod.rs b/core/src/data/message/mod.rs new file mode 100644 index 0000000..96fdd73 --- /dev/null +++ b/core/src/data/message/mod.rs @@ -0,0 +1,3 @@ +pub mod enums; +pub mod implements; +pub mod traits;
\ No newline at end of file diff --git a/core/src/data/message/traits.rs b/core/src/data/message/traits.rs new file mode 100644 index 0000000..c4b3af4 --- /dev/null +++ b/core/src/data/message/traits.rs @@ -0,0 +1,67 @@ +use std::collections::{HashMap, VecDeque}; +use std::fmt::Debug; +use std::hash::Hash; +use bincode::{Decode, Encode}; +use crate::data::{BINCODE_CONFIG, BINCODE_CONVERT_FAILED}; +use crate::service::service_types::ServiceType; + +/// Message Manager +/// Provides the ability to store and retrieve messages from a VecDeque +pub trait MessageManager<In, Out, Key> +where Key: Eq + Hash { + fn borrow_received_list_mut(&mut self) -> &mut HashMap<(ServiceType, Key), VecDeque<In>>; + + fn borrow_send_list_mut(&mut self) -> &mut HashMap<(ServiceType, Key), VecDeque<Out>>; + + fn send(&mut self, message: Out, key: Key, service: ServiceType) { + self.borrow_send_list_mut() + .entry((service, key)) + .or_insert_with(VecDeque::new) + .push_back(message); + } + + fn receive(&mut self, key: Key, service: ServiceType) -> Option<In> { + self.borrow_received_list_mut() + .entry((service, key)) + .or_insert_with(VecDeque::new) + .pop_front() + } + + fn pop_from_send_list(&mut self, key: Key, service: ServiceType) -> Option<Out> { + self.borrow_send_list_mut() + .entry((service, key)) + .or_insert_with(VecDeque::new) + .pop_front() + } + + fn put_into_receive_list(&mut self, message: In, key: Key, service: ServiceType) { + self.borrow_received_list_mut() + .entry((service, key)) + .or_insert_with(VecDeque::new) + .push_back(message); + } +} + +/// Message Encoder +/// Provides the ability to encode messages into binary data or decode them from binary data +pub trait MessageEncoder<M: Encode + Decode<()> + Default + Debug> { + fn err_result_decode () -> M { + M::default() + } + + fn err_result_encode () -> Vec<u8> { + BINCODE_CONVERT_FAILED + } + + fn en(&self) -> Vec<u8> where Self : Encode { + bincode::encode_to_vec(self, BINCODE_CONFIG) + .unwrap_or_else(|_| Self::err_result_encode()) + } + + fn de(encoded : Vec<u8>) -> M { + match bincode::decode_from_slice(&encoded[..], BINCODE_CONFIG) { + Ok((decoded, _)) => decoded, + Err(_) => Self::err_result_decode() + } + } +}
\ No newline at end of file diff --git a/core/src/data/mod.rs b/core/src/data/mod.rs new file mode 100644 index 0000000..19589a2 --- /dev/null +++ b/core/src/data/mod.rs @@ -0,0 +1,10 @@ +use bincode::config; +use bincode::config::Configuration; + +pub const BINCODE_CONVERT_FAILED : Vec<u8> = Vec::new(); +pub const BINCODE_CONFIG : Configuration = config::standard(); + +pub mod controller; +pub mod game; +pub mod message; +pub mod player;
\ No newline at end of file diff --git a/core/src/data/player/implements.rs b/core/src/data/player/implements.rs new file mode 100644 index 0000000..bbaefc4 --- /dev/null +++ b/core/src/data/player/implements.rs @@ -0,0 +1,119 @@ +use crate::data::player::structs::{Account, Customize, Player}; +use crate::data::player::ACCOUNT_HASH_SALT; +use hex::encode; +use sha1::{Digest, Sha1}; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use nogamepads::string_utils::process_id_text; + +impl Player { + + /// Create new player information using a username and password + pub fn register(id: String, password: String) -> Player { + let mut player = Player { + customize: None, + account: Account::default() + }; + + let processed_id = process_id_text(id); + + player.account.id = processed_id.clone(); + player.account.player_hash = Self::gen_hash(processed_id, password); + player + } + + pub fn check(&self, password: String) -> bool { + let hash = Self::gen_hash(self.account.id.clone(), password); + hash == self.account.player_hash + } + + fn gen_hash(processed_id: String, password: String) -> String { + let combined = format!("{}{}{}", processed_id, password, ACCOUNT_HASH_SALT); + let mut hasher = Sha1::new(); + hasher.update(combined); + let result = hasher.finalize(); + encode(&result[..]) + } +} + +// Customize implements +impl Player { + + /// Set player nickname + pub fn nickname(&mut self, name: &String) -> &mut Player { + self.change(|custom| { + custom.nickname = name.clone(); + custom + }) + } + + /// Set the hue of the player's color + pub fn hue(&mut self, mut hue: i32) -> &mut Player { + hue = hue.clamp(0, 360); + self.change(|custom| { + custom.color_hue = hue.clone(); + custom + }) + } + + /// Set the player's HSV values + pub fn hsv(&mut self, mut hue: i32, mut saturation: f64, mut value: f64) -> &mut Player { + hue = hue.clamp(0, 360); + saturation = saturation.clamp(0.0, 1.0); + value = value.clamp(0.0, 1.0); + self.change(|custom| { + custom.color_hue = hue.clone(); + custom.color_saturation = saturation.clone(); + custom.color_value = value.clone(); + custom + }) + } + + fn init(&mut self) { + if self.customize.is_none() { + self.customize = Some(Customize::default()); + } + } + + fn change<F>(&mut self, f: F) -> &mut Player + where F: FnOnce(&mut Customize) -> &mut Customize { + self.init(); + let mut customize = self.customize.clone().unwrap(); + f(&mut customize); + self.customize = Some(customize); + self + } +} + +impl PartialEq for Player { + fn eq(&self, other: &Self) -> bool { + self.account == other.account + } +} + +impl Hash for Player { + fn hash<H: Hasher>(&self, state: &mut H) { + self.account.hash(state); + } +} + +impl Display for Player { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.account.id.as_str()) + } +} + +impl From<Account> for Player { + fn from(account: Account) -> Self { + Player { + account, + customize: None + } + } +} + +impl Display for Account { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.id.as_str()) + } +}
\ No newline at end of file diff --git a/core/src/data/player/mod.rs b/core/src/data/player/mod.rs new file mode 100644 index 0000000..7e8fe28 --- /dev/null +++ b/core/src/data/player/mod.rs @@ -0,0 +1,4 @@ +pub mod implements; +pub mod structs; + +pub const ACCOUNT_HASH_SALT : &str = env!("TEST_PLAYER_ACCOUNT");
\ No newline at end of file diff --git a/core/src/data/player/structs.rs b/core/src/data/player/structs.rs new file mode 100644 index 0000000..cc3d7c9 --- /dev/null +++ b/core/src/data/player/structs.rs @@ -0,0 +1,45 @@ +use bincode::{Decode, Encode}; +use serde::{Deserialize, Serialize}; +use std::hash::{Hash}; + +/// Player information +/// Describes a player's specific details, which are frequently exchanged between the controller and game pad_client. +#[derive(Default, Clone, Encode, Decode, Serialize, Deserialize, Debug)] +pub struct Player { + + /// Account information + pub account: Account, + + /// Custom information (Optional) + pub customize: Option<Customize> +} + +/// Account information +/// Essential data for verifying player uniqueness, including the player's hash value and account ID. +#[derive(Default, Clone, Encode, Decode, Serialize, Deserialize, Eq, Hash, PartialEq, Debug)] +pub struct Account { + + /// Player name stored in data, allowing only lowercase letters and underscores + pub id: String, + + /// Player hash value proving player uniqueness + pub player_hash: String +} + +/// Custom information +/// Describes personalized player details displayed in-game, such as name, color, or other customizations. +#[derive(Default, Clone, Encode, Decode, Serialize, Deserialize, PartialEq, Debug)] +pub struct Customize { + + /// Player name displayed in the game + pub nickname: String, + + /// HSV Color - Hue (Range: 0 - 360) + pub color_hue: i32, + + /// HSV Color - Saturation (Range: 0 - 1) + pub color_saturation: f64, + + /// HSV Color - Value (Range: 0 - 1) + pub color_value: f64 +}
\ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs index 0c8cf83..49db429 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,9 +1,8 @@ use bincode::config; use bincode::config::Configuration; -pub mod pad_service; -pub mod pad_data; +pub mod data; +pub mod service; -pub const DEFAULT_PORT : u16 = 5989; pub const BINCODE_CONVERT_FAILED : Vec<u8> = Vec::new(); -pub const BINCODE_CONFIG : Configuration = config::standard();
\ No newline at end of file +pub const BINCODE_CONFIG : Configuration = config::standard(); diff --git a/core/src/pad_data/game_profile.rs b/core/src/pad_data/game_profile.rs deleted file mode 100644 index cd2a175..0000000 --- a/core/src/pad_data/game_profile.rs +++ /dev/null @@ -1,100 +0,0 @@ -pub mod game_profile { - use std::fmt::Display; - use bincode::{Decode, Encode}; - use serde::{Deserialize, Serialize}; - - #[repr(C)] - #[derive(Encode, Decode, Serialize, Deserialize, PartialEq, Debug)] - pub struct GameProfile { - - // 游戏名称 - pub game_name: String, - - // 游戏描述 - pub game_description: String, - - // 游戏组织 - pub organization: String, - - // 游戏版本 - pub version: String, - - // 工作室 & 游戏 主页 - pub website: String, - - // 交流邮箱 - pub email: String - } - - impl Default for GameProfile { - fn default() -> Self { - GameProfile { - game_name: "Unnamed Game".to_string(), - game_description: "".to_string(), - organization: "".to_string(), - version: "0.1".to_string(), - website: "".to_string(), - email: "".to_string() - } - } - } - - impl Clone for GameProfile { - fn clone(&self) -> Self { - GameProfile { - game_name: self.game_name.clone(), - game_description: self.game_description.clone(), - organization: self.organization.clone(), - version: self.version.clone(), - website: self.website.clone(), - email: self.email.clone() - } - } - } - - impl Display for GameProfile { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut string = String::new(); - string += format!("Game Name: {}\n", self.game_name).as_str(); - if !self.game_description.eq("") { string += format!("Description: {}\n", self.game_description).as_str(); } - if !self.organization.eq("") { string += format!("Org: {}\n", self.organization).as_str(); } - if !self.website.eq("") { string += format!("- Web: {}\n", self.website).as_str(); } - if !self.version.eq("") { string += format!("Version: {}\n", self.version).as_str(); } - if !self.email.eq("") { string += format!("- E-mail: {}\n", self.email).as_str(); } - - write!(f, "{}", string) - } - } - - impl GameProfile { - pub fn game_name(&mut self, game_name: &str) -> &mut GameProfile { - self.game_name = game_name.to_string(); - self - } - - pub fn game_description(&mut self, game_description: &str) -> &mut GameProfile { - self.game_description = game_description.to_string(); - self - } - - pub fn organization(&mut self, organization: &str) -> &mut GameProfile { - self.organization = organization.to_string(); - self - } - - pub fn version(&mut self, version: &str) -> &mut GameProfile { - self.version = version.to_string(); - self - } - - pub fn website(&mut self, website: &str) -> &mut GameProfile { - self.website = website.to_string(); - self - } - - pub fn email(&mut self, email: &str) -> &mut GameProfile { - self.email = email.to_string(); - self - } - } -}
\ No newline at end of file diff --git a/core/src/pad_data/layout.rs b/core/src/pad_data/layout.rs deleted file mode 100644 index 4c5f53f..0000000 --- a/core/src/pad_data/layout.rs +++ /dev/null @@ -1,137 +0,0 @@ -pub mod layout_data { - use std::collections::{HashMap, VecDeque}; - use bincode::{Decode, Encode}; - use serde::{Deserialize, Serialize}; - use crate::pad_data::pad_messages::nogamepads_messages::ControlMessage; - use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; - use crate::pad_service::server::nogamepads_server::PadServer; - - - #[derive(Encode, Decode, Serialize, Deserialize, PartialEq, Debug, Clone)] - pub struct LayoutKeyRegisters { - pub direction_keys : HashMap<u8, String>, // 注册方向键 - pub axis_keys : HashMap<u8, String>, // 注册轴向键 - pub button_keys : HashMap<u8, String>, // 注册按钮 - } - - impl Default for LayoutKeyRegisters { - fn default() -> LayoutKeyRegisters { - LayoutKeyRegisters { - direction_keys: Default::default(), - axis_keys: Default::default(), - button_keys: Default::default(), - } - } - } - - pub struct LayoutKeyRuntimeData { - directions : HashMap<u8, HashMap<String, (f64, f64)>>, // <键, <玩家Hash, (x, y)>> - axes : HashMap<u8, HashMap<String, f64>>, // <键, <玩家Hash, 轴向>> - button : HashMap<u8, HashMap<String, bool>>, // <键, <玩家Hash, 是否按下>> - - events : VecDeque<(String, ControlMessage)>, // (玩家Hash, 信息) - } - - impl Default for LayoutKeyRuntimeData { - fn default() -> Self { - LayoutKeyRuntimeData { - directions: Default::default(), - axes: Default::default(), - button: Default::default(), - events: Default::default(), - } - } - } - - impl LayoutKeyRuntimeData { - - // 输入控制信息到数据 - pub fn insert_control(&mut self, who: PlayerInfo, msg: ControlMessage) { - match msg { - // 消息放入信息队列待读取 - ControlMessage::Msg(_) => { - self.events.push_back((who.account.player_hash, msg)) - } - - // 按钮更新对应玩家的状态,并且放入信息队列待读取 - ControlMessage::Pressed(button_key) => { - self.button.entry(button_key) - .or_insert_with(HashMap::new) - .insert(who.account.player_hash.clone(), true); - self.events.push_back((who.account.player_hash, msg)) - } - ControlMessage::Released(button_key) => { - self.button.entry(button_key) - .or_insert_with(HashMap::new) - .insert(who.account.player_hash.clone(), false); - self.events.push_back((who.account.player_hash, msg)) - } - - // 轴向更新直接传入玩家状态 - ControlMessage::Axis(axis_key, axis) => { - self.axes.entry(axis_key) - .or_insert_with(HashMap::new) - .insert(who.account.player_hash.clone(), axis); - } - ControlMessage::Dir(dir_key, (x, y)) => { - self.directions.entry(dir_key) - .or_insert_with(HashMap::new) - .insert(who.account.player_hash.clone(), (x, y)); - } - _ => { } - } - } - - pub fn pop_control_event(&mut self, server: &PadServer) -> Option<(PlayerInfo, ControlMessage)> { - let pop = self.events.pop_front(); - if pop.is_some() { - let (hash, msg) = pop.unwrap(); - let info = server.find_online_player(hash); - if info.is_some() { - Some((info.unwrap(), msg)) - } else { - None - } - } else { - None - } - } - - pub fn get_direction(&self, who: &PlayerInfo, key: &u8) -> Option<(f64, f64)> { - Self::get(&self.directions, who, key) - } - - pub fn get_axis(&self, who: &PlayerInfo, key: &u8) -> Option<f64> { - Self::get(&self.axes, who, key) - } - - pub fn get_button_status(&self, who: &PlayerInfo, key: &u8) -> Option<bool> { - Self::get(&self.button, who, key) - } - - fn get<V: Clone>(map: &HashMap<u8, HashMap<String, V>>, who: &PlayerInfo, key: &u8) -> Option<V> { - let key = map.get(key); - if key.is_some() { - let value = key.unwrap().get(&who.account.player_hash); - if value.is_some() { - let result = value.unwrap(); - Some(result.clone()) - } else { None } - } else { None } - } - } -} - -pub mod layout_gamepad { - use bincode::{Decode, Encode}; - use serde::{Deserialize, Serialize}; - - #[derive(Encode, Decode, Serialize, Deserialize, PartialEq, Debug)] - pub struct PadLayout { - - } - - pub trait ButtonArea { - - } -}
\ No newline at end of file diff --git a/core/src/pad_data/mod.rs b/core/src/pad_data/mod.rs deleted file mode 100644 index e041524..0000000 --- a/core/src/pad_data/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod pad_messages; -pub mod pad_player_info; -pub mod game_profile; -pub mod layout;
\ No newline at end of file diff --git a/core/src/pad_data/pad_messages.rs b/core/src/pad_data/pad_messages.rs deleted file mode 100644 index f081e5d..0000000 --- a/core/src/pad_data/pad_messages.rs +++ /dev/null @@ -1,176 +0,0 @@ -pub mod nogamepads_messages { - use bincode::{Decode, Encode}; - use crate::pad_data::game_profile::game_profile::GameProfile; - use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; - - #[derive(Encode, Decode, PartialEq, Debug, Clone)] - pub enum ControlMessage { - - Msg(String), - - Pressed(u8), - - Released(u8), - - Axis(u8, f64), - - Dir(u8, (f64, f64)), - - Exit, - - Err - } - - #[derive(Encode, Decode, PartialEq, Debug, Clone)] - pub enum GameMessage { - - SkinEventTrigger(u8), - - DisableKey(u8), - - EnableKey(u8), - - Leave(LeaveReason), - - Err - } - - #[derive(Encode, Decode, PartialEq, Debug, Clone)] - pub enum LeaveReason { - - GameOver, - - ServerClosed, - - YouAreKicked, - - YouAreBanned - } - - #[derive(Encode, Decode, PartialEq, Debug, Clone)] - pub enum ConnectionMessage { - - Connection(PlayerInfo), - - RequestProfile, - - RequestLayoutConfigure, - - RequestSkinPackage, - - Ready, - - Err - } - - #[derive(Encode, Decode, PartialEq, Debug, Clone)] - pub enum ConnectionCallbackMessage { - - Profile(GameProfile), - - Deny(ConnectionErrorType), - - Fail(ConnectionErrorType), - - Ok, - - Welcome, - - Err - } - - #[derive(Encode, Decode, PartialEq, Debug, Clone)] - pub enum ConnectionErrorType { - - ContainSamePlayer, - - PlayerBanned, - - Timeout, - - GameLocked, - - WhatTheHell - } -} - -pub mod nogamepads_message_encoder { - use bincode::{Decode, Encode}; - use crate::{BINCODE_CONFIG, BINCODE_CONVERT_FAILED}; - use crate::pad_data::pad_messages::nogamepads_messages::{ConnectionCallbackMessage, ConnectionMessage, ControlMessage, GameMessage}; - - pub trait NgpdMessageEncoder<Message: Encode + Decode<()>> { - fn err_result_decode () -> Message; - fn err_result_encode () -> Vec<u8> { - BINCODE_CONVERT_FAILED - } - - fn en(&self) -> Vec<u8> where Self : Encode { - bincode::encode_to_vec(self, BINCODE_CONFIG) - .unwrap_or_else(|_| Self::err_result_encode()) - } - - fn de(encoded : Vec<u8>) -> Message { - match bincode::decode_from_slice(&encoded[..], BINCODE_CONFIG) { - Ok((decoded, _)) => decoded, - Err(_) => Self::err_result_decode() - } - } - } - - impl NgpdMessageEncoder<ControlMessage> for ControlMessage { - fn err_result_decode() -> ControlMessage { - ControlMessage::Err - } - } - - impl NgpdMessageEncoder<GameMessage> for GameMessage { - fn err_result_decode() -> GameMessage { - GameMessage::Err - } - } - - impl NgpdMessageEncoder<ConnectionMessage> for ConnectionMessage { - fn err_result_decode() -> ConnectionMessage { - ConnectionMessage::Err - } - } - - impl NgpdMessageEncoder<ConnectionCallbackMessage> for ConnectionCallbackMessage { - fn err_result_decode() -> ConnectionCallbackMessage { - ConnectionCallbackMessage::Err - } - } -} - -pub mod nogamepads_message_transfer { - use bincode::{Decode, Encode}; - use log::error; - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - use tokio::net::TcpStream; - use crate::pad_data::pad_messages::nogamepads_message_encoder::NgpdMessageEncoder; - - pub async fn send_msg <Message>(stream: &mut TcpStream, msg: impl NgpdMessageEncoder<Message> + Decode<()> + Encode) - where Message: NgpdMessageEncoder<Message> + Decode<()> + Encode { - match stream.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await { - Ok(_) => {} - Err(_) => { - error!("Failed to send message."); - } - } - } - - pub async fn read_msg<Message>(buffer: &mut [u8], stream: &mut TcpStream) -> Message - where Message: NgpdMessageEncoder<Message> + Decode<()> + Encode { - match stream.read(buffer).await { - Ok(read) => { - let received = &buffer[..read]; - <Message as NgpdMessageEncoder<Message>>::de(Vec::from(received)) - } - Err(err) => { - error!("Error reading from socket: {}", err); - <Message as NgpdMessageEncoder<Message>>::err_result_decode() - } - } - } -}
\ No newline at end of file diff --git a/core/src/pad_data/pad_player_info.rs b/core/src/pad_data/pad_player_info.rs deleted file mode 100644 index 1c8b7e0..0000000 --- a/core/src/pad_data/pad_player_info.rs +++ /dev/null @@ -1,127 +0,0 @@ -pub mod nogamepads_player_info { - - use bincode::{Decode, Encode}; - use hex::encode; - use sha1::{Digest, Sha1}; - use serde::{Deserialize, Serialize}; - - pub const ACCOUNT_HASH_SALT : &str = "Mr.Weicao"; - - #[repr(C)] - #[derive(Encode, Decode, - Serialize, Deserialize, - PartialEq, Debug)] - pub struct PlayerInfo { - pub account: PlayerAccountInfo, - pub customize: PlayerCustomizeInfo - } - - #[derive(Encode, Decode, - Serialize, Deserialize, - PartialEq, Debug)] - pub struct PlayerAccountInfo { - pub id: String, - pub player_hash: String - } - - #[derive(Encode, Decode, - Serialize, Deserialize, - PartialEq, Debug)] - pub struct PlayerCustomizeInfo { - pub nickname: String, - - pub color_hue: i32, // 0 - 360 - pub color_saturation: f64, // 0 - 1 - pub color_value: f64 // 0 - 1 - } - - impl PlayerInfo { - - pub fn new() -> PlayerInfo { - PlayerInfo { - customize: PlayerCustomizeInfo::default(), - account: PlayerAccountInfo::default() - } - } - - pub fn set_nickname(&mut self, name: &str) -> &mut PlayerInfo { - self.customize.nickname = String::from(name); - self - } - - pub fn set_customize_color_hue(&mut self, mut hue: i32) -> &mut PlayerInfo { - hue = hue.clamp(0, 360); - self.customize.color_hue = hue; - self - } - - pub fn set_customize_color_hsv(&mut self, mut hue: i32, mut saturation: f64, mut value: f64) -> &mut PlayerInfo { - hue = hue.clamp(0, 360); - saturation = saturation.clamp(0.0, 1.0); - value = value.clamp(0.0, 1.0); - - self.customize.color_hue = hue; - self.customize.color_saturation = saturation; - self.customize.color_value = value; - self - } - - pub fn setup_account_info(&mut self, id: &str, password: &str) -> &mut PlayerInfo { - - let combined = format!("{}{}{}", id, password, ACCOUNT_HASH_SALT); - let mut hasher = Sha1::new(); - hasher.update(combined); - let result = hasher.finalize(); - - self.account.id = String::from(id); - self.account.player_hash = encode(&result[..]); - self - } - } - - impl Clone for PlayerInfo { - fn clone(&self) -> PlayerInfo { - PlayerInfo { - account: PlayerAccountInfo { - id: String::from(self.account.id.clone()), - player_hash: String::from(self.account.player_hash.clone()) - }, - customize: PlayerCustomizeInfo { - nickname: self.customize.nickname.clone(), - color_hue: self.customize.color_hue.clone(), - color_saturation: self.customize.color_saturation.clone(), - color_value: self.customize.color_value.clone() - } - } - } - } - - impl Default for PlayerCustomizeInfo { - fn default() -> Self { - PlayerCustomizeInfo { - nickname: String::from("unnamed"), - - color_hue: 120, - color_saturation: 1.0, - color_value: 1.0 - } - } - } - - impl Default for PlayerAccountInfo { - fn default() -> Self { - PlayerAccountInfo { - id: String::from("empty"), - player_hash: String::from("") - } - } - } -} - -#[cfg(test)] -mod player_info_test { - #[test] - fn test_player_info_setup() { - - } -}
\ No newline at end of file diff --git a/core/src/pad_service/client.rs b/core/src/pad_service/client.rs deleted file mode 100644 index b0b05a2..0000000 --- a/core/src/pad_service/client.rs +++ /dev/null @@ -1,520 +0,0 @@ -pub mod nogamepads_client { - use std::collections::VecDeque; - use crate::pad_data::pad_messages::nogamepads_message_transfer::{read_msg, send_msg}; - use crate::pad_data::pad_messages::nogamepads_messages::{ConnectionCallbackMessage, ConnectionErrorType, ConnectionMessage, ControlMessage, GameMessage, LeaveReason}; - use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; - use log::{error, info}; - use std::net::{IpAddr, Ipv4Addr}; - use std::process::exit; - use std::sync::atomic::AtomicBool; - use std::sync::atomic::Ordering::SeqCst; - use std::sync::{Arc, Mutex}; - use std::time::Duration; - use clap::CommandFactory; - use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}; - use tokio::net::TcpStream; - use tokio::{io, spawn}; - use tokio::runtime::Runtime; - use nogamepads::console_utils::debug_console::read_cli; - use nogamepads::convert_utils::convert_deque_to_vec; - use nogamepads::logger_utils::logger_build; - use crate::pad_service::client_debug_cli::{process_debug_cmd, Pcc}; - use crate::DEFAULT_PORT; - use crate::pad_data::game_profile::game_profile::GameProfile; - use crate::pad_data::pad_messages::nogamepads_message_encoder::NgpdMessageEncoder; - use crate::pad_data::pad_messages::nogamepads_messages::ControlMessage::{Axis, Dir, Msg, Pressed, Released}; - - type WriteList = Arc<Mutex<VecDeque<ControlMessage>>>; - type ReadList = Arc<Mutex<VecDeque<GameMessage>>>; - - #[repr(C)] - pub struct PadClient { - - // --- 主要参数 --- - - // 目标地址 - target_address: IpAddr, - - // 目标端口 - #[allow(dead_code)] - target_port: u16, - - // 绑定的玩家 - bind_player: PlayerInfo, - - // 调试模式 - enable_console: bool, - - // 保持安静,不初始化 env_logger - quiet: bool, - - // --- 运行时参数 --- - - // 发送信息列表 - write_list: WriteList, - - // 读取信息列表 - read_list: ReadList, - - // 是否退出 - exit: AtomicBool, - } - - impl Clone for PadClient { - fn clone(&self) -> Self { - PadClient { - enable_console: self.enable_console.clone(), - target_address: self.target_address.clone(), - target_port: self.target_port.clone(), - bind_player: self.bind_player.clone(), - quiet: self.quiet.clone(), - - write_list: self.write_list.clone(), - read_list: self.read_list.clone(), - exit: AtomicBool::new(self.exit.load(SeqCst)), - } - } - } - - impl Default for PadClient { - fn default() -> Self { - PadClient { - enable_console: false, - target_address: IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), - target_port: DEFAULT_PORT, - bind_player: PlayerInfo::new(), - quiet: false, - - write_list: WriteList::default(), - read_list: ReadList::default(), - exit: AtomicBool::new(false) - } - } - } - - // 客户端构建部分 - impl PadClient { - - pub fn bind_addr(address: IpAddr) -> PadClient { - PadClient { - target_address: address, - ..PadClient::default() - } - } - - pub fn bind_addr_with_port(address: IpAddr, port: u16) -> PadClient { - PadClient { - target_address: address, - target_port: port, - ..PadClient::default() - } - } - - pub fn enable_console(&mut self) -> &mut PadClient { - self.enable_console = true; - self - } - - pub fn quiet(&mut self) -> &mut PadClient { - self.quiet = true; - self - } - - pub fn bind_player(&mut self, player: PlayerInfo) -> &mut PadClient { - self.bind_player = player; - self - } - - pub fn clone_addr(&self) -> (IpAddr, u16) { - (self.target_address, self.target_port) - } - - pub fn is_quiet(&self) -> bool { - self.quiet - } - - pub fn is_enable_console(&self) -> bool { - self.enable_console - } - - pub fn unbind_player(&mut self) -> PlayerInfo { - let player = self.bind_player.clone(); - self.bind_player = PlayerInfo::new(); - player - } - } - - // 客户端消息管理 - impl PadClient { - - pub fn key_press(&self, key_id: u8) { - self.put_msg(Pressed(key_id)); - } - - pub fn key_release(&self, key_id: u8) { - self.put_msg(Released(key_id)); - } - - pub fn change_axis(&self, axis_id: u8, axis: f64) { - self.put_msg(Axis(axis_id, axis)); - } - - pub fn change_direction(&self, direction_id: u8, x: f64, y: f64) { - self.put_msg(Dir(direction_id, (x.clamp(0.0, 1.0), y.clamp(0.0, 1.0)))); - } - - pub fn say_str(&self, msg: &str) { - self.put_msg(Msg(msg.to_owned())); - } - - pub fn say(&self, msg: String) { - self.put_msg(Msg(msg)); - } - - pub fn put_msg(&self, msg: ControlMessage) { - let mut guard = self.write_list.lock().unwrap(); - guard.push_back(msg); - } - - pub fn pop_a_msg(&self) -> Option<GameMessage> { - let mut guard = self.read_list.lock().unwrap(); - if !guard.is_empty() { - guard.pop_front() - } else { - None - } - } - - pub fn pop_msg_or(&self, or: GameMessage) -> GameMessage { - self.pop_a_msg().unwrap_or(or) - } - - pub fn list_received(&self) -> Vec<GameMessage> { - match self.read_list.lock() { - Ok(guard) => { - convert_deque_to_vec(&guard.to_owned()) - } - Err(_) => { Vec::new() } - } - } - } - - // 客户端状态控制 - impl PadClient { - - pub fn connect(self) { - self.connect_in_runtime(None); - } - - pub fn connect_in_runtime(self, tokio_runtime: Option<Runtime>) { - - self.exit.store(false, SeqCst); - - // 构建 Logger - if !self.quiet { - logger_build(); - } - - info!("Starting \"NoGamepads Client\"."); - - // 入口 - let entry = self.get_connect_entry(); - - // 阻塞运行 - if tokio_runtime.is_some() { - tokio_runtime.unwrap().block_on(entry); - } else { - let runtime = tokio::runtime::Builder::new_multi_thread() - .thread_name("nogpad-pad_service") - .thread_stack_size(32 * 1024 * 1024) - .enable_time() - .enable_io() - .build() - .unwrap(); - runtime.block_on(entry); - } - } - - pub fn get_connect_entry(self) -> impl Future<Output = ()> + Send + 'static { - - // 调试模式 - let debug = self.enable_console; - - // Arc - let arc_client = Arc::new(self); - - async move { - let main_thread = spawn({ - let client = Arc::clone(&arc_client); - async move { - Self::main_client_thread(client).await - } - }); - - let background_thread = spawn({ - let client = Arc::clone(&arc_client); - async move { - Self::background_thread(client).await - } - }); - - if debug { - let debug_cli = spawn({ - let client = Arc::clone(&arc_client); - async move { - Self::process_debug_cli(client).await - } - }); - let _ = tokio::join!(debug_cli, main_thread, background_thread); - } else { - let _ = tokio::join!(main_thread, background_thread); - } - } - } - - pub fn exit_server(&self) { - self.exit.store(true, SeqCst); - } - - async fn main_client_thread(self: Arc<Self>) { - let mut buffer : [u8; 1024] = [0; 1024]; - let addr_str = format!("{}:{}", self.target_address.to_string(), DEFAULT_PORT); - - info!("Connected to {}", &addr_str); - - // 下载服务端配置文件 - { - info!("Check: Downloaded game profile."); - let profile = self.check_server_profile(&mut buffer, addr_str.clone()).await; - if profile.is_some() { - info!("Success: Downloaded."); - let profile = profile.unwrap_or(GameProfile::default()); - for line in profile.to_string().split('\n') { - info!("{}", line); - } - } - else { - error!("Failed: Can't download profile!"); - self.exit_server(); - } - } - - // 尝试加入服务端,并建立长连接 - { - if !self.try_join_game(&mut buffer, addr_str.clone()).await { - error!("Failed: Can't join the game!"); - self.exit_server(); - return; - } - } - } - - async fn check_server_profile(self: &Arc<Self>, buffer: &mut [u8], addr_str: String) -> Option<GameProfile> { - match TcpStream::connect(&addr_str).await { - Ok(mut stream) => { - send_msg(&mut stream, ConnectionMessage::RequestProfile).await; - let callback : ConnectionCallbackMessage = read_msg(buffer, &mut stream).await; - match callback { - ConnectionCallbackMessage::Profile(profile) => { - Some(profile) - } - ConnectionCallbackMessage::Deny(err_type) => { - error!("Request failed: Server denied your request! ({:?})", err_type); - None - } - ConnectionCallbackMessage::Err => { - error!("Connection failed: Can't connect to server!"); - None - } - _ => { None } - } - } - Err(_err) => { - None - } - } - } - - async fn try_join_game(self: &Arc<Self>, buffer: &mut [u8], addr_str: String) -> bool { - - match TcpStream::connect(&addr_str).await { - Ok(mut stream) => { - - // 发送连接请求 - let info = self.bind_player.clone(); - send_msg(&mut stream, ConnectionMessage::Connection(info)).await; - - // 读取回调 - let callback : ConnectionCallbackMessage = read_msg(buffer, &mut stream).await; - match callback { - ConnectionCallbackMessage::Deny(error) => { - match error { - ConnectionErrorType::ContainSamePlayer => { - error!("Connection failed: Contains same player!"); - false - } - ConnectionErrorType::PlayerBanned => { - error!("Connection failed: You are banned!"); - false - } - ConnectionErrorType::Timeout => { - error!("Connection failed: Timeout!"); - false - } - ConnectionErrorType::GameLocked => { - error!("Connection failed: Game was locked!"); - false - } - _ => { false } - } - } - ConnectionCallbackMessage::Ok => { - - // 服务端检查完毕,发送 Ready 以示加入游戏 - send_msg(&mut stream, ConnectionMessage::Ready).await; - let callback : ConnectionCallbackMessage = read_msg(buffer, &mut stream).await; - match callback { - ConnectionCallbackMessage::Welcome => { - info!("Welcome!"); - Self::long_connection(Arc::clone(&self), stream).await; - } - ConnectionCallbackMessage::Deny(_error) => { - error!("Request failed: Server denied your request",); - } - _ => {} - } - true - } - _ => { false } - } - } - Err(err) => { - error!("Failed to connect to server: {}", err); - false - } - } - } - - async fn long_connection(self: Arc<Self>, stream: TcpStream) { - let (reader, writer) = io::split(stream); - spawn(Self::read_task(Arc::clone(&self), reader)); - spawn(Self::write_task(Arc::clone(&self), writer)); - } - - async fn read_task(self: Arc<Self>, mut reader: ReadHalf<TcpStream>) { - let mut buf = [0u8; 1024]; - loop { - match reader.read(&mut buf).await { - Ok(0) => break, - Ok(n) => { - let msg = GameMessage::de(buf[0..n].to_vec()); - { - match self.read_list.lock() { - Ok(mut guard) => { - match &msg { - GameMessage::Leave(reason) => { - match reason { - LeaveReason::GameOver => { - info!("Leave Game: Game Over!"); - self.exit_server(); - } - LeaveReason::ServerClosed => { - info!("Leave Game: Server closed!"); - self.exit_server(); - } - LeaveReason::YouAreKicked => { - error!("Kick Game: You are kicked!"); - self.exit_server(); - } - LeaveReason::YouAreBanned => { - error!("Kick Game: You are banned!"); - self.exit_server(); - } - } - } - _ => { - info!("{:?}", &msg); - guard.push_back(msg); - } - } - } - Err(_) => {} - } - } - } - Err(e) => { - error!("Error reading from stream: {}", e); - self.exit_server(); - break; - } - } - } - } - - async fn write_task(self: Arc<Self>, mut writer: WriteHalf<TcpStream>) { - loop { - let msg : Option<ControlMessage>; - { - let lock = self.write_list.lock(); - match lock { - Ok(mut guard) => { - if ! guard.is_empty() { - msg = guard.pop_front(); - } else { - msg = None; - } - } - Err(_) => { - msg = None; - } - } - } - if msg.is_some() { - let msg = msg.unwrap(); - match &writer.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await { - Ok(_) => { - info!("Sent {:?}", msg); - } - Err(_error) => { - error!("Sent {:?} failed!", msg); - } - } - } - } - } - - async fn background_thread(self: Arc<Self>) { - loop { - // 退出程序的监听 - if self.exit.load(SeqCst) { - tokio::time::sleep(Duration::from_secs(1)).await; - info!("Main thread exited."); - exit(0); - } - } - } - - async fn process_debug_cli(self: Arc<Self>) { - loop { - if self.exit.load(SeqCst) { - info!("Debug console exited"); - break - } - tokio::time::sleep(Duration::from_secs_f64(0.2)).await; - let option: Option<Pcc> = read_cli( - format!("CLIENT {}/{}> ", - self.target_address.to_string(), - self.bind_player.account.id).as_str(), - "pcc".to_string(), - Pcc::command() - ).await; - match option { - None => {} - Some(cmd) => { - process_debug_cmd(cmd, Arc::clone(&self)); - } - } - } - } - } -}
\ No newline at end of file diff --git a/core/src/pad_service/client_debug_cli.rs b/core/src/pad_service/client_debug_cli.rs deleted file mode 100644 index 988bf39..0000000 --- a/core/src/pad_service/client_debug_cli.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::pad_service::client::nogamepads_client::PadClient; -use crate::pad_data::pad_messages::nogamepads_messages::{ControlMessage, GameMessage}; -use clap::{Args, Parser, Subcommand}; -use std::sync::Arc; -use log::info; - -/// NoGamePads Client - Cli -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -pub struct Pcc { - #[command(subcommand)] - command: Commands, -} - -/// 主要命令 -#[derive(Subcommand, Debug)] -enum Commands { - - // 清屏 - #[command(about = "Clean the screen")] - Clear, - - // 断开当前连接 - #[command(about = "Exit from server")] - Exit, - - // 检查收到的消息 - #[command(about = "Check received")] - Received(ReceivedArgs), - - // 取出一条消息 - #[command(about = "Pop a message")] - Pop(PopArgs), - - // 发送消息 - #[command(about = "Send Message")] - Msg(MsgArgs), -} - -#[derive(Args, Debug)] -struct ReceivedArgs { - - #[arg(long)] - list: bool -} - -/// 发送消息 参数 -#[derive(Args, Debug)] -struct MsgArgs { - - // 消息内容 - #[arg(value_name = "CONTENT")] - message: String, -} - -#[derive(Args, Debug)] -struct PopArgs { } - -pub fn process_debug_cmd (cmd: Pcc, client: Arc<PadClient>) { - match cmd.command { - Commands::Clear => { - clearscreen::clear().expect("Failed to clear screen"); - } - - Commands::Exit => { - client.exit_server(); - } - - Commands::Received(args) => { - if args.list { - for msg in client.list_received() { - info!("{:?}", msg); - } - } else { - info!("Total {} messsage(s)!", client.list_received().iter().count()); - } - } - - Commands::Pop(_args) => { - info!("{:?}", client.pop_msg_or(GameMessage::Err)); - } - - Commands::Msg(args) => { - client.put_msg(ControlMessage::Msg(args.message)); - } - } -} - -#[allow(dead_code)] -fn put_to_list(client: Arc<PadClient>, message: ControlMessage) { - client.put_msg(message); -}
\ No newline at end of file diff --git a/core/src/pad_service/mod.rs b/core/src/pad_service/mod.rs deleted file mode 100644 index ae10f2d..0000000 --- a/core/src/pad_service/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod client; -pub mod client_debug_cli; - -pub mod server; -pub mod server_debug_cli;
\ No newline at end of file diff --git a/core/src/pad_service/server.rs b/core/src/pad_service/server.rs deleted file mode 100644 index eeadb01..0000000 --- a/core/src/pad_service/server.rs +++ /dev/null @@ -1,819 +0,0 @@ -pub mod nogamepads_server { - use std::collections::{HashMap, VecDeque}; - use crate::pad_data::pad_messages::nogamepads_message_encoder::NgpdMessageEncoder; - use crate::pad_data::pad_messages::nogamepads_message_transfer::{read_msg, send_msg}; - use crate::pad_data::pad_messages::nogamepads_messages::{ConnectionCallbackMessage, ConnectionMessage, ControlMessage, GameMessage, LeaveReason}; - use log::{error, info, warn}; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - use std::ops::Deref; - use std::process::exit; - use std::sync::atomic::AtomicBool; - use std::sync::atomic::Ordering::SeqCst; - use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; - use std::time::Duration; - use clap::CommandFactory; - use clearscreen::clear; - use prettytable::{Row, Table}; - use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}; - use tokio::net::{TcpListener, TcpStream}; - use tokio::{io, signal, spawn}; - use tokio::runtime::Runtime; - use nogamepads::console_utils::debug_console::read_cli; - use nogamepads::logger_utils::logger_build; - use crate::DEFAULT_PORT; - use crate::pad_data::game_profile::game_profile::GameProfile; - use crate::pad_data::layout::layout_data::{LayoutKeyRegisters, LayoutKeyRuntimeData}; - use crate::pad_data::pad_messages::nogamepads_messages::ConnectionErrorType::{ContainSamePlayer, GameLocked, PlayerBanned, WhatTheHell}; - use crate::pad_data::pad_messages::nogamepads_messages::GameMessage::Leave; - use crate::pad_data::pad_messages::nogamepads_messages::LeaveReason::ServerClosed; - use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; - use crate::pad_service::server_debug_cli::{process_debug_cmd, Psc}; - - type PlayerMap = Arc<Mutex<HashMap<String, PlayerInfo>>>; - type WriteList = Arc<Mutex<HashMap<String, VecDeque<GameMessage>>>>; - - #[repr(C)] - pub struct PadServer { - - // --- 主要参数 --- - - // 本地监听地址 - address: IpAddr, - - // 游戏信息 - game_profile: GameProfile, - - // 绑定端口 - port: u16, - - // 调试模式 - enable_console: bool, - - // 保持安静,不初始化 env_logger - quiet: bool, - - // 键位表 - keys: LayoutKeyRegisters, - - // --- 运行时参数 --- - - - // 发送信息列表 - write_list: WriteList, - - // 运行环境信息 - control_data: Arc<Mutex<LayoutKeyRuntimeData>>, - - // 在线玩家 - online_players: PlayerMap, - - // 被封禁的玩家 - banned_players: PlayerMap, - - // 是否锁定该游戏:禁止后续玩家加入 - game_locked: AtomicBool, - - // 是否停止服务器 - stop: AtomicBool, - - // 调试模式 - 实时信息显示模式 - monitor_view: AtomicBool, - } - - impl Clone for PadServer { - fn clone(&self) -> Self { - PadServer { - address: self.address.clone(), - game_profile: self.game_profile.clone(), - port: self.port.clone(), - enable_console: self.enable_console, - quiet: self.quiet, - keys: self.keys.clone(), - - write_list: self.write_list.clone(), - control_data: self.control_data.clone(), - online_players: self.online_players.clone(), - banned_players: self.banned_players.clone(), - game_locked: AtomicBool::new((&self.game_locked.load(SeqCst)).clone()), - stop: AtomicBool::new((&self.stop.load(SeqCst)).clone()), - monitor_view: AtomicBool::new((&self.monitor_view.load(SeqCst)).clone()) - } - } - } - - impl Default for PadServer { - fn default() -> Self { - PadServer { - address: IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), - game_profile: GameProfile::default(), - port: DEFAULT_PORT, - enable_console: false, - quiet: false, - keys: LayoutKeyRegisters::default(), - - write_list: WriteList::default(), - control_data: Arc::new(Mutex::new(LayoutKeyRuntimeData::default())), - online_players: PlayerMap::default(), - banned_players: PlayerMap::default(), - game_locked: AtomicBool::new(false), - stop: AtomicBool::new(false), - monitor_view: AtomicBool::new(false), - } - } - } - - // 服务端构建部分 - impl PadServer { - - pub fn build_simple() -> Arc<PadServer> { - Arc::new(Self::default() - .addr(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), DEFAULT_PORT) - .put_profile(GameProfile::default()).to_owned()) - } - - pub fn addr(&mut self, ip_addr: IpAddr, port: u16) -> &mut PadServer { - self.ip_addr(ip_addr).port(port) - } - - pub fn socket_addr(&mut self, socket_addr: SocketAddr) -> &mut PadServer { - self.ip_addr(socket_addr.ip()).port(socket_addr.port()) - } - - pub fn port(&mut self, port: u16) -> &mut PadServer { - self.port = port; - self - } - - pub fn ip_addr(&mut self, ip_addr: IpAddr) -> &mut PadServer { - self.address = ip_addr; - self - } - - pub fn put_profile(&mut self, profile: GameProfile) -> &mut PadServer { - self.game_profile = profile; - self - } - - pub fn enable_console(&mut self) -> &mut PadServer { - self.enable_console = true; - self - } - - pub fn quiet(&mut self) -> &mut PadServer { - self.quiet = true; - self - } - - pub fn register_keys(&mut self, keys: LayoutKeyRegisters) -> &mut PadServer { - self.keys = keys; - self - } - - pub fn register_button(&mut self, key: u8, name: &str) -> &mut PadServer { - self.keys.button_keys.insert(key, name.to_string()); - self - } - - pub fn register_axis(&mut self, key: u8, name: &str) -> &mut PadServer { - self.keys.axis_keys.insert(key, name.to_string()); - self - } - - pub fn register_direction(&mut self, key: u8, name: &str) -> &mut PadServer { - self.keys.direction_keys.insert(key, name.to_string()); - self - } - - pub fn build(&self) -> Arc<PadServer> { - Arc::new(self.clone()) - } - } - - // 服务端消息管理 - impl PadServer { - - pub fn put_msg_to(&self, msg: GameMessage, player: &PlayerInfo) { - match self.write_list.lock() { - Ok(mut guard) => { - let hash = &player.account.player_hash.clone(); - if ! guard.contains_key(hash.as_str()) { - guard.entry(player.account.player_hash.clone()) - .or_insert_with(VecDeque::new) - .push_back(msg); - } - } - Err(_) => { - error!("Cannot lock \"{:?}\" in write_list", player.account.player_hash); - } - } - } - - pub fn put_msg_to_all(&self, msg: &GameMessage) { - match self.list_players() { - Ok(list) => { - for player in list { - self.put_msg_to(msg.clone(), &player); - } - } - Err(_) => { - error!("Cannot put GameMessage with no players."); - } - } - } - - pub fn pop_a_msg(&self) -> Option<(PlayerInfo, ControlMessage)> { - match self.control_data.lock() { - Ok(mut guard) => { - guard.pop_control_event(self) - } - Err(_) => { - None - } - } - } - } - - // 操控信息管理 - impl PadServer { - pub fn get_player_direction(&self, player: &PlayerInfo, key: &u8) -> Option<(f64, f64)> { - match self.control_data.lock() { - Ok(guard) => { - guard.get_direction(player, &key) - } - Err(_) => { - None - } - } - } - - pub fn get_player_axis(&self, player: &PlayerInfo, key: &u8) -> Option<f64> { - match self.control_data.lock() { - Ok(guard) => { - guard.get_axis(player, &key) - } - Err(_) => { - None - } - } - } - - pub fn get_player_button_status(&self, player: &PlayerInfo, key: &u8) -> Option<bool> { - match self.control_data.lock() { - Ok(guard) => { - guard.get_button_status(player, &key) - } - Err(_) => { - None - } - } - } - } - - // 服务端玩家管理 - impl PadServer { - - pub fn is_player_online (&self, player: &PlayerInfo) -> bool { - let guard = self.online_players.lock().unwrap(); - guard.contains_key(&player.account.player_hash) - } - - fn set_player_online (&self, player: &PlayerInfo, online: bool) { - let online_current = self.is_player_online(player); - if online_current && !online { - let mut guard = self.online_players.lock().unwrap(); - guard.remove(&player.account.player_hash); - info!("{} is OFFLINE!", player.account.id); - } else if !online_current && online { - let mut guard = self.online_players.lock().unwrap(); - guard.insert(player.account.player_hash.clone(), player.clone()); - info!("{} is ONLINE!", player.account.id); - } - } - - pub fn is_player_banned (&self, player: &PlayerInfo) -> bool { - let guard = self.banned_players.lock().unwrap(); - guard.contains_key(&player.account.player_hash) - } - - pub fn kick_player(&self, player: &PlayerInfo) { - if self.is_player_online(player) { - self.put_msg_to(Leave(LeaveReason::YouAreKicked), player); - } - } - - pub fn ban_player(&self, player: &PlayerInfo) { - self.set_player_banned(player, true); - if self.is_player_online(player) { - self.put_msg_to(Leave(LeaveReason::YouAreBanned), player); - } - } - - pub fn pardon_player(&self, player: &PlayerInfo) { - self.set_player_banned(player, false); - } - - fn set_player_banned (&self, player: &PlayerInfo, banned: bool) { - let banned_current = self.is_player_banned(player); - if banned_current && !banned { - let mut guard = self.banned_players.lock().unwrap(); - guard.remove(&player.account.player_hash); - info!("Pardoned player {}", player.account.id); - } else if !banned_current && banned { - let mut guard = self.banned_players.lock().unwrap(); - guard.insert(player.account.player_hash.clone(), player.clone()); - info!("Banned player {}!", player.account.id); - } - } - - pub fn list_players(&self) -> Result<Vec<PlayerInfo>, PoisonError<MutexGuard<HashMap<String, PlayerInfo>>>> { - match self.online_players.lock() { - Ok(guard) => { - Ok(guard.values().cloned().collect()) - } - Err(err) => Err(err) - } - } - - pub fn list_players_banned(&self) -> Result<Vec<PlayerInfo>, PoisonError<MutexGuard<HashMap<String, PlayerInfo>>>> { - match self.banned_players.lock() { - Ok(guard) => { - Ok(guard.values().cloned().collect()) - } - Err(err) => Err(err) - } - } - - pub fn find_online_player(&self, hash: String) -> Option<PlayerInfo> { - match self.online_players.lock() { - Ok(guard) => { - guard.get(&hash).cloned() - } - Err(_) => None - } - } - } - - // 服务端状态控制 - #[allow(dead_code)] - impl PadServer { - - pub fn stop_server(&self) { - self.put_msg_to_all(&Leave(ServerClosed)); - self.stop.store(true, SeqCst); - } - - pub fn start_server(self: Arc<Self>) { - - // 构建 Logger - if ! self.quiet { - logger_build(); - } - - // 运行时 - let runtime = Self::get_runtime(); - - info!("Starting \"NoGamepads Server\"."); - - // 入口 - let console = self.enable_console; - let entry = self.get_entry(console); - - // 阻塞运行 - runtime.block_on(entry); - } - - fn get_runtime() -> Runtime { - tokio::runtime::Builder::new_multi_thread() - .thread_name("nogpad-server") - .thread_stack_size(32 * 1024 * 1024) - .enable_time() - .enable_io() - .build() - .unwrap() - } - - fn get_entry(self: Arc<Self>, debug: bool) -> impl Future<Output = ()> + Send + 'static { - async move { - let main_thread = spawn({ - let client = Arc::clone(&self); - async move { - Self::main_request_thread(client).await - } - }); - - let background_thread = spawn({ - let client = Arc::clone(&self); - async move { - Self::background_thread(client).await - } - }); - - let ctrl_c_thread = spawn({ - let client = Arc::clone(&self); - async move { - Self::process_ctrl_c(client).await - } - }); - - if debug { - let debug_cli = spawn({ - let client = Arc::clone(&self); - async move { - Self::process_debug_cli(client).await - } - }); - - let _ = tokio::join!(ctrl_c_thread, debug_cli, main_thread, background_thread); - } else { - let _ = tokio::join!(ctrl_c_thread, main_thread, background_thread); - } - } - } - - pub fn lock_game(&self) { - self.game_locked.store(true, SeqCst); - } - - pub fn unlock_game(&self) { - self.game_locked.store(false, SeqCst); - } - - pub fn is_game_locked(&self) -> bool { - self.game_locked.load(SeqCst) - } - - pub fn enter_monitor(&self) { - self.monitor_view.store(true, SeqCst); - } - - pub fn exit_monitor(&self) { - self.monitor_view.store(false, SeqCst); - } - - async fn main_request_thread(self: Arc<Self>) { - - let addr_str = format!("{}:{}", self.address.to_string(), self.port); - info!("Server listening at {}", addr_str); - - // Tcp 监听器 - let listener : TcpListener; - match TcpListener::bind(&addr_str).await { - Ok(result) => { - info!("Listener created."); - listener = result; - } - Err(_) => { - error!("Server listening at {} failed!", addr_str); - exit(1); - } - } - - // 请求信息循环 - loop { - match listener.accept().await { - Ok((stream, _)) => { - spawn(Self::process_request(Arc::clone(&self), stream)); - } - Err(error) => { - error!("Error: {}", error); - } - } - } - } - - async fn process_request(self: Arc<Self>, mut stream: TcpStream) { - let mut buffer = [0; 1024]; - let connection_msg : ConnectionMessage = read_msg(&mut buffer, &mut stream).await; - match connection_msg { - - // 客户端请求加入游戏,并建立长连接 - ConnectionMessage::Connection(info) => { - - // 加入游戏资格检测 - info!("Account {} trying to connect.", info.account.player_hash); - - // 0. 当前游戏是否已经锁定? - if self.is_game_locked() { - // 当前游戏已经锁定,禁止加入玩家,发送失败信息,并断开连接 - send_msg(&mut stream, ConnectionCallbackMessage::Deny(GameLocked)).await; - return; - } - - // 1. 是否存在重复玩家? - let online = self.is_player_online(&info); - if online { - // 当前玩家已在线,发送失败信息,并断开连接 - send_msg(&mut stream, ConnectionCallbackMessage::Deny(ContainSamePlayer)).await; - return; - } - - // 2. 该玩家是否被封禁? - let banned = self.is_player_banned(&info); - if banned { - // 当前玩家已被封禁,发送失败信息,并断开连接 - send_msg(&mut stream, ConnectionCallbackMessage::Deny(PlayerBanned)).await; - return; - } - - // OK!若执行到此处,说明该玩家具有加入资格,Welcome! - - send_msg(&mut stream, ConnectionCallbackMessage::Ok).await; - let callback : ConnectionMessage = read_msg(&mut buffer, &mut stream).await; - - match callback { - // 玩家已就绪,发送 Welcome 信息以邀请该玩家加入游戏 - ConnectionMessage::Ready => { - info!("Player \"{}\" is ready!", info.account.id); - - // 发送 Welcome - send_msg(&mut stream, ConnectionCallbackMessage::Welcome).await; - - // 注册该玩家到在线列表 - self.set_player_online(&info, true); - - // 启动控制循环 - - spawn(Self::long_connection(Arc::clone(&self), stream, info)); - }, - _ => { - send_msg(&mut stream, ConnectionCallbackMessage::Deny(WhatTheHell)).await; // WTH ? - } - } - } - - // 客户端请求获得游戏信息 - ConnectionMessage::RequestProfile => { - // 发送游戏信息到客户端 - send_msg(&mut stream, ConnectionCallbackMessage::Profile(self.game_profile.clone())).await; - } - - // 客户端发来了错误信息 - ConnectionMessage::Err => { - match stream.peer_addr() { - Ok(addr) => { - warn!("Received an error message from {}.", addr.to_string()); - } - Err(_) => { - warn!("Received an error message from unknown pad_service."); - } - } - } - - // 客户端发来了不相干的信息 - _ => { - match stream.peer_addr() { - Ok(addr) => { - warn!("Received unknown connection message from {}.", addr.to_string()); - } - Err(_) => { - warn!("Received unknown connection message from unknown pad_service."); - } - } - } - } - } - - async fn long_connection(self: Arc<Self>, stream: TcpStream, player_info: PlayerInfo) { - let player_info_arc = Arc::new(player_info); - let (reader, writer) = io::split(stream); - spawn(Self::read_task(Arc::clone(&self), reader, Arc::clone(&player_info_arc))); - spawn(Self::write_task(Arc::clone(&self), writer, Arc::clone(&player_info_arc))); - } - - async fn read_task(self: Arc<Self>, - mut reader: ReadHalf<TcpStream>, - player_info: Arc<PlayerInfo>) { - let player_hash = player_info.account.player_hash.clone(); - let mut buf = [0u8; 1024]; - loop { - match reader.read(&mut buf).await { - Ok(0) => break, - Ok(n) => { - let msg = ControlMessage::de(buf[0..n].to_vec()); - match self.control_data.lock() { - Ok(mut guard) => { - info!("{:?} from {}({})", &msg, player_info.customize.nickname, player_info.account.id); - let player_info = self.find_online_player(player_hash.clone()); - if player_info.is_some() { - let player_info = player_info.unwrap(); - guard.insert_control(player_info, msg); - } else { - warn!("Player \"{}\" not found!", player_hash); - } - } - Err(_) => { - } - } - } - Err(e) => { - warn!("Error reading from stream: {}", e); - - self.set_player_online(&player_info, false); - - // 放入一条错误信息到队列,使 write_task 及时发现该玩家离开 - self.put_msg_to(GameMessage::Err, &player_info); - - break; - } - } - } - } - - async fn write_task(self: Arc<Self>, - mut writer: WriteHalf<TcpStream>, - player_info: Arc<PlayerInfo>) { - let player_hash = player_info.account.player_hash.clone(); - let mut exit = false; - loop { - let msg : Option<GameMessage>; - match self.write_list.lock() { - Ok(mut hash_map) => { - if ! hash_map.is_empty() { - match hash_map.get_mut(&player_hash) { - None => { - msg = None; - } - Some(queue) => { - if ! queue.is_empty() { - msg = queue.pop_front(); - } else { - msg = None; - hash_map.remove(&player_hash); - } - } - } - } - else { msg = None; } - } - Err(_) => { - msg = None; - } - } - if msg.is_some() { - let msg = msg.unwrap(); - match &writer.write_all(NgpdMessageEncoder::en(&msg).as_slice()).await { - Ok(_) => { - - info!("Sent {:?} to {}", msg, &player_info.account.id); - } - Err(error) => { - warn!("Sent {:?} to {} failed!", msg, &player_info.account.id); - warn!("{:?}", error); - - exit = true; - } - } - } - if exit { - warn!("Long connection between \"{}\" closed.", &player_info.account.id); - break - } - } - } - - async fn background_thread(self: Arc<Self>) { - loop { - // 退出程序的监听 - if self.stop.load(SeqCst) { - tokio::time::sleep(Duration::from_secs(1)).await; - - info!("Main thread exited."); - exit(0); - } - } - } - - async fn process_ctrl_c(self: Arc<Self>) { - loop { - signal::ctrl_c().await.unwrap(); - if self.monitor_view.load(SeqCst) { - self.exit_monitor(); - } else { - self.stop_server(); - info!("Stopping server..."); - break; - } - } - } - - async fn process_debug_cli(self: Arc<Self>) { - loop { - if self.stop.load(SeqCst) { - info!("Debug console exited"); - return; - } - - if ! self.monitor_view.load(SeqCst) { - tokio::time::sleep(Duration::from_secs_f64(0.2)).await; - - // 控制台模式 - let option: Option<Psc> = read_cli( - format!("SERVER {}> ", self.address.to_string()).as_str(), - "psc".to_string(), - Psc::command() - ).await; - match option { - None => {} - Some(cmd) => { - process_debug_cmd(cmd, Arc::clone(&self)); - } - } - } else { - tokio::time::sleep(Duration::from_secs_f64(0.2)).await; - - // 实时信息模式 - - // 表头文本 - let mut header : Vec<String> = Vec::new(); - header.push("PLAYER \\ KEYS".to_string()); - self.all_keys(|key, name, i|{ - if i == 0 { - header.push(format!("{}(btn:{})", name, key)); - } else if i == 1 { - header.push(format!("{}(dir:{})", name, key)); - } else if i == 2 { - header.push(format!("{}(ax:{})", name, key)); - } - }); - - let mut info_table = Table::new(); - - // 添加表头 - info_table.add_row(Row::from_iter(header)); - - // 玩家 - let players = &self.deref().online_players; - match players.lock() { - Ok(guard) => { - for (_player_hash, player) in guard.iter() { - - // 行 - let mut line = Vec::new(); - line.push(player.account.id.to_string()); - self.all_keys(|key, _, i|{ - if i == 0 { - // 按钮 - let r = self.get_player_button_status(player, key); - if r.is_some() { - let r = r.unwrap(); - if r { - line.push("TRUE".to_string()); - } else { - line.push("FALSE".to_string()); - } - } else { - line.push("UNKNOWN".to_string()); - } - - } else if i == 1 { - // 方向 - let r = self.get_player_direction(player, key); - if r.is_some() { - let r = r.unwrap(); - line.push(format!("{}, {}", r.0, r.1)); - } else { - line.push("UNKNOWN".to_string()); - } - - } else if i == 2 { - // 轴向 - let r = self.get_player_axis(player, key); - if r.is_some() { - let r = r.unwrap(); - line.push(format!("{}", r)); - } else { - line.push("UNKNOWN".to_string()); - } - } - }); - - // 添加行文本 - info_table.add_row(Row::from_iter(line)); - } - } - Err(_) => {} - } - - let result = info_table.to_string(); - let _ = clear(); - println!("[HELP] Ctrl + C to exit monitor.\n{}", result); - } - } - } - - fn all_keys<F>(self: &Arc<Self>, mut f: F) - where F: FnMut(&u8, &String, i32){ - let mut i = 0; - for iter in [ - self.keys.button_keys.iter(), - self.keys.direction_keys.iter(), - self.keys.axis_keys.iter(), - ] { - for (key, name) in iter { - f(key, name, i); - } - i += 1; - } - } - } -}
\ No newline at end of file diff --git a/core/src/pad_service/server_debug_cli.rs b/core/src/pad_service/server_debug_cli.rs deleted file mode 100644 index ac52253..0000000 --- a/core/src/pad_service/server_debug_cli.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::collections::HashMap; -use crate::pad_data::pad_messages::nogamepads_messages::{GameMessage}; -use crate::pad_service::server::nogamepads_server::PadServer; -use clap::{Args, Parser, Subcommand}; -use std::sync::{Arc, MutexGuard, PoisonError}; -use log::{error, info}; -use crate::pad_data::pad_player_info::nogamepads_player_info::PlayerInfo; - -/// NoGamePads Server - Cli -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -pub struct Psc { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand, Debug)] -enum Commands { - - // 清屏 - #[command(about = "Clean the screen")] - Clear, - - #[command(about = "Enter monitor")] - Monitor, - - // 关闭服务器 - #[command(about = "Close the server")] - Stop, - - // 展示所有玩家 - #[command(about = "List all online players")] - List, - - // 展示所有封禁的玩家 - #[command(about = "List all banned players")] - Banned, - - // 取出一条消息 - #[command(about = "Pop a message")] - Pop, - - // 踢出玩家 - #[command(about = "Kick a player")] - Kick(PlayerArgs), - - // 封禁玩家 - #[command(about = "Ban a player")] - Ban(PlayerArgs), - - // 解封(赦免)玩家 - #[command(about = "Pardon a player")] - Pardon(PlayerArgs), - - // 锁定游戏 - #[command(about = "Lock this game")] - Lock, - - // 解锁游戏 - #[command(about = "Unlock this game")] - Unlock, - - // 激活事件触发器 - #[command(about = "Send SkinEventTrigger")] - Event(EventArgs) -} - -// 检查收到的消息 -#[derive(Args, Debug)] -struct ReceivedArgs { - - #[arg(default_value = "0")] - player: usize, - - #[arg(long)] - list: bool, -} - -/// 激活事件触发器 参数 -#[derive(Args, Debug)] -struct EventArgs { - - // 玩家序号 - #[arg(value_name = "PLAYER_INDEX")] - index: usize, - - // 事件编号 - #[arg(value_name = "CONTENT")] - message: u8, -} - -#[derive(Args, Debug)] -struct PlayerArgs { - - // 玩家序号 - #[arg(value_name = "PLAYER_INDEX")] - index: usize -} - -pub fn process_debug_cmd (cmd: Psc, server: Arc<PadServer>) { - match cmd.command { - - Commands::Clear => { - clearscreen::clear().expect("Failed to clear screen"); - } - - Commands::Monitor => { - server.enter_monitor(); - } - - Commands::Stop => { - server.stop_server(); - } - - Commands::List => { - print_player_list(server.list_players()); - } - - Commands::Banned => { - print_player_list(server.list_players_banned()); - } - - Commands::Pop => { - let message = server.pop_a_msg(); - if message.is_some() { - let (player, msg) = message.unwrap(); - info!("{:?} from \"{}\"({})", msg, player.customize.nickname, player.account.id); - } - } - - Commands::Kick(args) => { - let player = get_player_by_index(&server, args.index); - if player.is_some() { - let player = player.unwrap(); - server.kick_player(&player); - } - } - - Commands::Ban(args) => { - let player = get_player_by_index(&server, args.index); - if player.is_some() { - let player = player.unwrap(); - server.ban_player(&player); - } - } - - Commands::Pardon(args) => { - let player = get_player_by_ban_index(&server, args.index); - if player.is_some() { - let player = player.unwrap(); - server.pardon_player(&player); - } - } - - Commands::Lock => { - if ! server.is_game_locked() { - server.lock_game(); - info!("Game locked"); - } - } - - Commands::Unlock => { - if server.is_game_locked() { - server.unlock_game(); - info!("Game unlocked"); - } - } - - Commands::Event(args) => { - put_to_list(server, args.index, GameMessage::SkinEventTrigger(args.message)); - } - } -} - -fn put_to_list(server: Arc<PadServer>, player_index: usize, message: GameMessage) { - match get_player_by_index(&server, player_index) { - None => { - error!("Put message failed : Player index \"{}\" not found!", player_index); - } - Some(player) => { - server.put_msg_to(message, &player); - } - } -} - -fn get_player_by_index(server: &Arc<PadServer>, index: usize) -> Option<PlayerInfo> { - let list = server.list_players().unwrap_or(Vec::new()); - let max = list.iter().count(); - let index = if max > 0 { index.clamp(0, max - 1) } else { 0 }; - - let result = list.get(index).cloned(); - result -} - -fn get_player_by_ban_index(server: &Arc<PadServer>, index: usize) -> Option<PlayerInfo> { - let list = server.list_players_banned().unwrap_or(Vec::new()); - let max = list.iter().count(); - let index = if max > 0 { index.clamp(0, max - 1) } else { 0 }; - - let result = list.get(index).cloned(); - result -} - -fn print_player_list(list: Result<Vec<PlayerInfo>, PoisonError<MutexGuard<HashMap<String, PlayerInfo>>>>) { - let list = list.unwrap_or(Vec::new()); - let mut i = 0; - for player in list { - let n = player.customize.nickname; - info!("({}){} ", i, n); - i += 1; - } -}
\ No newline at end of file diff --git a/core/src/service/cli_addition/mod.rs b/core/src/service/cli_addition/mod.rs new file mode 100644 index 0000000..ced68da --- /dev/null +++ b/core/src/service/cli_addition/mod.rs @@ -0,0 +1,2 @@ +pub mod runtime_consoles; +mod utils; diff --git a/core/src/service/cli_addition/runtime_consoles.rs b/core/src/service/cli_addition/runtime_consoles.rs new file mode 100644 index 0000000..76ff20c --- /dev/null +++ b/core/src/service/cli_addition/runtime_consoles.rs @@ -0,0 +1,60 @@ +use crate::service::cli_addition::utils::read_cli; +use clap::{Command, FromArgMatches}; +use std::sync::{Arc, Mutex}; +use tokio::{join, spawn}; +use crate::service::service_runner::NoGamepadsService; + +pub struct RuntimeConsole<PadService, Cmd> +where Cmd: FromArgMatches { + command: Command, + prefix: String, + service: Arc<Mutex<PadService>>, + process_command: fn(Arc<Mutex<PadService>>, Cmd), +} + +impl<PadService: Send + 'static, Cmd: FromArgMatches + 'static> RuntimeConsole<PadService, Cmd> { + pub fn build(command: Command, + prefix: String, + service: Arc<Mutex<PadService>>, + process_command: fn(Arc<Mutex<PadService>>, Cmd), + ) -> RuntimeConsole<PadService, Cmd> { + RuntimeConsole { command, prefix, service, process_command } + } + + pub fn build_entry(self) -> NoGamepadsService { + let arc = Arc::new(self); + + let entry = async move { + let console_main = spawn({ + let console = Arc::clone(&arc); + async move { + Self::console_main(console).await + } + }); + + // Join + let _ = join!(console_main); + }; + + Box::pin(entry) + } + + async fn console_main(self: Arc<RuntimeConsole<PadService, Cmd>>) { + let prefix_uppercase = self.prefix.to_uppercase(); + let prefix_lowercase = self.prefix.to_lowercase(); + + loop { + let option : Option<Cmd> = read_cli( + format!("{}> ", prefix_uppercase), + &prefix_lowercase, + self.command.clone() + ).await; + match option { + None => {} + Some(cmd) => { + (self.process_command)(Arc::clone(&self.service), cmd); + } + } + } + } +}
\ No newline at end of file diff --git a/core/src/service/cli_addition/utils.rs b/core/src/service/cli_addition/utils.rs new file mode 100644 index 0000000..05969a5 --- /dev/null +++ b/core/src/service/cli_addition/utils.rs @@ -0,0 +1,58 @@ +use clap::{Command, FromArgMatches}; +use clap::ColorChoice::Auto; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt}; + +pub async fn read_cli<Cmd>(prefix: String, entry: &String, cmd: Command) -> Option<Cmd> +where Cmd: FromArgMatches { + let input: String = { + let mut buffer = String::new(); + let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()); + let mut stdout = tokio::io::stdout(); + + stdout.write_all(prefix.as_bytes()).await.ok().unwrap(); + stdout.flush().await.ok().unwrap(); + + stdin.read_line(&mut buffer).await.ok().unwrap(); + buffer.trim().to_string() + }; + + process_debug_cli(entry, input, cmd).await +} + +async fn process_debug_cli<Cmd>(entry: &String, input: String, cmd: Command) -> Option<Cmd> +where Cmd: FromArgMatches { + if input.trim().is_empty() { + return None; + } + + let cmd = cmd + .color(Auto) + .help_template( + "{subcommands}{options}" + ) + .disable_help_flag(true) + .disable_version_flag(true); + + let args = shell_words::split(input.as_str()).unwrap_or_else(|_e| { + ["".to_string()].to_vec() + }); + + let full_args = std::iter::once(entry.into()).chain(args); + + match cmd.try_get_matches_from(full_args) { + Ok(matches) => { + match Cmd::from_arg_matches(&matches) { + Ok(cmd) => { + Some(cmd) + } + Err(_err) => { + None + } + } + } + Err(err) => { + println!("{}", err); + None + } + } +}
\ No newline at end of file diff --git a/core/src/service/mod.rs b/core/src/service/mod.rs new file mode 100644 index 0000000..2631af8 --- /dev/null +++ b/core/src/service/mod.rs @@ -0,0 +1,4 @@ +pub mod cli_addition; +pub mod tcp_network; +pub mod service_types; +pub mod service_runner; diff --git a/core/src/service/service_runner.rs b/core/src/service/service_runner.rs new file mode 100644 index 0000000..bd2d1d9 --- /dev/null +++ b/core/src/service/service_runner.rs @@ -0,0 +1,31 @@ +use std::pin::Pin; +use crate::service::tcp_network::utils::tokio_utils::build_tokio_runtime; +use tokio::spawn; + +#[macro_export] +#[allow(unused_macros)] +macro_rules! run_services { + ($($service:expr),+ $(,)?) => { + nogamepads_core::service::service_runner::ServiceRunner::run(Vec::from([$($service),+])); + }; +} + +pub type NoGamepadsService = Pin<Box<dyn Future<Output = ()> + Send>>; + +pub struct ServiceRunner; + +impl ServiceRunner { + pub fn run(futures: Vec<NoGamepadsService>) { + let runtime = build_tokio_runtime("nogamepads".to_string()); + runtime.block_on(async { + let mut handles = Vec::new(); + for fut in futures { + handles.push(spawn(fut)); + } + + for handle in handles { + handle.await.expect("Task panicked"); + } + }); + } +}
\ No newline at end of file diff --git a/core/src/service/service_types.rs b/core/src/service/service_types.rs new file mode 100644 index 0000000..dda18bd --- /dev/null +++ b/core/src/service/service_types.rs @@ -0,0 +1,13 @@ +use bincode::{Decode, Encode}; +use crate::data::message::traits::MessageEncoder; +use crate::encoder; + +#[derive(Default, Encode, Decode, PartialEq, Debug, Clone, Eq, Hash)] +pub enum ServiceType { + #[default] + TCPConnection, + BlueTooth, + USB, +} + +encoder!(ServiceType);
\ No newline at end of file diff --git a/core/src/service/tcp_network/long_connection.rs b/core/src/service/tcp_network/long_connection.rs new file mode 100644 index 0000000..ada9751 --- /dev/null +++ b/core/src/service/tcp_network/long_connection.rs @@ -0,0 +1,272 @@ +use crate::data::player::structs::Player; +use crate::service::tcp_network::pad_client::structs::PadClientNetwork; +use crate::service::tcp_network::pad_server::structs::PadServerNetwork; +use std::sync::Arc; +use std::sync::atomic::Ordering::SeqCst; +use log::{error, info, trace, warn}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; +use tokio::net::TcpStream; +use tokio::spawn; +use nogamepads::entry_mutex; +use crate::data::message::enums::{ControlMessage, GameMessage}; +use crate::data::message::enums::ExitReason::GameOver; +use crate::data::message::enums::GameMessage::{End, LetExit}; +use crate::data::message::traits::{MessageEncoder, MessageManager}; +use crate::service::service_types::ServiceType; +use crate::service::service_types::ServiceType::TCPConnection; + +impl PadServerNetwork { + + pub async fn start_long_connection(self: Arc<Self>, player: Player, stream: TcpStream) { + let (reader, writer) = stream.into_split(); + spawn(Self::read_task(Arc::clone(&self), player.clone(), reader)); + spawn(Self::write_task(Arc::clone(&self), player.clone(), writer)); + } + + async fn read_task(self: Arc<Self>, player: Player, mut reader: OwnedReadHalf) { + info!("[TCP Server] [Runtime] Reader started."); + entry_mutex!(self.runtime, |guard| { + guard.reader_count += 1; + }); + + let mut buffer = [0u8; 1024]; + let mut err_message_counter = 0; + + loop { + let read = reader.read(&mut buffer).await; + match read { + Ok(size) => { + let message : ControlMessage = ControlMessage::de(buffer[0..size].to_vec()); + + // Preprocess messages: handle exit messages. + match message { + ControlMessage::Exit => { + info!("[TCP Server] [Runtime] Player {} exited.", player.account.id); + entry_mutex!(self.runtime, |guard| { + guard.send((player.account.clone(), End), player.account.clone(), ServiceType::TCPConnection); + }); + break; + } + + ControlMessage::Err => { + info!("[TCP Server] [Runtime] Received error message from {}.", player.account.id); + if err_message_counter < 16 { + err_message_counter += 1; + } else { + warn!("[TCP Server] [Runtime] Too many error messages! Connection closed."); + entry_mutex!(self.runtime, |guard| { + guard.send((player.account.clone(), End), player.account.clone(), ServiceType::TCPConnection); + }); + break; + } + } + + _ => {} + } + + // Process messages + entry_mutex!(self.runtime, |guard| { + trace!("[TCP Server] [Runtime] Received: {:?}", &message); + guard.put_into_receive_list((player.account.clone(), message), player.account.clone(), ServiceType::TCPConnection); + }); + } + + Err(error) => { + warn!("[TCP Server] [Runtime] Error reading from socket: {:?}", error); + break; + } + } + + // Check close + entry_mutex!(self.runtime, |guard| { + if guard.data.close.load(SeqCst) { + break; + } + }); + } + + info!("[TCP Server] [Runtime] Reader between {} closed.", player.account.id); + entry_mutex!(self.runtime, |guard| { + guard.send((player.account.clone(), End), player.account.clone(), TCPConnection); + guard.reader_count -= 1; + }) + } + + async fn write_task(self: Arc<Self>, player: Player, mut writer: OwnedWriteHalf) { + info!("[TCP Server] [Runtime] Writer started."); + entry_mutex!(self.runtime, |guard| { + guard.writer_count += 1; + }); + + let mut closed = false; + + loop { + // Check close + entry_mutex!(self.runtime, |guard| { + if guard.data.close.load(SeqCst) && !closed { + guard.send((player.account.clone(), LetExit(GameOver)), player.account.clone(), ServiceType::TCPConnection); + closed = true; + } + }); + + let mut message = None; + entry_mutex!(self.runtime, |guard| { + message = guard.pop_from_send_list(player.account.clone(), ServiceType::TCPConnection); + }); + + if let Some(message) = message { + + // Preprocess messages: handle end messages. + match message.1 { + End => { + break; + } + GameMessage::Err => { + continue; + } + _ => {} + } + + // Process messages + match writer.write_all(GameMessage::en(&message.1).as_slice()).await { + Ok(_) => { + trace!("[TCP Server] [Runtime] Sent {:?} to {}", &message.1, player.account.id); + writer.flush().await.expect("[TCP Client] [Runtime] Writer encountered an error"); + } + Err(error) => { + warn!("[TCP Server] [Runtime] Sent {:?} to {} failed: {}", &message.1, player.account.id, error); + break; + } + } + } + } + + info!("[TCP Server] [Runtime] Writer between {} closed.", player.account.id); + entry_mutex!(self.runtime, |guard| { + guard.data.sign_player_online_status(&player, TCPConnection, false); + guard.writer_count -= 1; + }) + } +} + +impl PadClientNetwork { + + pub async fn start_long_connection(self: Arc<Self>, stream: TcpStream) { + let (reader, writer) = stream.into_split(); + spawn(Self::read_task(Arc::clone(&self), reader)); + spawn(Self::write_task(Arc::clone(&self), writer)); + } + + async fn read_task(self: Arc<Self>, mut reader: OwnedReadHalf) { + info!("[TCP Client] [Runtime] Reader started."); + + let mut buffer = [0u8; 1024]; + let mut err_message_counter = 0; + loop { + // Check close + entry_mutex!(self.runtime, |guard| { + if guard.close.load(SeqCst) { + break; + } + }); + + let read = reader.read(&mut buffer).await; + match read { + Ok(size) => { + let message : GameMessage = GameMessage::de(buffer[0..size].to_vec()); + + // Preprocess messages: handle exit messages. + match message { + LetExit(reason) => { + info!("[TCP Client] [Runtime] Server let you exit: {:?}", reason); + entry_mutex!(self.runtime, |guard| { + guard.send(ControlMessage::End, 0, ServiceType::TCPConnection); + }); + break; + } + + GameMessage::Err => { + info!("[TCP Client] [Runtime] Received error message from server."); + if err_message_counter < 16 { + err_message_counter += 1; + } else { + warn!("[TCP Client] [Runtime] Too many error messages! Connection closed."); + entry_mutex!(self.runtime, |guard| { + guard.send(ControlMessage::End, 0, ServiceType::TCPConnection); + }); + break; + } + } + + _ => {} + } + + // Process messages + entry_mutex!(self.runtime, |guard| { + trace!("[TCP Client] [Runtime] Received: {:?}", &message); + guard.put_into_receive_list(message, 0, ServiceType::TCPConnection); + }); + } + Err(err) => { + error!("[TCP Client] [Runtime] Reader encountered an error: {}", err); + break; + } + } + } + + info!("[TCP Client] [Runtime] Reader closed."); + } + + async fn write_task(self: Arc<Self>, mut writer: OwnedWriteHalf) { + info!("[TCP Client] [Runtime] Writer started."); + + let mut closed = false; + + loop { + // Check close + if !closed { + entry_mutex!(self.runtime, |guard| { + if guard.close.load(SeqCst) { + guard.send(ControlMessage::Exit, 0, ServiceType::TCPConnection); + closed = true; + } + }); + } + + let mut message = None; + entry_mutex!(self.runtime, |guard| { + message = guard.pop_from_send_list(0, ServiceType::TCPConnection); + }); + + if let Some(message) = message { + + // Preprocess messages: handle exit messages. + match message { + ControlMessage::End => { break; } + ControlMessage::Err => { + continue; + } + _ => {} + } + + // Process messages + match writer.write_all(ControlMessage::en(&message).as_slice()).await { + Ok(_) => { + trace!("[TCP Client] [Runtime] Sent {:?}.", &message); + writer.flush().await.expect("[TCP Client] [Runtime] Writer encountered an error"); + } + Err(error) => { + warn!("[TCP Client] [Runtime] Sent {:?} failed: {}", &message, error); + break; + } + } + } + } + + info!("[TCP Client] [Runtime] Writer closed."); + entry_mutex!(self.runtime, |guard| { + guard.close(); + }) + } +}
\ No newline at end of file diff --git a/core/src/service/tcp_network/mod.rs b/core/src/service/tcp_network/mod.rs new file mode 100644 index 0000000..4954c1c --- /dev/null +++ b/core/src/service/tcp_network/mod.rs @@ -0,0 +1,6 @@ +pub mod utils; +pub mod pad_client; +pub mod pad_server; +pub mod long_connection; + +pub const DEFAULT_PORT : u16 = 5989;
\ No newline at end of file diff --git a/core/src/service/tcp_network/pad_client/implements.rs b/core/src/service/tcp_network/pad_client/implements.rs new file mode 100644 index 0000000..584bc83 --- /dev/null +++ b/core/src/service/tcp_network/pad_client/implements.rs @@ -0,0 +1,156 @@ +use std::net::{IpAddr, SocketAddr}; +use std::sync::{Arc, Mutex}; +use std::sync::atomic::Ordering::SeqCst; +use std::time::Duration; +use log::{error, info, warn}; +use tokio::{join, spawn}; +use tokio::time::sleep; +use nogamepads::entry_mutex; +use crate::data::controller::runtime::structs::ControllerRuntime; +use crate::data::message::enums::ConnectionMessage::{Join, RequestGameInfos}; +use crate::data::message::enums::ConnectionResponseMessage; +use crate::service::service_runner::NoGamepadsService; +use crate::service::tcp_network::pad_client::structs::PadClientNetwork; +use crate::service::tcp_network::DEFAULT_PORT; +use crate::service::tcp_network::utils::stream_utils::{read_msg, send_msg}; +use crate::service::tcp_network::utils::tokio_utils::build_tokio_runtime; + +macro_rules! connect_once { + ($addr:expr, |$conn:ident| $code:block) => {{ + use tokio::net::TcpStream; + match TcpStream::connect($addr).await { + Ok(mut $conn) => { + $code + true + }, + Err(e) => { + error!("[TCP Client] [Main] Connection failed {:?}", e); + false + } + } + }} +} + +impl PadClientNetwork { + + pub fn build(runtime: Arc<Mutex<ControllerRuntime>>) -> PadClientNetwork { + PadClientNetwork { + addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)), + runtime + } + } + + pub fn bind_addr(&mut self, addr: SocketAddr) -> &mut PadClientNetwork { + self.addr = addr; + self + } + + pub fn bind_ip(&mut self, addr: IpAddr) -> &mut PadClientNetwork { + self.addr.set_ip(addr); + self + } + + pub fn bind_port(&mut self, port: u16) -> &mut PadClientNetwork { + self.addr.set_port(port); + self + } + + pub fn build_entry(self) -> NoGamepadsService { + let arc = Arc::new(self); + + let entry = async move { + // Connection thread: Download the relevant resources, verify connection eligibility, and attempt to join the game. + let connection_thread = spawn({ + let client = Arc::clone(&arc); + async move { + Self::connection_thread(client).await + } + }); + + // Join + let _ = join!(connection_thread); + }; + + Box::pin(entry) + } + + pub fn connect(self) { + let runtime = build_tokio_runtime("padclient_tcp".to_string()); + + info!("[TCP Client] Connecting to {}:{}", self.addr.ip().to_string(), self.addr.port()); + runtime.block_on(self.build_entry()); + } +} + +impl PadClientNetwork { + + async fn connection_thread(self: Arc<PadClientNetwork>) { + let mut buffer = [0; 1024]; + + // Requests game infos + if !connect_once!(self.addr, |stream| { + info!("[TCP Client] [Main] Requesting game infos."); + send_msg(&mut stream, RequestGameInfos).await; + let response : ConnectionResponseMessage = read_msg(&mut buffer, &mut stream).await; + match response { + ConnectionResponseMessage::GameInfos(infos) => { + entry_mutex!(self.runtime, |guard| { + guard.game_info = infos; + }); + info!("[TCP Client] [Main] Download game infos successfully."); + } + ConnectionResponseMessage::Err => { + warn!("[TCP Client] [Main] Download game infos failed."); + } + _ => { + warn!("[TCP Client] [Main] Not found game infos."); + } + } + }) { + return; + } + + // TODO :: Download game layouts + + // TODO :: Download skin assets + + // Try to join game + let _ = connect_once!(self.addr, |connection| { + let mut player = None; + entry_mutex!(self.runtime, |guard| { + player = Some(guard.player.clone()); + }); + if player.is_some() { + info!("[TCP Client] [Main] Trying to join game."); + send_msg(&mut connection, Join(player.unwrap())).await; + let response : ConnectionResponseMessage = read_msg(&mut buffer, &mut connection).await; + match response { + ConnectionResponseMessage::Welcome => { + + // Long Connection + info!("[TCP Client] [Main] Welcome"); + spawn(Self::start_long_connection(Arc::clone(&self), connection)); + } + ConnectionResponseMessage::Deny(why) => { + error!("[TCP Client] [Main] Connection denied: {:?}", why); + } + _ => { } + } + } else { + error!("[TCP Client] [Main] No player found."); + return; + } + }); + + loop { + sleep(Duration::from_millis(1000)).await; + entry_mutex!(self.runtime, |guard| { + if guard.close.load(SeqCst) { + break; + } + }) + } + + info!("[TCP Client] [Main] Main thread closed."); + } +}
\ No newline at end of file diff --git a/core/src/service/tcp_network/pad_client/mod.rs b/core/src/service/tcp_network/pad_client/mod.rs new file mode 100644 index 0000000..0ff870f --- /dev/null +++ b/core/src/service/tcp_network/pad_client/mod.rs @@ -0,0 +1,2 @@ +pub mod implements; +pub mod structs;
\ No newline at end of file diff --git a/core/src/service/tcp_network/pad_client/structs.rs b/core/src/service/tcp_network/pad_client/structs.rs new file mode 100644 index 0000000..10c93c4 --- /dev/null +++ b/core/src/service/tcp_network/pad_client/structs.rs @@ -0,0 +1,8 @@ +use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; +use crate::data::controller::runtime::structs::ControllerRuntime; + +pub struct PadClientNetwork { + pub(crate) addr: SocketAddr, + pub(crate) runtime: Arc<Mutex<ControllerRuntime>> +}
\ No newline at end of file diff --git a/core/src/service/tcp_network/pad_server/implements.rs b/core/src/service/tcp_network/pad_server/implements.rs new file mode 100644 index 0000000..c4d8cfc --- /dev/null +++ b/core/src/service/tcp_network/pad_server/implements.rs @@ -0,0 +1,186 @@ +use std::net::{IpAddr, SocketAddr}; +use std::sync::{Arc, Mutex}; +use std::sync::atomic::Ordering::SeqCst; +use std::time::Duration; +use log::{error, info, trace, warn}; +use tokio::{join, select, spawn}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::watch::{channel, Receiver, Sender}; +use tokio::time::sleep; +use nogamepads::entry_mutex; +use crate::data::game::runtime::structs::GameRuntime; +use crate::data::message::enums::ConnectionMessage; +use crate::data::message::enums::ConnectionMessage::{Join, RequestGameInfos, RequestLayoutConfigure, RequestSkinPackage, Ready}; +use crate::data::message::enums::ConnectionResponseMessage::{Deny, GameInfos, Welcome}; +use crate::service::service_runner::NoGamepadsService; +use crate::service::tcp_network::DEFAULT_PORT; +use crate::service::tcp_network::pad_server::structs::PadServerNetwork; +use crate::service::tcp_network::utils::stream_utils::{get_target_address, read_msg, send_msg}; +use crate::service::tcp_network::utils::tokio_utils::build_tokio_runtime; + +impl PadServerNetwork { + + pub fn build(runtime: Arc<Mutex<GameRuntime>>) -> PadServerNetwork { + let (close_tx, close_rx) = channel(false); + PadServerNetwork { + addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)), + runtime, + close_tx, + close_rx + } + } + + pub fn bind_ip(&mut self, ip: IpAddr) -> &mut PadServerNetwork { + self.addr.set_ip(ip); + self + } + + pub fn bind_port(&mut self, port: u16) -> &mut PadServerNetwork { + self.addr.set_port(port); + self + } + + pub fn build_entry(self) -> NoGamepadsService { + let arc = Arc::new(self); + + let entry = async move { + // Main thread: Used to handle connection requests, data requests, and transfer skin assets + let main_thread = spawn({ + let server = Arc::clone(&arc); + async move { + Self::main_thread(server).await + } + }); + + let close_checker = { + let server = Arc::clone(&arc); + async move { + Self::close_checker(server).await + } + }; + + // Join + let _ = join!(close_checker, main_thread); + }; + + Box::pin(entry) + } + + pub fn listening_block_on(self) { + let runtime = build_tokio_runtime("padserver_tcp".to_string()); + + info!("[TCP Server] Server start."); + runtime.block_on(self.build_entry()); + info!("[TCP Server] Finished."); + } +} + +impl PadServerNetwork { + + async fn main_thread(self: Arc<PadServerNetwork>) { + + info!("[TCP Server] [Main] Server listening at {}", self.addr.to_string()); + + let listener = TcpListener::bind(self.addr).await; + if listener.is_err() { + error!("[TCP Server] [Main] Failed to bind to {}", self.addr.to_string()); + return; + } + + let listener = listener.unwrap(); + info!("[TCP Server] [Main] Listener created, start listening."); + + let mut local_close_rx = self.close_rx.clone(); + + loop { + select! { + _ = local_close_rx.changed() => { + if *local_close_rx.borrow() { + break; + } + } + + accept = listener.accept() => { + match accept { + Ok((stream, _)) => { + spawn(Self::process_connection(Arc::clone(&self), stream)); + } + Err(error) => { + warn!("[TCP Server] [Main] Failed to accept TCP connections: {}", error); + } + } + } + } + } + + info!("[TCP Server] [Main] Main thread closed."); + } + + async fn process_connection(self: Arc<Self>, mut stream: TcpStream) { + let mut buffer = [0; 1024]; + let message: ConnectionMessage = read_msg(&mut buffer, &mut stream).await; + let from_address = get_target_address(&stream); + + match message { + + Join(player) => { + trace!("[TCP Server] [Main] Trying to join Player \"{}\"", &player.account.id); + let mut result = Ok(()); + entry_mutex!(self.runtime, |guard| { + match guard.try_join_player(player.clone()) { + Ok(_) => { result = Ok(()); } + Err(why) => { result = Err(why); } + } + }); + if result.is_err() { + let fail_message = result.unwrap_err(); + error!("[TCP Server] [Main] Player join failed: {:?}", &fail_message); + send_msg(&mut stream, Deny(fail_message)).await; + } else { + + // Long Connection + info!("[TCP Server] [Main] Player joined, begin long connection."); + send_msg(&mut stream, Welcome).await; + spawn(Self::start_long_connection(Arc::clone(&self), player, stream)); + } + } + + RequestGameInfos => { + info!("[TCP Server] [Main] Client({}) requests game infos.", from_address); + let mut info = Default::default(); + entry_mutex!(self.runtime, |guard| { + info = guard.info.clone(); + }); + send_msg(&mut stream, GameInfos(info)).await; + info!("[TCP Server] [Main] Game infos sent."); + } + + RequestLayoutConfigure => { + info!("[TCP Server] [Main] Client({}) requests layout configures.", from_address); + } + + RequestSkinPackage => { + info!("[TCP Server] [Main] Client({}) requests to download skin package.", from_address); + } + + Ready => { + info!("[TCP Server] [Main] Client({}) is ready!", from_address); + warn!("[TCP Server] [Main] But I don't know who he is.....") + } + + _ => { } + } + } + + async fn close_checker(self: Arc<Self>) { + loop { + sleep(Duration::from_millis(1000)).await; + entry_mutex!(self.runtime, |guard| { + if guard.data.close.load(SeqCst) { + let _ = self.close_tx.send(true); + break; + } + }) + } + } +}
\ No newline at end of file diff --git a/core/src/service/tcp_network/pad_server/mod.rs b/core/src/service/tcp_network/pad_server/mod.rs new file mode 100644 index 0000000..b37b320 --- /dev/null +++ b/core/src/service/tcp_network/pad_server/mod.rs @@ -0,0 +1,2 @@ +pub mod implements; +pub mod structs; diff --git a/core/src/service/tcp_network/pad_server/structs.rs b/core/src/service/tcp_network/pad_server/structs.rs new file mode 100644 index 0000000..f46608a --- /dev/null +++ b/core/src/service/tcp_network/pad_server/structs.rs @@ -0,0 +1,12 @@ +use crate::data::game::runtime::structs::GameRuntime; +use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; +use tokio::sync::watch::{Receiver, Sender}; + +pub struct PadServerNetwork { + pub(crate) addr: SocketAddr, + pub(crate) runtime: Arc<Mutex<GameRuntime>>, + + pub(crate) close_tx: Sender<bool>, + pub(crate) close_rx: Receiver<bool>, +}
\ No newline at end of file diff --git a/core/src/service/tcp_network/utils/mod.rs b/core/src/service/tcp_network/utils/mod.rs new file mode 100644 index 0000000..ad06dac --- /dev/null +++ b/core/src/service/tcp_network/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod stream_utils; +pub mod tokio_utils;
\ No newline at end of file diff --git a/core/src/service/tcp_network/utils/stream_utils.rs b/core/src/service/tcp_network/utils/stream_utils.rs new file mode 100644 index 0000000..9ccff3b --- /dev/null +++ b/core/src/service/tcp_network/utils/stream_utils.rs @@ -0,0 +1,44 @@ +use std::fmt::Debug; +use bincode::{Decode, Encode}; +use log::{error, trace}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use crate::data::message::traits::MessageEncoder; + +pub async fn send_msg<Message>( + stream: &mut TcpStream, + msg: impl MessageEncoder<Message> + Encode + Decode<()> + Default + Debug +) +where Message: MessageEncoder<Message> + Encode + Decode<()> + Default + Debug { + match stream.write_all(MessageEncoder::en(&msg).as_slice()).await { + Ok(_) => { trace!("[Message Sender] Sent {:?} to {}", msg, get_target_address(stream)); } + Err(err) => { error!("[Message Sender] Failed to send message: {}", err); } + } +} + +pub async fn read_msg<Message>( + buffer: &mut [u8], + stream: &mut TcpStream +) -> Message +where Message: MessageEncoder<Message> + Encode + Decode<()> + Default + Debug { + match stream.read(buffer).await { + Ok(read) => { + let received = Message::de(Vec::from(&buffer[..read])); + trace!("[Message Reader] Received {:?} from {}", received, get_target_address(stream)); + received + } + Err(err) => { + error!("[Message Reader] Error reading from stream: {}", err); + Message::err_result_decode() + } + } +} + +pub fn get_target_address(stream: &TcpStream) -> String { + let p = stream.peer_addr(); + if p.is_ok() { + p.unwrap().to_string() + } else { + "Unknown".to_string() + } +}
\ No newline at end of file diff --git a/core/src/service/tcp_network/utils/tokio_utils.rs b/core/src/service/tcp_network/utils/tokio_utils.rs new file mode 100644 index 0000000..efa68c3 --- /dev/null +++ b/core/src/service/tcp_network/utils/tokio_utils.rs @@ -0,0 +1,10 @@ +use tokio::runtime::{Builder, Runtime}; + +pub fn build_tokio_runtime(name: String) -> Runtime { + Builder::new_multi_thread() + .thread_name(name) + .thread_stack_size(32 * 1024 * 1024) + .enable_all() + .build() + .unwrap() +}
\ No newline at end of file |
