veloren_voxygen/singleplayer/
mod.rs1use 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
23pub 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 paused: Arc<AtomicBool>,
32}
33
34impl Singleplayer {
35 pub fn is_paused(&self) -> bool { self.paused.load(Ordering::SeqCst) }
37
38 pub fn pause(&self, state: bool) { self.paused.store(state, Ordering::SeqCst); }
41}
42
43impl Drop for Singleplayer {
44 fn drop(&mut self) {
45 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 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, };
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 let mut clock = Clock::new(Duration::from_secs_f64(1.0 / TPS as f64));
183
184 loop {
185 match stop_server_r.try_recv() {
187 Ok(()) => break,
188 Err(TryRecvError::Disconnected) => break,
189 Err(TryRecvError::Empty) => (),
190 }
191
192 clock.tick();
194
195 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 server.cleanup();
216 }
217}