Skip to main content

yog_api/mods/storage/kept/
owned.rs

1//! Values kept for each owner — the world, a player, a block —
2//! in the owner's store: the Rust side of a Yog Script object's `saved`
3//! and `synced` members (`docs/design/storage.md`), at the same keys.
4
5use std::collections::HashMap;
6use std::hash::Hash;
7
8use yog_core::BlockPos;
9use yog_data::Schema;
10use yog_ids::storage::SEP;
11use yog_ids::units::{CHUNK_BITS, CHUNK_MASK};
12use yog_sync::Mutex;
13
14use super::synced::{self, Audience, Outgoing};
15use super::{Gone, Kept, register};
16use crate::storage::Store;
17
18/// What a kind of owner is to its values: where an owner's value is (its
19/// store, its key there), the owner a [`Gone`] names, the owner as a
20/// synced row names it.
21struct Owners<K> {
22    place: fn(&K, &str) -> Option<(Store, String)>,
23    whose: fn(&Gone<'_>) -> Option<K>,
24    text: fn(&K) -> String,
25}
26
27/// A value, whether it changed since it was put in its store, and since it
28/// was sent.
29struct Slot<T> {
30    value: T,
31    changed: bool,
32    unsent: bool,
33}
34
35/// What an old save called a value (Yog Script's `was`): its key then,
36/// and what reads it from a store as the value is now.
37pub struct Was<T> {
38    pub(super) key: &'static str,
39    read: fn(&Store, &str) -> Option<T>,
40}
41
42impl<T: Schema> Was<T> {
43    /// The old value at `key` of `store`, as the value is now.
44    pub(super) fn read(&self, store: &Store, key: &str) -> Option<T> {
45        (self.read)(store, key)
46    }
47
48    /// The value under `key` before, of the same type.
49    pub const fn renamed(key: &'static str) -> Self {
50        Self::from::<T>(key)
51    }
52
53    /// The value under `key` before, an `Old` the value is made from.
54    pub const fn from<Old: Schema + Into<T>>(key: &'static str) -> Self {
55        Was {
56            key,
57            read: read_as::<Old, T>,
58        }
59    }
60}
61
62/// `store`'s value at `key` as an `Old`, made a `T`.
63fn read_as<Old: Schema + Into<T>, T>(store: &Store, key: &str) -> Option<T> {
64    store.get::<Old>(key).map(Into::into)
65}
66
67/// A value kept for each owner `K`: loaded from the owner's store as it's
68/// first used, written back as the world is saved, removed as the owner
69/// goes; sent to who sees it when `synced`.
70struct Owned<K: 'static, T> {
71    key: &'static str,
72    slots: Mutex<Option<HashMap<K, Slot<T>>>>,
73    owners: &'static Owners<K>,
74    synced: Option<Audience>,
75    was: Option<Was<T>>,
76}
77
78impl<K: Hash + Eq + Clone + Send + Sync + 'static, T: Schema + Default + Send + 'static>
79    Owned<K, T>
80{
81    const fn new(key: &'static str, owners: &'static Owners<K>, synced: Option<Audience>) -> Self {
82        Owned {
83            key,
84            slots: Mutex::new(None),
85            owners,
86            synced,
87            was: None,
88        }
89    }
90
91    fn with<R>(&'static self, owner: K, read: impl FnOnce(&T) -> R) -> R {
92        self.at(owner, |slot| read(&slot.value))
93    }
94
95    fn with_mut<R>(&'static self, owner: K, change: impl FnOnce(&mut T) -> R) -> R {
96        self.at(owner, |slot| {
97            slot.changed = true;
98            slot.unsent = true;
99            change(&mut slot.value)
100        })
101    }
102
103    fn at<R>(&'static self, owner: K, f: impl FnOnce(&mut Slot<T>) -> R) -> R {
104        register(self);
105        let mut slots = self.slots.lock();
106        let slot = slots
107            .get_or_insert_with(HashMap::new)
108            .entry(owner)
109            .or_insert_with_key(|owner| {
110                let (value, changed) = self.first(owner);
111                Slot {
112                    value,
113                    changed,
114                    unsent: false,
115                }
116            });
117        f(slot)
118    }
119
120    /// `owner`'s value as it's first used: what arrived for it (a client),
121    /// or what its store has — under its key, else under what an old save
122    /// called it (`was`: read as it is now, the old record gone, the value
123    /// to be written under its key: `changed`).
124    fn first(&self, owner: &K) -> (T, bool) {
125        let arrived = self
126            .synced
127            .and_then(|_| synced::pending(self.key, &(self.owners.text)(owner)))
128            .and_then(|bytes| yog_data::read_bytes(&bytes).ok());
129        if let Some(value) = arrived {
130            return (value, false);
131        }
132        let Some((store, key)) = (self.owners.place)(owner, self.key) else {
133            return (T::default(), false);
134        };
135        if let Some(value) = store.get(&key) {
136            return (value, false);
137        }
138        let old = self.was.as_ref().and_then(|was| {
139            let (store, old_key) = (self.owners.place)(owner, was.key)?;
140            let value = was.read(&store, &old_key)?;
141            store.remove(&old_key);
142            Some(value)
143        });
144        match old {
145            Some(value) => (value, true),
146            None => (T::default(), false),
147        }
148    }
149}
150
151impl<K: Hash + Eq + Clone + Send + Sync + 'static, T: Schema + Default + Send + 'static> Kept
152    for Owned<K, T>
153{
154    fn keep(&self) {
155        let mut slots = self.slots.lock();
156        for (owner, slot) in slots.iter_mut().flatten().filter(|(_, s)| s.changed) {
157            if let Some((store, key)) = (self.owners.place)(owner, self.key) {
158                store.set(&key, &slot.value);
159                slot.changed = false;
160            }
161        }
162    }
163
164    fn forget(&self) {
165        *self.slots.lock() = None;
166    }
167
168    fn gone(&self, owner: &Gone<'_>) {
169        let Some(owner) = (self.owners.whose)(owner) else {
170            return;
171        };
172        if let Some(slots) = self.slots.lock().as_mut() {
173            slots.remove(&owner);
174        }
175        if let Some((store, key)) = (self.owners.place)(&owner, self.key) {
176            store.remove(&key);
177        }
178    }
179
180    fn sync(&self, every: bool, out: &mut dyn FnMut(Outgoing)) {
181        let Some(audience) = self.synced else {
182            return;
183        };
184        let mut slots = self.slots.lock();
185        for (owner, slot) in slots
186            .iter_mut()
187            .flatten()
188            .filter(|(_, s)| every || s.unsent)
189        {
190            if !every {
191                slot.unsent = false;
192            }
193            let owner = (self.owners.text)(owner);
194            out(Outgoing {
195                to: matches!(audience, Audience::Owner).then(|| owner.clone()),
196                owner,
197                key: self.key,
198                bytes: yog_data::to_bytes(&slot.value),
199            });
200        }
201    }
202
203    fn take(&self, key: &str, owner: &str, bytes: &[u8]) -> bool {
204        if self.synced.is_none() || key != self.key {
205            return false;
206        }
207        let Ok(value) = yog_data::read_bytes::<T>(bytes) else {
208            return true;
209        };
210        let mut slots = self.slots.lock();
211        let slot = slots
212            .iter_mut()
213            .flatten()
214            .find(|(k, _)| (self.owners.text)(k) == owner);
215        match slot {
216            Some((_, slot)) => slot.value = value,
217            None => return false,
218        }
219        true
220    }
221}
222
223/// The public face of an [`Owned`] whose owners are `$owners`: taken as
224/// `$owner` (`$take` makes the key), sent to `$audience` when synced.
225macro_rules! owned {
226    ($(#[$doc:meta])* $name:ident, $key:ty, $owner:ty, $take:expr, $owners:expr, $audience:expr) => {
227        $(#[$doc])*
228        pub struct $name<T>(Owned<$key, T>);
229
230        impl<T: Schema + Default + Send + 'static> $name<T> {
231            pub const fn new(key: &'static str) -> Self {
232                $name(Owned::new(key, $owners, None))
233            }
234
235            /// Kept, and sent to the clients that see its owner as it
236            /// changes (their reads the server's value; their writes their
237            /// own until it sends the next).
238            pub const fn synced(key: &'static str) -> Self {
239                $name(Owned::new(key, $owners, Some($audience)))
240            }
241
242            /// What an old save called the value: read from there when
243            /// its key has nothing, then kept under its key.
244            pub const fn was(mut self, was: Was<T>) -> Self {
245                self.0.was = Some(was);
246                self
247            }
248
249            /// What `read` makes of the owner's value.
250            pub fn with<R>(&'static self, owner: $owner, read: impl FnOnce(&T) -> R) -> R {
251                self.0.with($take(owner), read)
252            }
253
254            /// The owner's value changed by `change` (saved with the world).
255            pub fn with_mut<R>(&'static self, owner: $owner, change: impl FnOnce(&mut T) -> R) -> R {
256                self.0.with_mut($take(owner), change)
257            }
258        }
259    };
260}
261
262owned!(
263    /// A value kept for each player of the world, by uuid
264    /// (`<world>/yog-data/<mod>/player/<uuid>.ydata`, at its key); synced,
265    /// to that player alone.
266    PlayerData, String, &str, str::to_owned,
267    &Owners {
268        place: |uuid, key| Some((crate::storage::player(uuid)?, key.to_owned())),
269        whose: |_| None,
270        text: String::clone,
271    },
272    Audience::Owner
273);
274
275owned!(
276    /// A value kept for each block, by dimension and place (its chunk's
277    /// store, as `<x>_<y>_<z>.<key>` within the chunk): removed as it's
278    /// broken or another is put there.
279    BlockData, (String, [i32; 3]), (&str, BlockPos),
280    |(dim, pos): (&str, BlockPos)| (dim.to_owned(), [pos.x, pos.y, pos.z]),
281    &Owners {
282        place: block_place,
283        whose: |Gone::Block(dim, pos)| Some(((*dim).to_owned(), [pos.x, pos.y, pos.z])),
284        text: |(dim, [x, y, z])| format!("{dim}{SEP}{x}{SEP}{y}{SEP}{z}"),
285    },
286    Audience::All
287);
288
289/// A block's value's place: its chunk's store, and its key there (its
290/// place in the chunk, then the value's).
291fn block_place((dim, [x, y, z]): &(String, [i32; 3]), key: &str) -> Option<(Store, String)> {
292    let store = crate::storage::chunk(dim, x >> CHUNK_BITS, z >> CHUNK_BITS)?;
293    let owner = format!("{}{SEP}{y}{SEP}{}", x & CHUNK_MASK, z & CHUNK_MASK);
294    Some((store, yog_event::storage::record_key(&owner, key)))
295}
296
297/// The world's owners: the one world.
298static WORLD: Owners<()> = Owners {
299    place: |(), key| Some((crate::storage::world()?, key.to_owned())),
300    whose: |_| None,
301    text: |()| String::new(),
302};
303
304/// A value kept with the world (`<world>/yog-data/<mod>/world.ydata`, at
305/// its key): its default where there's none yet; with no world open (a
306/// client of another's server) it's kept in memory only. Synced, to everyone.
307pub struct WorldData<T>(Owned<(), T>);
308
309impl<T: Schema + Default + Send + 'static> WorldData<T> {
310    pub const fn new(key: &'static str) -> Self {
311        WorldData(Owned::new(key, &WORLD, None))
312    }
313
314    /// Kept, and sent to every client as it changes.
315    pub const fn synced(key: &'static str) -> Self {
316        WorldData(Owned::new(key, &WORLD, Some(Audience::All)))
317    }
318
319    /// What an old save called the value: read from there when its key
320    /// has nothing, then kept under its key.
321    pub const fn was(mut self, was: Was<T>) -> Self {
322        self.0.was = Some(was);
323        self
324    }
325
326    /// What `read` makes of the value.
327    pub fn with<R>(&'static self, read: impl FnOnce(&T) -> R) -> R {
328        self.0.with((), read)
329    }
330
331    /// The value changed by `change` (saved with the world); what `change` returns.
332    pub fn with_mut<R>(&'static self, change: impl FnOnce(&mut T) -> R) -> R {
333        self.0.with_mut((), change)
334    }
335}