yog_api/mods/storage.rs
1//! Where the mod keeps its data — typed values in stores scoped the way web
2//! storage is (`yog-storage`), opened for this mod in the right folder:
3//!
4//! ```ignore
5//! use yog_api::storage;
6//!
7//! storage::game().set("theme", &theme); // the install, whatever world
8//! if let Some(world) = storage::world() { // this world, saved with it
9//! world.update("visits", |n: &mut u32| *n += 1);
10//! }
11//! storage::session().set("last_screen", &screen); // while the game runs
12//! ```
13//!
14//! Or a value kept for the mod, loaded when first used and saved with the
15//! world ([`WorldData`], [`PlayerData`], [`GameData`]):
16//!
17//! ```ignore
18//! static LABELS: WorldData<Vec<Label>> = WorldData::new("labels");
19//! LABELS.with_mut(|labels| labels.push(label));
20//! ```
21//!
22//! What's kept with the world is written as the world is saved
23//! (`server::life::Saving`: an autosave, `/save-all`, the server stopping),
24//! so it's saved with the world it belongs to.
25
26mod kept;
27mod records;
28
29use std::path::PathBuf;
30
31use yog_sync::Mutex;
32use yog_sync::atomic::{AtomicBool, Ordering};
33
34pub use kept::{GameData, PlayerData, WorldData};
35#[doc(hidden)]
36pub use records::__provide_records;
37pub use yog_storage::{Scope, Store, close_under, flush_all};
38
39use crate::Registry;
40use crate::event::server::life::{Saving, Started, Stopping};
41
42/// The open world's folder (none on a client of another's server, or at the title screen).
43static WORLD: Mutex<Option<PathBuf>> = Mutex::new(None);
44/// The server is stopping: the next save is the world's last.
45static CLOSING: AtomicBool = AtomicBool::new(false);
46
47/// This mod's session store: kept while the game runs, never written.
48pub fn session() -> Store {
49 Store::session(crate::mod_id())
50}
51
52/// This mod's store for the install, whatever world.
53pub fn game() -> Store {
54 Store::open(&game_dir(), crate::mod_id(), Scope::Game)
55}
56
57/// This mod's store for the open world.
58pub fn world() -> Option<Store> {
59 in_world(Scope::World)
60}
61
62/// This mod's store for dimension `dim` of the open world.
63pub fn dimension(dim: &str) -> Option<Store> {
64 in_world(Scope::Dimension(dim))
65}
66
67/// This mod's store for player `uuid` in the open world.
68pub fn player(uuid: &str) -> Option<Store> {
69 in_world(Scope::Player(uuid))
70}
71
72/// This mod's store for entity `uuid` in the open world.
73pub fn entity(uuid: &str) -> Option<Store> {
74 in_world(Scope::Entity(uuid))
75}
76
77/// This mod's store for chunk `cx`, `cz` of dimension `dim` in the open world.
78pub fn chunk(dim: &str, cx: i32, cz: i32) -> Option<Store> {
79 in_world(Scope::Chunk(dim, cx, cz))
80}
81
82fn in_world(scope: Scope<'_>) -> Option<Store> {
83 let root = WORLD.lock().clone()?;
84 Some(Store::open(&root, crate::mod_id(), scope))
85}
86
87/// The game's folder (the working folder when the host can't say).
88fn game_dir() -> PathBuf {
89 crate::game::dir().map(PathBuf::from).unwrap_or_default()
90}
91
92/// The world's folder known as it starts — registered by `export_mod!`
93/// before the mod's own handlers, so theirs find its stores.
94#[doc(hidden)]
95pub fn __know_world(registry: &mut Registry) {
96 registry.on::<Started, _>(|e, _| {
97 *WORLD.lock() = Some(PathBuf::from(&e.world_dir));
98 });
99}
100
101/// What's kept saved with the world — registered by `export_mod!` after
102/// the mod's own handlers, so what theirs put in the stores is written.
103#[doc(hidden)]
104pub fn __save_with_world(registry: &mut Registry) {
105 registry.on::<Stopping, _>(|_, _| CLOSING.store(true, Ordering::Release));
106 registry.on::<Saving, _>(|_, _| saving());
107}
108
109/// The world being saved: what's kept written; the world's last save closes it.
110fn saving() {
111 kept::keep_all();
112 if let Err(e) = flush_all() {
113 crate::error!("[{}] couldn't save its data: {e}", crate::mod_id());
114 }
115 if !CLOSING.swap(false, Ordering::AcqRel) {
116 return;
117 }
118 kept::forget_world();
119 if let Some(root) = WORLD.lock().take()
120 && let Err(e) = close_under(&root)
121 {
122 crate::error!("[{}] couldn't save its data: {e}", crate::mod_id());
123 }
124}
125
126#[cfg(test)]
127mod tests;