Skip to main content

nil_server/server/
local.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::app::App;
5use crate::error::{CoreError, Result};
6use crate::router;
7use nil_core::world::config::WorldId;
8use nil_core::world::{World, WorldOptions};
9use serde::Serialize;
10use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
11use std::path::{Path, PathBuf};
12use tokio::fs;
13use tokio::task::{AbortHandle, spawn, spawn_blocking};
14use uuid::Uuid;
15
16#[derive(Clone, Debug, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct LocalServer {
19  world: WorldId,
20  addr: SocketAddrV4,
21
22  #[serde(skip_serializing)]
23  handle: AbortHandle,
24}
25
26impl LocalServer {
27  async fn serve(world: World) -> Result<Self> {
28    let (listener, mut addr) = super::bind(0).await?;
29    if addr.ip().is_unspecified() {
30      addr.set_ip(Ipv4Addr::LOCALHOST);
31    }
32
33    let world_id = world.id();
34    let router = router::create()
35      .with_state(App::new_local(world))
36      .into_make_service_with_connect_info::<SocketAddr>();
37
38    let task = spawn(async move {
39      axum::serve(listener, router)
40        .await
41        .expect("Failed to start Call of Nil server");
42    });
43
44    Ok(Self {
45      world: world_id,
46      addr,
47      handle: task.abort_handle(),
48    })
49  }
50
51  #[inline]
52  pub fn world(&self) -> WorldId {
53    self.world
54  }
55
56  #[inline]
57  pub fn addr(&self) -> SocketAddrV4 {
58    self.addr
59  }
60
61  #[inline]
62  pub fn stop(self) {
63    self.handle.abort();
64  }
65}
66
67pub async fn start(options: &WorldOptions) -> Result<LocalServer> {
68  LocalServer::serve(options.try_into()?).await
69}
70
71pub async fn load(path: impl AsRef<Path>) -> Result<LocalServer> {
72  let bytes = fs::read(path).await?;
73  let world = spawn_blocking(move || World::load(&bytes))
74    .await
75    .map_err(|_| CoreError::FailedToReadSavedata)??;
76
77  LocalServer::serve(world).await
78}
79
80pub(crate) async fn save(mut dir: PathBuf, bytes: Vec<u8>) {
81  let result = try {
82    fs::create_dir_all(&dir).await?;
83    dir.push(format!("{}.nil", Uuid::now_v7()));
84    fs::write(&dir, bytes).await?;
85  };
86
87  if let Err(err) = result {
88    tracing::error!(message = %err, error = ?err);
89  }
90}