nil_server/server/
local.rs1use 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")]
18#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
19#[cfg_attr(feature = "typescript", ts(export))]
20pub struct LocalServer {
21 world: WorldId,
22 addr: SocketAddrV4,
23
24 #[serde(skip_serializing)]
25 #[cfg_attr(feature = "typescript", ts(skip))]
26 handle: AbortHandle,
27}
28
29impl LocalServer {
30 async fn serve(world: World) -> Result<Self> {
31 let (listener, mut addr) = super::bind(0).await?;
32 if addr.ip().is_unspecified() {
33 addr.set_ip(Ipv4Addr::LOCALHOST);
34 }
35
36 let world_id = world.id();
37 let router = router::create()
38 .with_state(App::new_local(world))
39 .into_make_service_with_connect_info::<SocketAddr>();
40
41 let task = spawn(async move {
42 axum::serve(listener, router)
43 .await
44 .expect("Failed to start Call of Nil server");
45 });
46
47 Ok(Self {
48 world: world_id,
49 addr,
50 handle: task.abort_handle(),
51 })
52 }
53
54 #[inline]
55 pub fn world(&self) -> WorldId {
56 self.world
57 }
58
59 #[inline]
60 pub fn addr(&self) -> SocketAddrV4 {
61 self.addr
62 }
63
64 #[inline]
65 pub fn stop(self) {
66 self.handle.abort();
67 }
68}
69
70pub async fn start(options: WorldOptions) -> Result<LocalServer> {
71 LocalServer::serve(options.try_into()?).await
72}
73
74pub async fn load(path: impl AsRef<Path>) -> Result<LocalServer> {
75 let bytes = fs::read(path).await?;
76 let world = spawn_blocking(move || World::load(&bytes))
77 .await
78 .map_err(|_| CoreError::FailedToReadSavedata)??;
79
80 LocalServer::serve(world).await
81}
82
83pub(crate) async fn save(mut dir: PathBuf, bytes: Vec<u8>) {
84 let result = try {
85 fs::create_dir_all(&dir).await?;
86 dir.push(format!("{}.nil", Uuid::now_v7()));
87 fs::write(&dir, bytes).await?;
88 };
89
90 if let Err(err) = result {
91 tracing::error!(message = %err, error = ?err);
92 }
93}