aboutsummaryrefslogtreecommitdiff
path: root/core/src/data
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/data
parentc9dbba0d288becb7f05cebe526be25c76e5a850a (diff)
重构所有部分
Diffstat (limited to 'core/src/data')
-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
25 files changed, 1095 insertions, 0 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