Skip to main content

nil_core/
savedata.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use crate::chat::Chat;
5use crate::continent::Continent;
6use crate::error::{AnyResult, Error, Result};
7use crate::military::Military;
8use crate::npc::bot::BotManager;
9use crate::npc::precursor::PrecursorManager;
10use crate::player::{Player, PlayerManager};
11use crate::ranking::Ranking;
12use crate::round::{Round, RoundId};
13use crate::world::config::{WorldConfig, WorldName};
14use crate::world::stats::WorldStats;
15use anyhow::bail;
16use flate2::Compression;
17use flate2::bufread::GzDecoder;
18use flate2::write::GzEncoder;
19use jiff::Zoned;
20use semver::Version;
21use serde::de::DeserializeOwned;
22use serde::{Deserialize, Serialize};
23use std::fmt;
24use std::io::{Read, Write};
25use tar::Archive;
26
27const MINIFY: bool = cfg!(any(
28  not(debug_assertions),
29  target_os = "android",
30  target_os = "ios"
31));
32
33type TarBuilder<'a> = tar::Builder<GzEncoder<&'a mut Vec<u8>>>;
34
35#[derive(Clone, Debug, Deserialize, Serialize)]
36#[serde(rename_all = "camelCase")]
37pub struct Savedata {
38  pub(crate) round: Round,
39  pub(crate) continent: Continent,
40  pub(crate) player_manager: PlayerManager,
41  pub(crate) bot_manager: BotManager,
42  pub(crate) precursor_manager: PrecursorManager,
43  pub(crate) military: Military,
44  pub(crate) ranking: Ranking,
45  pub(crate) chat: Chat,
46
47  pub(crate) config: WorldConfig,
48  pub(crate) stats: WorldStats,
49}
50
51impl Savedata {
52  pub fn read(bytes: &[u8]) -> Result<Self> {
53    read_tar(bytes, "world")
54      .inspect_err(|err| tracing::error!(message = %err, error = ?err))
55      .map_err(|_| Error::FailedToReadSavedata)
56  }
57
58  pub(crate) fn write(self, buffer: &mut Vec<u8>) -> Result<()> {
59    write_tar(buffer, &self)
60      .inspect_err(|err| tracing::error!(message = %err, error = ?err))
61      .map_err(|_| Error::FailedToWriteSavedata)
62  }
63
64  pub fn players(&self) -> impl Iterator<Item = &Player> {
65    self.player_manager.players()
66  }
67}
68
69#[derive(Clone, Debug, Deserialize, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub struct SavedataInfo {
72  world_name: WorldName,
73  round: RoundId,
74  version: Version,
75  saved_at: Zoned,
76}
77
78impl SavedataInfo {
79  fn new(data: &Savedata) -> AnyResult<Self> {
80    Ok(Self {
81      world_name: data.config.name(),
82      round: data.round.id(),
83      version: Version::parse(env!("CARGO_PKG_VERSION"))?,
84      saved_at: Zoned::now(),
85    })
86  }
87
88  pub fn read(bytes: &[u8]) -> Result<Self> {
89    read_tar(bytes, "info")
90      .inspect_err(|err| tracing::error!(message = %err, error = ?err))
91      .map_err(|_| Error::FailedToReadSavedata)
92  }
93}
94
95fn read_tar<T>(bytes: &[u8], entry_name: &str) -> AnyResult<T>
96where
97  T: DeserializeOwned,
98{
99  let decoder = GzDecoder::new(bytes);
100  let mut archive = Archive::new(decoder);
101
102  for entry in archive.entries()? {
103    let mut entry = entry?;
104    if let Ok(entry_path) = entry.path()
105      && let Some(path) = entry_path.to_str()
106      && path == entry_name
107    {
108      let size = entry.size().try_into()?;
109      let mut buffer = Vec::with_capacity(size);
110      entry.read_to_end(&mut buffer)?;
111      return Ok(serde_json::from_slice(&buffer)?);
112    }
113  }
114
115  bail!("Entry not found: {entry_name}");
116}
117
118fn write_tar(buffer: &mut Vec<u8>, data: &Savedata) -> AnyResult<()> {
119  let encoder = GzEncoder::new(buffer, Compression::best());
120  let mut tar_builder = TarBuilder::new(encoder);
121
122  let info = SavedataInfo::new(data)?;
123  append(&mut tar_builder, &info, "info")?;
124  append(&mut tar_builder, data, "world")?;
125
126  tar_builder.into_inner()?.finish()?.flush()?;
127
128  Ok(())
129}
130
131fn append<T>(builder: &mut TarBuilder, value: &T, path: &str) -> AnyResult<()>
132where
133  T: ?Sized + Serialize,
134{
135  let bytes = if MINIFY {
136    serde_json::to_vec(value)?
137  } else {
138    serde_json::to_vec_pretty(value)?
139  };
140
141  let mut header = tar::Header::new_gnu();
142  header.set_size(bytes.len().try_into()?);
143
144  builder.append_data(&mut header, path, bytes.as_slice())?;
145
146  Ok(())
147}
148
149pub struct SaveHandle(Box<dyn FnOnce(Vec<u8>) + Send + Sync>);
150
151impl SaveHandle {
152  pub fn new<F>(f: F) -> Self
153  where
154    F: FnOnce(Vec<u8>) + Send + Sync + 'static,
155  {
156    Self(Box::new(f))
157  }
158
159  #[inline]
160  pub fn save(self, data: Vec<u8>) {
161    (self.0)(data);
162  }
163}
164
165impl fmt::Debug for SaveHandle {
166  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167    f.debug_tuple("SaveHandle")
168      .finish_non_exhaustive()
169  }
170}