From 98ee910ce10c10405833c6974e271e1e3309226b Mon Sep 17 00:00:00 2001 From: Rusty Striker Date: Mon, 14 Jul 2025 21:48:48 +0300 Subject: [PATCH] basic save for game server --- .gitignore | 1 + src/lib.rs | 33 +++++++++++++++++++++++++++++++-- src/main.rs | 7 ++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..f6c313c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/data \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3a8dc3c..7e9092d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,17 +7,24 @@ use game::{ entry::{ActionDefinition, DiceRoll}, }; use serde::{Deserialize, Serialize}; -use tokio::sync::{broadcast, mpsc}; +use tokio::{ + sync::{broadcast, mpsc}, + time::Instant, +}; pub mod api; pub mod game; pub mod pathfinder2r_impl; pub mod user; +pub const GAME_SAVE_FILE: &str = "data/game.json"; + #[derive(Serialize, Deserialize)] pub struct GameServer { game: Game, chat: Vec<(String, ChatMessage)>, + #[serde(skip, default = "default_last_save_time")] + last_save_time: Instant, // we dont want to save the logged users as it will always be empty when the server restarts #[serde(skip)] /// Logged in users, bool value used to know if the user is an admin or not @@ -25,6 +32,10 @@ pub struct GameServer { // TODO: JEESH REPLACE THIS WITH A DATABASE AND PROPER SECURITY ONCE ITS DONE PLEASE GOD creds: HashMap, } +/// This is needed for the default value in GameServer.last_save_time +fn default_last_save_time() -> Instant { + Instant::now() +} impl GameServer { pub fn new() -> Self { let mut creds = HashMap::new(); @@ -51,11 +62,19 @@ impl GameServer { ))) .with_action(ActionDefinition::new("Attack -1".to_string())), )], + last_save_time: Instant::now(), users: HashMap::new(), creds, } } + pub async fn save(&self) { + let _ = tokio::fs::create_dir("data").await; + if let Ok(json) = serde_json::to_string(self) { + let _ = tokio::fs::write(GAME_SAVE_FILE, json.as_bytes()).await; + } + } + pub async fn server_loop( mut self, mut msgs: mpsc::Receiver<(String, api::Request)>, @@ -206,7 +225,11 @@ impl GameServer { } _ = broadcast.send((Some(id), api::Response::Shutdown)); } - api::Request::Shutdown => break, + api::Request::Shutdown => { + if *self.users.get(&id).unwrap_or(&false) { + break; + } + } api::Request::CharacterDisplay { id: _ } => {} api::Request::CharacterInputs { id: _ } => todo!(), api::Request::CharacterGetField { id: _, field: _ } => todo!(), @@ -217,8 +240,14 @@ impl GameServer { } => todo!(), api::Request::CharacterAssign { id: _, user: _ } => todo!(), } + // Auto save every 5 minutes, once a request is received + // TODO: Export the auto save time + if self.last_save_time.elapsed().as_secs() > 5 * 60 { + self.save().await; + } } _ = broadcast.send((None, api::Response::Shutdown)); + self.save().await; // Save the game one last time :) } fn get_scene_list(&self, id: &str) -> api::Response { diff --git a/src/main.rs b/src/main.rs index 8c5e2c9..1fff156 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,12 @@ async fn main() { ); let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap(); - let game = open_tavern::GameServer::new(); + let game = tokio::fs::read_to_string(open_tavern::GAME_SAVE_FILE) + .await + .map(|s| serde_json::from_str(&s).ok()) + .ok() + .flatten() + .unwrap_or(open_tavern::GameServer::new()); tokio::spawn(game.server_loop(mrecv, bsend)); println!("Server is running at http://localhost:3001");