1use crate::error::{Error, Result};
5use crate::response::{MaybeResponse, from_err};
6use crate::server::{remote, spawn_round_duration_task};
7use crate::{VERSION, res};
8use dashmap::DashMap;
9use either::Either;
10use jiff::Zoned;
11use nil_core::chat::Chat;
12use nil_core::continent::Continent;
13use nil_core::military::Military;
14use nil_core::npc::bot::BotManager;
15use nil_core::npc::precursor::PrecursorManager;
16use nil_core::player::PlayerManager;
17use nil_core::ranking::Ranking;
18use nil_core::report::ReportManager;
19use nil_core::round::Round;
20use nil_core::world::config::WorldId;
21use nil_core::world::{World, WorldOptions};
22use nil_crypto::password::Password;
23use nil_server_database::Database;
24use nil_server_database::model::game::{GameWithBlob, NewGame};
25use nil_server_database::sql_types::player_id::db_PlayerId;
26use nil_server_types::ServerKind;
27use nil_server_types::round::RoundDuration;
28use semver::{Prerelease, Version};
29use std::num::NonZeroU16;
30use std::sync::{Arc, Weak};
31use std::time::Duration;
32use tap::TryConv;
33use tokio::sync::RwLock;
34use tokio::task::{spawn, spawn_blocking};
35
36#[derive(Clone)]
37pub struct App {
38 server_kind: ServerKind,
39 database: Option<Database>,
40 worlds: Arc<DashMap<WorldId, Arc<RwLock<World>>>>,
41 world_limit: NonZeroU16,
42 world_limit_per_user: NonZeroU16,
43}
44
45#[bon::bon]
46impl App {
47 pub fn new_local(world: World) -> Self {
48 let id = world.config().id();
49 let app = Self {
50 server_kind: ServerKind::Local { id },
51 database: None,
52 worlds: Arc::new(DashMap::new()),
53 world_limit: NonZeroU16::MIN,
54 world_limit_per_user: NonZeroU16::MIN,
55 };
56
57 app
58 .worlds
59 .insert(id, Arc::new(RwLock::new(world)));
60
61 app
62 }
63
64 pub async fn new_remote(database_url: &str) -> Result<Self> {
65 let worlds = Arc::new(DashMap::new());
66 let database = Database::new(database_url).await?;
67
68 let mut invalid_games = Vec::new();
69
70 for game_id in database.get_game_ids().await? {
71 if let Ok(game) = database.get_game_with_blob(game_id).await
72 && has_valid_version(&game)
73 && has_valid_age(&game)
74 && let Ok(world) = game.to_world()
75 {
76 let world_id = world.config().id();
77 let round_id = world.round().id();
78 let is_round_idle = world.round().is_idle();
79
80 let database = database.clone();
81 let world = Arc::new(RwLock::new(world));
82 let weak_world = Arc::downgrade(&world);
83
84 if let Some(round_duration) = game.round_duration
85 && !is_round_idle
86 {
87 spawn(spawn_round_duration_task(
88 round_id,
89 Weak::clone(&weak_world),
90 round_duration.into(),
91 ));
92 }
93
94 world.write().await.on_next_round(
95 remote::on_next_round()
96 .database(database)
97 .weak_world(weak_world)
98 .maybe_round_duration(game.round_duration)
99 .call(),
100 );
101
102 worlds.insert(world_id, world);
103 } else {
104 tracing::warn!(invalid_game = %game_id);
105 invalid_games.push(game_id);
106 }
107 }
108
109 database.delete_games(invalid_games).await?;
110
111 Ok(Self {
112 server_kind: ServerKind::Remote,
113 database: Some(database),
114 worlds,
115 world_limit: nil_env::remote_world_limit(),
116 world_limit_per_user: nil_env::remote_world_limit_per_user(),
117 })
118 }
119
120 #[inline]
121 pub fn server_kind(&self) -> ServerKind {
122 self.server_kind
123 }
124
125 pub fn database(&self) -> Database {
129 if let ServerKind::Remote = self.server_kind
130 && let Some(database) = &self.database
131 {
132 database.clone()
133 } else {
134 panic!("Not a remote server")
135 }
136 }
137
138 pub fn world_ids(&self) -> Vec<WorldId> {
139 self
140 .worlds
141 .iter()
142 .map(|entry| *entry.key())
143 .collect()
144 }
145
146 #[inline]
147 pub fn world_limit(&self) -> u16 {
148 self.world_limit.get()
149 }
150
151 #[inline]
152 pub fn world_limit_per_user(&self) -> u16 {
153 self.world_limit_per_user.get()
154 }
155
156 #[builder]
162 pub(crate) async fn create_remote(
163 &self,
164 #[builder(start_fn)] options: &WorldOptions,
165 #[builder(into)] player_id: db_PlayerId,
166 #[builder(into)] world_description: Option<String>,
167 #[builder(into)] world_password: Option<Password>,
168 #[builder(into)] round_duration: Option<RoundDuration>,
169 server_version: Version,
170 ) -> Result<WorldId> {
171 self
172 .check_remote_world_limit(player_id.clone())
173 .await?;
174
175 let database = self.database();
176 let user = database.get_user(player_id).await?;
177
178 let world = World::try_from(options)?;
179 let world_id = world.config().id();
180 let blob = world.to_bytes()?;
181
182 NewGame::builder(world_id, blob)
183 .created_by(user.id)
184 .maybe_description(world_description)
185 .maybe_password(world_password)
186 .maybe_round_duration(round_duration)
187 .server_version(server_version)
188 .build()
189 .await?
190 .create(&database)
191 .await?;
192
193 let database = database.clone();
194 let world = Arc::new(RwLock::new(world));
195
196 world.write().await.on_next_round(
197 remote::on_next_round()
198 .database(database)
199 .weak_world(Arc::downgrade(&world))
200 .maybe_round_duration(round_duration)
201 .call(),
202 );
203
204 self.worlds.insert(world_id, world);
205
206 Ok(world_id)
207 }
208
209 async fn check_remote_world_limit(&self, player: db_PlayerId) -> Result<()> {
211 let database = self.database();
212
213 let limit = i64::from(self.world_limit.get());
214 if database.count_games().await? >= limit {
215 return Err(Error::WorldLimitReached);
216 }
217
218 let limit_per_user = i64::from(self.world_limit_per_user.get());
219 if database.count_games_by_user(player).await? >= limit_per_user {
220 return Err(Error::WorldLimitReached);
221 }
222
223 Ok(())
224 }
225
226 pub(crate) fn get(&self, id: WorldId) -> Result<Arc<RwLock<World>>> {
227 self
228 .worlds
229 .get(&id)
230 .map(|world| Arc::clone(&world))
231 .ok_or_else(|| Error::WorldNotFound(id))
232 }
233
234 pub(crate) fn remove(&self, id: WorldId) -> Option<Arc<RwLock<World>>> {
235 self.worlds.remove(&id).map(|it| it.1)
236 }
237
238 pub async fn world<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
239 where
240 F: FnOnce(&World) -> T,
241 {
242 match self.get(id) {
243 Ok(world) => Either::Left(f(&*world.read().await)),
244 Err(err) => Either::Right(from_err(err)),
245 }
246 }
247
248 pub async fn world_mut<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
249 where
250 F: FnOnce(&mut World) -> T,
251 {
252 match self.get(id) {
253 Ok(world) => Either::Left(f(&mut *world.write().await)),
254 Err(err) => Either::Right(from_err(err)),
255 }
256 }
257
258 pub async fn world_blocking<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
259 where
260 F: FnOnce(&World) -> T + Send + Sync + 'static,
261 T: Send + Sync + 'static,
262 {
263 match self.get(id) {
264 Ok(world) => {
265 match spawn_blocking(move || f(&world.blocking_read())).await {
266 Ok(value) => Either::Left(value),
267 Err(err) => {
268 tracing::error!(message = %err, error = ?err);
269 Either::Right(res!(INTERNAL_SERVER_ERROR))
270 }
271 }
272 }
273 Err(err) => Either::Right(from_err(err)),
274 }
275 }
276
277 pub async fn world_blocking_mut<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
278 where
279 F: FnOnce(&mut World) -> T + Send + Sync + 'static,
280 T: Send + Sync + 'static,
281 {
282 match self.get(id) {
283 Ok(world) => {
284 match spawn_blocking(move || f(&mut world.blocking_write())).await {
285 Ok(value) => Either::Left(value),
286 Err(err) => {
287 tracing::error!(message = %err, error = ?err);
288 Either::Right(res!(INTERNAL_SERVER_ERROR))
289 }
290 }
291 }
292 Err(err) => Either::Right(from_err(err)),
293 }
294 }
295
296 pub async fn bot_manager<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
297 where
298 F: FnOnce(&BotManager) -> T,
299 {
300 self
301 .world(id, |world| f(world.bot_manager()))
302 .await
303 }
304
305 pub async fn chat<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
306 where
307 F: FnOnce(&Chat) -> T,
308 {
309 self.world(id, |world| f(world.chat())).await
310 }
311
312 pub async fn continent<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
313 where
314 F: FnOnce(&Continent) -> T,
315 {
316 self
317 .world(id, |world| f(world.continent()))
318 .await
319 }
320
321 pub async fn military<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
322 where
323 F: FnOnce(&Military) -> T,
324 {
325 self
326 .world(id, |world| f(world.military()))
327 .await
328 }
329
330 pub async fn player_manager<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
331 where
332 F: FnOnce(&PlayerManager) -> T,
333 {
334 self
335 .world(id, |world| f(world.player_manager()))
336 .await
337 }
338
339 pub async fn precursor_manager<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
340 where
341 F: FnOnce(&PrecursorManager) -> T,
342 {
343 self
344 .world(id, |world| f(world.precursor_manager()))
345 .await
346 }
347
348 pub async fn ranking<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
349 where
350 F: FnOnce(&Ranking) -> T,
351 {
352 self
353 .world(id, |world| f(world.ranking()))
354 .await
355 }
356
357 pub async fn report_manager<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
358 where
359 F: FnOnce(&ReportManager) -> T,
360 {
361 self
362 .world(id, |world| f(world.report_manager()))
363 .await
364 }
365
366 pub async fn round<F, T>(&self, id: WorldId, f: F) -> MaybeResponse<T>
367 where
368 F: FnOnce(&Round) -> T,
369 {
370 self
371 .world(id, |world| f(world.round()))
372 .await
373 }
374}
375
376fn has_valid_version(game: &GameWithBlob) -> bool {
377 let Ok(version) = Version::parse(VERSION) else {
378 unreachable!("Current version should always be valid")
379 };
380
381 let minor = if version.major == 0 { version.minor } else { 0 };
382 let version_cmp = semver::Comparator {
383 op: semver::Op::Caret,
384 major: version.major,
385 minor: Some(minor),
386 patch: Some(0),
387 pre: Prerelease::EMPTY,
388 };
389
390 version_cmp.matches(&game.server_version)
391}
392
393fn has_valid_age(game: &GameWithBlob) -> bool {
394 let Ok(duration) = game
395 .updated_at
396 .duration_until(&Zoned::now())
397 .try_conv::<Duration>()
398 else {
399 return false;
400 };
401
402 duration <= Duration::from_days(30)
403}