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//!
26//! A value kept for each owner — [`PlayerData`], [`EntityData`] (gone as it
27//! dies), [`BlockData`] (gone as it's broken) — is in the owner's store, at
28//! the keys a Yog Script object's `saved` members have; a value kept on a
29//! stack, travelling with it, is an [`ItemData`].
30
31mod item;
32mod kept;
33mod records;
34
35use std::path::PathBuf;
36
37use yog_sync::Mutex;
38use yog_sync::atomic::{AtomicBool, Ordering};
39
40pub use item::ItemData;
41pub use kept::{BlockData, EntityData, GameData, PlayerData, WorldData};
42#[doc(hidden)]
43pub use records::__provide_records;
44pub use yog_storage::{Scope, Store, close_under, flush_all};
45
46use crate::Registry;
47use crate::event::block::{Break, Place};
48use crate::event::entity::life::Death;
49use crate::event::server::life::{Saving, Started, Stopping};
50use kept::Gone;
51
52/// The open world's folder (none on a client of another's server, or at the title screen).
53static WORLD: Mutex<Option<PathBuf>> = Mutex::new(None);
54/// The server is stopping: the next save is the world's last.
55static CLOSING: AtomicBool = AtomicBool::new(false);
56
57/// This mod's session store: kept while the game runs, never written.
58pub fn session() -> Store {
59 Store::session(crate::mod_id())
60}
61
62/// This mod's store for the install, whatever world.
63pub fn game() -> Store {
64 Store::open(&game_dir(), crate::mod_id(), Scope::Game)
65}
66
67/// This mod's store for the open world.
68pub fn world() -> Option<Store> {
69 in_world(Scope::World)
70}
71
72/// This mod's store for dimension `dim` of the open world.
73pub fn dimension(dim: &str) -> Option<Store> {
74 in_world(Scope::Dimension(dim))
75}
76
77/// This mod's store for player `uuid` in the open world.
78pub fn player(uuid: &str) -> Option<Store> {
79 in_world(Scope::Player(uuid))
80}
81
82/// This mod's store for entity `uuid` in the open world.
83pub fn entity(uuid: &str) -> Option<Store> {
84 in_world(Scope::Entity(uuid))
85}
86
87/// This mod's store for chunk `cx`, `cz` of dimension `dim` in the open world.
88pub fn chunk(dim: &str, cx: i32, cz: i32) -> Option<Store> {
89 in_world(Scope::Chunk(dim, cx, cz))
90}
91
92/// The server runs in this game: its world is open here.
93fn world_open() -> bool {
94 WORLD.lock().is_some()
95}
96
97fn in_world(scope: Scope<'_>) -> Option<Store> {
98 let root = WORLD.lock().clone()?;
99 Some(Store::open(&root, crate::mod_id(), scope))
100}
101
102/// The game's folder (the working folder when the host can't say).
103fn game_dir() -> PathBuf {
104 crate::game::dir().map(PathBuf::from).unwrap_or_default()
105}
106
107/// The world's folder known as it starts — registered by `export_mod!`
108/// before the mod's own handlers, so theirs find its stores.
109#[doc(hidden)]
110pub fn __know_world(registry: &mut Registry) {
111 registry.on::<Started, _>(|e, _| {
112 *WORLD.lock() = Some(PathBuf::from(&e.world_dir));
113 });
114 // A block put in place starts fresh: what the one before kept is gone
115 // before the mod's handlers set its own.
116 registry.on::<Place, _>(|e, _| kept::gone(&Gone::Block(&e.dimension, e.pos)));
117}
118
119/// What's kept saved with the world — registered by `export_mod!` after
120/// the mod's own handlers, so what theirs put in the stores is written.
121#[doc(hidden)]
122pub fn __save_with_world(registry: &mut Registry) {
123 registry.on::<Stopping, _>(|_, _| CLOSING.store(true, Ordering::Release));
124 registry.on::<Saving, _>(|_, _| saving());
125 // Gone after the mod's handlers: theirs still read what it kept.
126 registry.on::<Break, _>(|e, _| kept::gone(&Gone::Block(&e.dimension, e.pos)));
127 registry.on::<Death, _>(|e, _| kept::gone(&Gone::Entity(&e.uuid)));
128 kept::synced::listen(registry);
129}
130
131/// The world being saved: what's kept written; the world's last save closes it.
132fn saving() {
133 kept::keep_all();
134 if let Err(e) = flush_all() {
135 crate::error!("[{}] couldn't save its data: {e}", crate::mod_id());
136 }
137 if !CLOSING.swap(false, Ordering::AcqRel) {
138 return;
139 }
140 kept::forget_world();
141 if let Some(root) = WORLD.lock().take()
142 && let Err(e) = close_under(&root)
143 {
144 crate::error!("[{}] couldn't save its data: {e}", crate::mod_id());
145 }
146}
147
148/// Tests that open a world, or count on none open, take turns.
149#[cfg(test)]
150static WORLD_TESTS: Mutex<()> = Mutex::new(());
151
152#[cfg(test)]
153mod tests;