aboutsummaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
author1992414357@qq.com <1992414357@qq.com>2025-06-09 01:44:36 +0800
committer1992414357@qq.com <1992414357@qq.com>2025-06-09 01:44:36 +0800
commit49192dbb98e0ab1f2a66b4786fac86d63f69be64 (patch)
tree7cdf27c7cab5fb6b1aabc2ac7c44b7b492558945 /core/src
parentc9dbba0d288becb7f05cebe526be25c76e5a850a (diff)
重构所有部分
Diffstat (limited to 'core/src')
-rw-r--r--core/src/data/controller/cli/cli_command.rs48
-rw-r--r--core/src/data/controller/cli/mod.rs1
-rw-r--r--core/src/data/controller/implements.rs21
-rw-r--r--core/src/data/controller/mod.rs4
-rw-r--r--core/src/data/controller/runtime/implements.rs29
-rw-r--r--core/src/data/controller/runtime/mod.rs2
-rw-r--r--core/src/data/controller/runtime/structs.rs20
-rw-r--r--core/src/data/controller/structs.rs10
-rw-r--r--core/src/data/game/cli/cli_command.rs35
-rw-r--r--core/src/data/game/cli/mod.rs1
-rw-r--r--core/src/data/game/implements.rs99
-rw-r--r--core/src/data/game/mod.rs6
-rw-r--r--core/src/data/game/runtime/implements.rs324
-rw-r--r--core/src/data/game/runtime/mod.rs2
-rw-r--r--core/src/data/game/runtime/structs.rs40
-rw-r--r--core/src/data/game/structs.rs29
-rw-r--r--core/src/data/game/types.rs7
-rw-r--r--core/src/data/message/enums.rs154
-rw-r--r--core/src/data/message/implements.rs15
-rw-r--r--core/src/data/message/mod.rs3
-rw-r--r--core/src/data/message/traits.rs67
-rw-r--r--core/src/data/mod.rs10
-rw-r--r--core/src/data/player/implements.rs119
-rw-r--r--core/src/data/player/mod.rs4
-rw-r--r--core/src/data/player/structs.rs45
-rw-r--r--core/src/lib.rs7
-rw-r--r--core/src/pad_data/game_profile.rs100
-rw-r--r--core/src/pad_data/layout.rs137
-rw-r--r--core/src/pad_data/mod.rs4
-rw-r--r--core/src/pad_data/pad_messages.rs176
-rw-r--r--core/src/pad_data/pad_player_info.rs127
-rw-r--r--core/src/pad_service/client.rs520
-rw-r--r--core/src/pad_service/client_debug_cli.rs92
-rw-r--r--core/src/pad_service/mod.rs5
-rw-r--r--core/src/pad_service/server.rs819
-rw-r--r--core/src/pad_service/server_debug_cli.rs212
-rw-r--r--core/src/service/cli_addition/mod.rs2
-rw-r--r--core/src/service/cli_addition/runtime_consoles.rs60
-rw-r--r--core/src/service/cli_addition/utils.rs58
-rw-r--r--core/src/service/mod.rs4
-rw-r--r--core/src/service/service_runner.rs31
-rw-r--r--core/src/service/service_types.rs13
-rw-r--r--core/src/service/tcp_network/long_connection.rs272
-rw-r--r--core/src/service/tcp_network/mod.rs6
-rw-r--r--core/src/service/tcp_network/pad_client/implements.rs156
-rw-r--r--core/src/service/tcp_network/pad_client/mod.rs2
-rw-r--r--core/src/service/tcp_network/pad_client/structs.rs8
-rw-r--r--core/src/service/tcp_network/pad_server/implements.rs186
-rw-r--r--core/src/service/tcp_network/pad_server/mod.rs2
-rw-r--r--core/src/service/tcp_network/pad_server/structs.rs12
-rw-r--r--core/src/service/tcp_network/utils/mod.rs2
-rw-r--r--core/src/service/tcp_network/utils/stream_utils.rs44
-rw-r--r--core/src/service/tcp_network/utils/tokio_utils.rs10
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