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/// A value kept for each owner `K`: loaded from the owner's store as it's
36/// first used, written back as the world is saved, removed as the owner
37/// goes; sent to who sees it when `synced`.
38struct Owned<K: 'static, T> {
39    key: &'static str,
40    slots: Mutex<Option<HashMap<K, Slot<T>>>>,
41    owners: &'static Owners<K>,
42    synced: Option<Audience>,
43}
44
45impl<K: Hash + Eq + Clone + Send + Sync + 'static, T: Schema + Default + Send + 'static>
46    Owned<K, T>
47{
48    const fn new(key: &'static str, owners: &'static Owners<K>, synced: Option<Audience>) -> Self {
49        Owned {
50            key,
51            slots: Mutex::new(None),
52            owners,
53            synced,
54        }
55    }
56
57    fn with<R>(&'static self, owner: K, read: impl FnOnce(&T) -> R) -> R {
58        self.at(owner, |slot| read(&slot.value))
59    }
60
61    fn with_mut<R>(&'static self, owner: K, change: impl FnOnce(&mut T) -> R) -> R {
62        self.at(owner, |slot| {
63            slot.changed = true;
64            slot.unsent = true;
65            change(&mut slot.value)
66        })
67    }
68
69    fn at<R>(&'static self, owner: K, f: impl FnOnce(&mut Slot<T>) -> R) -> R {
70        register(self);
71        let mut slots = self.slots.lock();
72        let slot = slots
73            .get_or_insert_with(HashMap::new)
74            .entry(owner)
75            .or_insert_with_key(|owner| Slot {
76                value: self.first(owner),
77                changed: false,
78                unsent: false,
79            });
80        f(slot)
81    }
82
83    /// `owner`'s value as it's first used: what arrived for it (a client),
84    /// or what its store has.
85    fn first(&self, owner: &K) -> T {
86        let arrived = self
87            .synced
88            .and_then(|_| synced::pending(self.key, &(self.owners.text)(owner)))
89            .and_then(|bytes| yog_data::read_bytes(&bytes).ok());
90        arrived.unwrap_or_else(|| {
91            (self.owners.place)(owner, self.key)
92                .and_then(|(store, key)| store.get(&key))
93                .unwrap_or_default()
94        })
95    }
96}
97
98impl<K: Hash + Eq + Clone + Send + Sync + 'static, T: Schema + Default + Send + 'static> Kept
99    for Owned<K, T>
100{
101    fn keep(&self) {
102        let mut slots = self.slots.lock();
103        for (owner, slot) in slots.iter_mut().flatten().filter(|(_, s)| s.changed) {
104            if let Some((store, key)) = (self.owners.place)(owner, self.key) {
105                store.set(&key, &slot.value);
106                slot.changed = false;
107            }
108        }
109    }
110
111    fn forget(&self) {
112        *self.slots.lock() = None;
113    }
114
115    fn gone(&self, owner: &Gone<'_>) {
116        let Some(owner) = (self.owners.whose)(owner) else {
117            return;
118        };
119        if let Some(slots) = self.slots.lock().as_mut() {
120            slots.remove(&owner);
121        }
122        if let Some((store, key)) = (self.owners.place)(&owner, self.key) {
123            store.remove(&key);
124        }
125    }
126
127    fn sync(&self, every: bool, out: &mut dyn FnMut(Outgoing)) {
128        let Some(audience) = self.synced else {
129            return;
130        };
131        let mut slots = self.slots.lock();
132        for (owner, slot) in slots
133            .iter_mut()
134            .flatten()
135            .filter(|(_, s)| every || s.unsent)
136        {
137            if !every {
138                slot.unsent = false;
139            }
140            let owner = (self.owners.text)(owner);
141            out(Outgoing {
142                to: matches!(audience, Audience::Owner).then(|| owner.clone()),
143                owner,
144                key: self.key,
145                bytes: yog_data::to_bytes(&slot.value),
146            });
147        }
148    }
149
150    fn take(&self, key: &str, owner: &str, bytes: &[u8]) -> bool {
151        if self.synced.is_none() || key != self.key {
152            return false;
153        }
154        let Ok(value) = yog_data::read_bytes::<T>(bytes) else {
155            return true;
156        };
157        let mut slots = self.slots.lock();
158        let slot = slots
159            .iter_mut()
160            .flatten()
161            .find(|(k, _)| (self.owners.text)(k) == owner);
162        match slot {
163            Some((_, slot)) => slot.value = value,
164            None => return false,
165        }
166        true
167    }
168}
169
170/// The public face of an [`Owned`] whose owners are `$owners`: taken as
171/// `$owner` (`$take` makes the key), sent to `$audience` when synced.
172macro_rules! owned {
173    ($(#[$doc:meta])* $name:ident, $key:ty, $owner:ty, $take:expr, $owners:expr, $audience:expr) => {
174        $(#[$doc])*
175        pub struct $name<T>(Owned<$key, T>);
176
177        impl<T: Schema + Default + Send + 'static> $name<T> {
178            pub const fn new(key: &'static str) -> Self {
179                $name(Owned::new(key, $owners, None))
180            }
181
182            /// Kept, and sent to the clients that see its owner as it
183            /// changes (their reads the server's value; their writes their
184            /// own until it sends the next).
185            pub const fn synced(key: &'static str) -> Self {
186                $name(Owned::new(key, $owners, Some($audience)))
187            }
188
189            /// What `read` makes of the owner's value.
190            pub fn with<R>(&'static self, owner: $owner, read: impl FnOnce(&T) -> R) -> R {
191                self.0.with($take(owner), read)
192            }
193
194            /// The owner's value changed by `change` (saved with the world).
195            pub fn with_mut<R>(&'static self, owner: $owner, change: impl FnOnce(&mut T) -> R) -> R {
196                self.0.with_mut($take(owner), change)
197            }
198        }
199    };
200}
201
202owned!(
203    /// A value kept for each player of the world, by uuid
204    /// (`<world>/yog-data/<mod>/player/<uuid>.ydata`, at its key); synced,
205    /// to that player alone.
206    PlayerData, String, &str, str::to_owned,
207    &Owners {
208        place: |uuid, key| Some((crate::storage::player(uuid)?, key.to_owned())),
209        whose: |_| None,
210        text: String::clone,
211    },
212    Audience::Owner
213);
214
215owned!(
216    /// A value kept for each block, by dimension and place (its chunk's
217    /// store, as `<x>_<y>_<z>.<key>` within the chunk): removed as it's
218    /// broken or another is put there.
219    BlockData, (String, [i32; 3]), (&str, BlockPos),
220    |(dim, pos): (&str, BlockPos)| (dim.to_owned(), [pos.x, pos.y, pos.z]),
221    &Owners {
222        place: block_place,
223        whose: |Gone::Block(dim, pos)| Some(((*dim).to_owned(), [pos.x, pos.y, pos.z])),
224        text: |(dim, [x, y, z])| format!("{dim}{SEP}{x}{SEP}{y}{SEP}{z}"),
225    },
226    Audience::All
227);
228
229/// A block's value's place: its chunk's store, and its key there (its
230/// place in the chunk, then the value's).
231fn block_place((dim, [x, y, z]): &(String, [i32; 3]), key: &str) -> Option<(Store, String)> {
232    let store = crate::storage::chunk(dim, x >> CHUNK_BITS, z >> CHUNK_BITS)?;
233    let owner = format!("{}{SEP}{y}{SEP}{}", x & CHUNK_MASK, z & CHUNK_MASK);
234    Some((store, yog_event::storage::record_key(&owner, key)))
235}
236
237/// The world's owners: the one world.
238static WORLD: Owners<()> = Owners {
239    place: |(), key| Some((crate::storage::world()?, key.to_owned())),
240    whose: |_| None,
241    text: |()| String::new(),
242};
243
244/// A value kept with the world (`<world>/yog-data/<mod>/world.ydata`, at
245/// its key): its default where there's none yet; with no world open (a
246/// client of another's server) it's kept in memory only. Synced, to everyone.
247pub struct WorldData<T>(Owned<(), T>);
248
249impl<T: Schema + Default + Send + 'static> WorldData<T> {
250    pub const fn new(key: &'static str) -> Self {
251        WorldData(Owned::new(key, &WORLD, None))
252    }
253
254    /// Kept, and sent to every client as it changes.
255    pub const fn synced(key: &'static str) -> Self {
256        WorldData(Owned::new(key, &WORLD, Some(Audience::All)))
257    }
258
259    /// What `read` makes of the value.
260    pub fn with<R>(&'static self, read: impl FnOnce(&T) -> R) -> R {
261        self.0.with((), read)
262    }
263
264    /// The value changed by `change` (saved with the world); what `change` returns.
265    pub fn with_mut<R>(&'static self, change: impl FnOnce(&mut T) -> R) -> R {
266        self.0.with_mut((), change)
267    }
268}