Skip to main content

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