Skip to main content

yog_api/storage/
kept.rs

1//! Values the mod keeps: loaded from their store when first used, written
2//! back as the world is saved — no loading and saving by hand.
3
4use std::collections::HashMap;
5
6use yog_data::Schema;
7use yog_sync::Mutex;
8
9/// What's kept, as the saving sees it.
10trait Kept: Sync {
11    /// Puts what changed into its store.
12    fn keep(&self);
13    /// The world closed: what belongs to it is forgotten (the next world loads its own).
14    fn forget(&self);
15}
16
17/// Every kept value used so far.
18static KEPT: Mutex<Vec<&'static dyn Kept>> = Mutex::new(Vec::new());
19
20fn register(kept: &'static dyn Kept) {
21    let mut all = KEPT.lock();
22    if !all.iter().any(|k| std::ptr::addr_eq(*k, kept)) {
23        all.push(kept);
24    }
25}
26
27pub(super) fn keep_all() {
28    for kept in KEPT.lock().iter() {
29        kept.keep();
30    }
31}
32
33pub(super) fn forget_world() {
34    for kept in KEPT.lock().iter() {
35        kept.forget();
36    }
37}
38
39/// A value and whether it changed since it was put in its store.
40struct Slot<T> {
41    value: T,
42    changed: bool,
43}
44
45/// A value kept with the world (`<world>/yog-data/<mod>/world.ydata`, at
46/// its key): its default where there's none yet; with no world open (a
47/// client of another's server) it's kept in memory only.
48pub struct WorldData<T> {
49    key: &'static str,
50    slot: Mutex<Option<Slot<T>>>,
51}
52
53impl<T: Schema + Default + Send + 'static> WorldData<T> {
54    pub const fn new(key: &'static str) -> Self {
55        WorldData {
56            key,
57            slot: Mutex::new(None),
58        }
59    }
60
61    /// What `read` makes of the value.
62    pub fn with<R>(&'static self, read: impl FnOnce(&T) -> R) -> R {
63        self.at(|slot| read(&slot.value))
64    }
65
66    /// The value changed by `change` (saved with the world); what `change` returns.
67    pub fn with_mut<R>(&'static self, change: impl FnOnce(&mut T) -> R) -> R {
68        self.at(|slot| {
69            slot.changed = true;
70            change(&mut slot.value)
71        })
72    }
73
74    fn at<R>(&'static self, f: impl FnOnce(&mut Slot<T>) -> R) -> R {
75        register(self);
76        let mut slot = self.slot.lock();
77        f(slot.get_or_insert_with(|| Slot {
78            value: super::world()
79                .and_then(|store| store.get(self.key))
80                .unwrap_or_default(),
81            changed: false,
82        }))
83    }
84}
85
86impl<T: Schema + Default + Send + 'static> Kept for WorldData<T> {
87    fn keep(&self) {
88        let mut slot = self.slot.lock();
89        if let Some(slot) = slot.as_mut().filter(|s| s.changed)
90            && let Some(store) = super::world()
91        {
92            store.set(self.key, &slot.value);
93            slot.changed = false;
94        }
95    }
96
97    fn forget(&self) {
98        *self.slot.lock() = None;
99    }
100}
101
102/// A value kept for each player of the world (`<world>/yog-data/<mod>/player/<uuid>.ydata`, at its key).
103pub struct PlayerData<T> {
104    key: &'static str,
105    slots: Mutex<Option<HashMap<String, Slot<T>>>>,
106}
107
108impl<T: Schema + Default + Send + 'static> PlayerData<T> {
109    pub const fn new(key: &'static str) -> Self {
110        PlayerData {
111            key,
112            slots: Mutex::new(None),
113        }
114    }
115
116    /// What `read` makes of player `uuid`'s value.
117    pub fn with<R>(&'static self, uuid: &str, read: impl FnOnce(&T) -> R) -> R {
118        self.at(uuid, |slot| read(&slot.value))
119    }
120
121    /// Player `uuid`'s value changed by `change` (saved with the world).
122    pub fn with_mut<R>(&'static self, uuid: &str, change: impl FnOnce(&mut T) -> R) -> R {
123        self.at(uuid, |slot| {
124            slot.changed = true;
125            change(&mut slot.value)
126        })
127    }
128
129    fn at<R>(&'static self, uuid: &str, f: impl FnOnce(&mut Slot<T>) -> R) -> R {
130        register(self);
131        let mut slots = self.slots.lock();
132        let slot = slots
133            .get_or_insert_with(HashMap::new)
134            .entry(uuid.to_owned())
135            .or_insert_with(|| Slot {
136                value: super::player(uuid)
137                    .and_then(|store| store.get(self.key))
138                    .unwrap_or_default(),
139                changed: false,
140            });
141        f(slot)
142    }
143}
144
145impl<T: Schema + Default + Send + 'static> Kept for PlayerData<T> {
146    fn keep(&self) {
147        let mut slots = self.slots.lock();
148        for (uuid, slot) in slots.iter_mut().flatten().filter(|(_, s)| s.changed) {
149            if let Some(store) = super::player(uuid) {
150                store.set(self.key, &slot.value);
151                slot.changed = false;
152            }
153        }
154    }
155
156    fn forget(&self) {
157        *self.slots.lock() = None;
158    }
159}
160
161/// A value kept for the install, whatever world (`<game>/yog-data/<mod>/game.ydata`):
162/// written as it changes.
163pub struct GameData<T> {
164    key: &'static str,
165    slot: Mutex<Option<T>>,
166}
167
168impl<T: Schema + Default + Send + 'static> GameData<T> {
169    pub const fn new(key: &'static str) -> Self {
170        GameData {
171            key,
172            slot: Mutex::new(None),
173        }
174    }
175
176    /// What `read` makes of the value.
177    pub fn with<R>(&'static self, read: impl FnOnce(&T) -> R) -> R {
178        let mut slot = self.slot.lock();
179        read(slot.get_or_insert_with(|| super::game().get_or_default(self.key)))
180    }
181
182    /// The value changed by `change`, and written.
183    pub fn with_mut<R>(&'static self, change: impl FnOnce(&mut T) -> R) -> R {
184        let mut slot = self.slot.lock();
185        let value = slot.get_or_insert_with(|| super::game().get_or_default(self.key));
186        let out = change(value);
187        let store = super::game();
188        store.set(self.key, value);
189        if let Err(e) = store.flush() {
190            crate::error!("[{}] couldn't save its data: {e}", crate::mod_id());
191        }
192        out
193    }
194}