veloren_voxygen/singleplayer/
mod.rs

1use common::clock::Clock;
2use crossbeam_channel::{Receiver, Sender, TryRecvError, bounded, unbounded};
3use server::{
4    Error as ServerError, Event, Input, Server, ServerInitStage,
5    persistence::{DatabaseSettings, SqlLogMode},
6};
7use std::{
8    sync::{
9        Arc,
10        atomic::{AtomicBool, Ordering},
11    },
12    thread::{self, JoinHandle},
13    time::Duration,
14};
15use tokio::runtime::Runtime;
16use tracing::{error, info, trace, warn};
17
18mod singleplayer_world;
19pub use singleplayer_world::{SingleplayerWorld, SingleplayerWorlds};
20
21const TPS: u64 = 30;
22
23/// Used to start and stop the background thread running the server
24/// when in singleplayer mode.
25pub struct Singleplayer {
26    _server_thread: JoinHandle<()>,
27    stop_server_s: Sender<()>,
28    pub receiver: Receiver<Result<(), ServerError>>,
29    pub init_stage_receiver: Receiver<ServerInitStage>,
30    // Wether the server is stopped or not
31    paused: Arc<AtomicBool>,
32}
33
34impl Singleplayer {
35    /// Returns wether or not the server is paused
36    pub fn is_paused(&self) -> bool { self.paused.load(Ordering::SeqCst) }
37
38    /// Pauses if true is passed and unpauses if false (Does nothing if in that
39    /// state already)
40    pub fn pause(&self, state: bool) { self.paused.store(state, Ordering::SeqCst); }
41}
42
43impl Drop for Singleplayer {
44    fn drop(&mut self) {
45        // Ignore the result
46        let _ = self.stop_server_s.send(());
47    }
48}
49
50#[derive(Default)]
51pub enum SingleplayerState {
52    #[default]
53    None,
54    Init(SingleplayerWorlds),
55    Running(Singleplayer),
56}
57
58impl SingleplayerState {
59    pub fn init() -> Self {
60        let dir = common_base::userdata_dir_workspace!();
61
62        Self::Init(SingleplayerWorlds::load(&dir))
63    }
64
65    pub fn run(&mut self, runtime: &Arc<Runtime>) {
66        if let Self::Init(worlds) = self {
67            let Some(world) = worlds.current() else {
68                error!("Failed to get the current world.");
69                return;
70            };
71            let server_data_dir = world.path.clone();
72
73            let mut settings = server::Settings::singleplayer(&server_data_dir);
74            let editable_settings = server::EditableSettings::singleplayer(&server_data_dir);
75
76            let file_opts = if let Some(gen_opts) = &world.gen_opts
77                && !world.is_generated
78            {
79                server::FileOpts::Save(world.map_path.clone(), gen_opts.clone())
80            } else {
81                if !world.is_generated && world.gen_opts.is_none() {
82                    world.copy_default_world();
83                }
84                server::FileOpts::Load(world.map_path.clone())
85            };
86
87            settings.map_file = Some(file_opts);
88            settings.world_seed = world.seed;
89            settings.day_length = world.day_length;
90
91            let (stop_server_s, stop_server_r) = unbounded();
92
93            let (server_stage_tx, server_stage_rx) = unbounded();
94
95            // Create server
96
97            // Relative to data_dir
98            const PERSISTENCE_DB_DIR: &str = "saves";
99
100            let database_settings = DatabaseSettings {
101                db_dir: server_data_dir.join(PERSISTENCE_DB_DIR),
102                sql_log_mode: SqlLogMode::Disabled, /* Voxygen doesn't take in command-line
103                                                     * arguments
104                                                     * so SQL logging can't be enabled for
105                                                     * singleplayer without changing this line
106                                                     * manually */
107            };
108
109            let paused = Arc::new(AtomicBool::new(false));
110            let paused1 = Arc::clone(&paused);
111
112            let (result_sender, result_receiver) = bounded(1);
113
114            let builder = thread::Builder::new().name("singleplayer-server-thread".into());
115            let runtime = Arc::clone(runtime);
116            let thread = builder
117                .spawn(move || {
118                    trace!("starting singleplayer server thread");
119
120                    let (server, init_result) = match Server::new(
121                        settings,
122                        editable_settings,
123                        database_settings,
124                        &server_data_dir,
125                        &|init_stage| {
126                            let _ = server_stage_tx.send(init_stage);
127                        },
128                        runtime,
129                    ) {
130                        Ok(server) => (Some(server), Ok(())),
131                        Err(err) => (None, Err(err)),
132                    };
133
134                    match (result_sender.send(init_result), server) {
135                        (Err(e), _) => warn!(
136                            ?e,
137                            "Failed to send singleplayer server initialization result. Most \
138                             likely the channel was closed by cancelling server creation. \
139                             Stopping Server"
140                        ),
141                        (Ok(()), None) => (),
142                        (Ok(()), Some(server)) => run_server(server, stop_server_r, paused1),
143                    }
144
145                    trace!("ending singleplayer server thread");
146                })
147                .unwrap();
148
149            *self = SingleplayerState::Running(Singleplayer {
150                _server_thread: thread,
151                stop_server_s,
152                init_stage_receiver: server_stage_rx,
153                receiver: result_receiver,
154                paused,
155            });
156        } else {
157            error!("SingleplayerState::run was called, but singleplayer is already running!");
158        }
159    }
160
161    pub fn as_running(&self) -> Option<&Singleplayer> {
162        match self {
163            SingleplayerState::Running(s) => Some(s),
164            _ => None,
165        }
166    }
167
168    pub fn as_init(&self) -> Option<&SingleplayerWorlds> {
169        match self {
170            SingleplayerState::Init(s) => Some(s),
171            _ => None,
172        }
173    }
174
175    pub fn is_running(&self) -> bool { matches!(self, SingleplayerState::Running(_)) }
176}
177
178fn run_server(mut server: Server, stop_server_r: Receiver<()>, paused: Arc<AtomicBool>) {
179    info!("Starting server-cli...");
180
181    // Set up an fps clock
182    let mut clock = Clock::new(Duration::from_secs_f64(1.0 / TPS as f64));
183
184    loop {
185        // Check any event such as stopping and pausing
186        match stop_server_r.try_recv() {
187            Ok(()) => break,
188            Err(TryRecvError::Disconnected) => break,
189            Err(TryRecvError::Empty) => (),
190        }
191
192        // Wait for the next tick.
193        clock.tick();
194
195        // Skip updating the server if it's paused
196        if paused.load(Ordering::SeqCst) && server.number_of_players() < 2 {
197            continue;
198        } else if server.number_of_players() > 1 {
199            paused.store(false, Ordering::SeqCst);
200        }
201
202        let events = server
203            .tick(Input::default(), clock.dt())
204            .expect("Failed to tick server!");
205
206        for event in events {
207            match event {
208                Event::ClientConnected { .. } => info!("Client connected!"),
209                Event::ClientDisconnected { .. } => info!("Client disconnected!"),
210                Event::Chat { entity: _, msg } => info!("[Client] {}", msg),
211            }
212        }
213
214        // Clean up the server after a tick.
215        server.cleanup();
216    }
217}