Skip to main content
Verticopolis engineGitHub

verticopolis_engine/
sim_save.rs

1//! The saved game, as a JSON tree (`Simulation::serialize`) and as JSON text
2//! (`Simulation::save_text`, behind `Engine.serialize`). Both read one list
3//! of fields, so they write the same keys in the same order (the tree keeps
4//! that order because the crate builds `serde_json` with `preserve_order`);
5//! each value's bytes rest on `serde_json` writing a borrowed value as it
6//! writes that value's `Value`, which the tests pin. The ledger's and the
7//! events' objects are the two shapes written twice. The text is written as
8//! it goes: each unit, transport, log line and ledger day becomes its own
9//! `Value` that is written and dropped before the next, and the tower's name,
10//! the pending choice's message and each excavated tile, room id, blockbuster
11//! cinema and milestone are written from their own values, so a save never
12//! holds the whole game as a tree beside the game (#1242).
13
14use serde::ser::{Serialize, SerializeMap, Serializer};
15use serde_json::{json, Map, Value};
16
17use crate::events::{EventSystem, PendingChoice};
18use crate::ledger::{totals_json, Ledger};
19use crate::sim::{LogEntry, Simulation, LOG_SAVE_CAP, SAVE_VERSION};
20use crate::tower::{Transport, Unit};
21
22/// One top-level value of the save. The lists stay borrowed until they are
23/// written, one entry at a time.
24enum Field<'a> {
25    Owned(Value),
26    Borrowed(&'a Value),
27    Text(&'a str),
28    Units(&'a [Unit]),
29    Transports(&'a [Transport]),
30    Strings(&'a [String]),
31    Ids(&'a [i64]),
32    Floats(&'a [f64]),
33    Ledger(&'a Ledger),
34    Events(&'a EventSystem),
35    Log(&'a [LogEntry]),
36}
37
38fn log_entry(e: &LogEntry) -> Value {
39    json!({ "minute": e.minute, "text": e.text, "kind": e.kind.as_str() })
40}
41
42/// A list written as `Value::Array` writes its elements, each element's
43/// `Value` built in turn: the same sequence calls with the same length.
44struct Each<'a, T, F>(&'a [T], F);
45
46impl<T, F: Fn(&T) -> Value> Serialize for Each<'_, T, F> {
47    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
48        s.collect_seq(self.0.iter().map(&self.1))
49    }
50}
51
52impl Field<'_> {
53    fn into_value(self) -> Value {
54        match self {
55            Field::Owned(v) => v,
56            Field::Borrowed(v) => v.clone(),
57            Field::Text(t) => json!(t),
58            Field::Units(us) => Value::Array(us.iter().map(Unit::serialize).collect()),
59            Field::Transports(ts) => Value::Array(ts.iter().map(Transport::serialize).collect()),
60            Field::Strings(ss) => json!(ss),
61            Field::Ids(ids) => json!(ids),
62            Field::Floats(fs) => json!(fs),
63            Field::Ledger(l) => l.serialize(),
64            Field::Events(e) => e.save_state(),
65            Field::Log(es) => Value::Array(es.iter().map(log_entry).collect()),
66        }
67    }
68}
69
70impl Serialize for Field<'_> {
71    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
72        match self {
73            Field::Owned(v) => v.serialize(s),
74            Field::Borrowed(v) => v.serialize(s),
75            Field::Units(us) => Each(us, Unit::serialize).serialize(s),
76            Field::Transports(ts) => Each(ts, Transport::serialize).serialize(s),
77            // A `Value::String` writes as its string does, a
78            // `Value::Number` holding an integer as its digits do, and one
79            // holding a float, or the null a non-finite float becomes, as
80            // `serialize_f64` writes that float.
81            Field::Text(t) => t.serialize(s),
82            Field::Strings(ss) => ss.serialize(s),
83            Field::Ids(ids) => ids.serialize(s),
84            Field::Floats(fs) => fs.serialize(s),
85            // `Ledger::serialize`'s object, `today` then `history`.
86            Field::Ledger(l) => {
87                let mut m = s.serialize_map(Some(2))?;
88                m.serialize_entry("today", &totals_json(&l.today))?;
89                m.serialize_entry("history", &Each(&l.history, totals_json))?;
90                m.end()
91            }
92            // `EventSystem::save_state`'s object; `pending` is a literal null
93            // when there is no choice.
94            Field::Events(e) => {
95                let mut m = s.serialize_map(Some(3 + usize::from(e.bomb.is_some())))?;
96                m.serialize_entry("lastSantaYear", &json!(e.last_santa_year))?;
97                m.serialize_entry("rngState", &json!(e.extra.seed()))?;
98                match &e.pending {
99                    Some(p) => m.serialize_entry("pending", &Pending(p))?,
100                    None => m.serialize_entry("pending", &Value::Null)?,
101                }
102                // Written only while a search runs, so a save without one
103                // keeps its exact shape.
104                if let Some(b) = &e.bomb {
105                    m.serialize_entry("bomb", &b.save_state())?;
106                }
107                m.end()
108            }
109            Field::Log(es) => Each(es, log_entry).serialize(s),
110        }
111    }
112}
113
114/// A pending choice, written as `save_state` builds it, with its message
115/// from the borrow.
116struct Pending<'a>(&'a PendingChoice);
117
118impl Serialize for Pending<'_> {
119    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
120        let mut m = s.serialize_map(Some(3))?;
121        m.serialize_entry("kind", self.0.kind)?;
122        m.serialize_entry("cost", &json!(self.0.cost))?;
123        m.serialize_entry("message", self.0.message.as_str())?;
124        m.end()
125    }
126}
127
128/// The save's top-level object, written as `Value::Object` writes a map.
129struct Fields<'a>(Vec<(&'static str, Field<'a>)>);
130
131impl Serialize for Fields<'_> {
132    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
133        let mut m = s.serialize_map(Some(self.0.len()))?;
134        for (k, v) in &self.0 {
135            m.serialize_entry(k, v)?;
136        }
137        m.end()
138    }
139}
140
141/// A top-level value the save owns.
142fn own(k: &'static str, v: Value) -> (&'static str, Field<'static>) {
143    (k, Field::Owned(v))
144}
145
146impl Simulation {
147    /// The save's fields, in the order `serialize` has always written them.
148    /// Absent optional fields are left out, which is what the canonical
149    /// writer expects. Each key appears once.
150    fn save_fields(&self) -> Vec<(&'static str, Field<'_>)> {
151        let mut m: Vec<(&'static str, Field<'_>)> = Vec::with_capacity(31);
152        m.push(own("version", json!(SAVE_VERSION)));
153        m.push(own("seed", json!(self.rng.seed())));
154        m.push(own("initialSeed", json!(self.rng.initial_seed)));
155        m.push(own("money", json!(self.money)));
156        m.push(own("star", json!(self.star)));
157        m.push(own("minutes", json!(self.clock.minutes)));
158        m.push(own("mode", json!(self.mode.as_str())));
159        m.push(own("modernCalendar", json!(self.modern_calendar.as_str())));
160        if !self.auto_bridge {
161            m.push(own("autoBridge", json!(false)));
162        }
163        if self.founder {
164            m.push(own("founder", json!(true)));
165        }
166        m.push(own("lastQuarterMoney", json!(self.last_quarter_money)));
167        m.push(("units", Field::Units(&self.tower.units)));
168        m.push(("transports", Field::Transports(&self.tower.transports)));
169        m.push(own("nextId", json!(self.tower.next_id)));
170        if let Some(n) = &self.tower.tower_name {
171            m.push(("towerName", Field::Text(n)));
172        }
173        if let Some(b) = self.tower.built_wedding_hall {
174            m.push(own("builtWeddingHall", json!(b)));
175        }
176        if let Some(b) = self.evaluated_tower {
177            m.push(own("evaluatedTower", json!(b)));
178        }
179        m.push(own("vipVisitDay", json!(self.vip_visit_day)));
180        m.push(own("vipFavorable", json!(self.vip_favorable)));
181        m.push(own("vipVisits", json!(self.vip_visits)));
182        m.push(own("lastVipNagDay", json!(self.last_vip_nag_day)));
183        m.push(own("treasuresFound", json!(self.treasures_found)));
184        if let Some(d) = self.extermination_due_day {
185            m.push(own("exterminationDueDay", json!(d)));
186            if let Some(ids) = &self.extermination_room_ids {
187                m.push(("exterminationRoomIds", Field::Ids(ids)));
188            }
189        }
190        m.push(("events", Field::Events(&self.events)));
191        m.push(("excavated", Field::Strings(&self.excavated)));
192        m.push(("blockbusters", Field::Floats(&self.blockbusters)));
193        m.push(("milestones", Field::Strings(&self.milestones)));
194        m.push(("ledger", Field::Ledger(&self.ledger)));
195        if let Some(v) = &self.view {
196            m.push(("view", Field::Borrowed(v)));
197        }
198        if !self.log.is_empty() {
199            let start = self.log.len().saturating_sub(LOG_SAVE_CAP);
200            m.push(("log", Field::Log(&self.log[start..])));
201        }
202        m
203    }
204
205    /// The whole game as save JSON, the object a `.vctower` file wraps.
206    ///
207    /// A host that edits or inspects the save uses this, then
208    /// [`crate::load::stamp_save`] and [`crate::load::encode_vctower`] to
209    /// write the file. To go straight to the file's bytes, use
210    /// [`crate::load::encode_simulation_vctower`].
211    ///
212    /// ```
213    /// use verticopolis_engine::clock::GameMode;
214    /// use verticopolis_engine::sim::{Simulation, SAVE_VERSION};
215    ///
216    /// let sim = Simulation::new_game(3, GameMode::Classic);
217    /// let save = sim.serialize();
218    /// assert_eq!(save["version"], SAVE_VERSION);
219    /// ```
220    pub fn serialize(&self) -> Value {
221        let mut m = Map::new();
222        for (k, f) in self.save_fields() {
223            m.insert(k.into(), f.into_value());
224        }
225        Value::Object(m)
226    }
227
228    /// The save as a tree without its `units`, and the units as `Value`s one
229    /// at a time: the `.TDT` export reads the units as they come and the
230    /// rest from the tree (`build_tdt_streamed`, #1431), so a save of
231    /// thousands of units is never a tree beside the game. Each unit is the
232    /// `Value` `serialize()` holds for it.
233    #[cfg_attr(not(feature = "wasm"), allow(dead_code))]
234    pub(crate) fn save_without_units(&self) -> (Value, impl Iterator<Item = Value> + '_) {
235        let mut m = Map::new();
236        for (k, f) in self.save_fields() {
237            if k != "units" {
238                m.insert(k.into(), f.into_value());
239            }
240        }
241        (
242            Value::Object(m),
243            self.tower.units.iter().map(Unit::serialize),
244        )
245    }
246
247    #[doc(hidden)]
248    /// The saved game as `serde_json`'s compact text: the same bytes as
249    /// `serialize().to_string()`, written without the tree. `Engine.serialize`
250    /// returns it; that binding keeps its doc line, `serialize()` as JSON
251    /// text, as `engine.d.ts` has it.
252    pub fn save_text(&self) -> String {
253        // Writing to memory fails only on a map key that is not a string or
254        // a value that refuses to serialize; every key here is a string, and
255        // every value a `Value`, a string, a number, or a map or list of
256        // those, so the write cannot fail.
257        serde_json::to_string(&Fields(self.save_fields())).expect("a save writes as JSON")
258    }
259
260    /// The same text, written into `buf`, so the memory tests can reserve the
261    /// buffer first and count only what the write builds beside it.
262    #[cfg(test)]
263    pub(crate) fn write_save(&self, buf: &mut Vec<u8>) {
264        serde_json::to_writer(buf, &Fields(self.save_fields())).expect("a save writes as JSON");
265    }
266}
267
268#[cfg(test)]
269#[path = "sim_save_tests.rs"]
270mod tests;