use std::collections::HashMap;
use std::hash::Hash;
use yog_core::BlockPos;
use yog_data::Schema;
use yog_ids::storage::SEP;
use yog_ids::units::{CHUNK_BITS, CHUNK_MASK};
use yog_sync::Mutex;
use super::synced::{self, Audience, Outgoing};
use super::{Gone, Kept, register};
use crate::storage::Store;
struct Owners<K> {
place: fn(&K, &str) -> Option<(Store, String)>,
whose: fn(&Gone<'_>) -> Option<K>,
text: fn(&K) -> String,
}
struct Slot<T> {
value: T,
changed: bool,
unsent: bool,
}
pub struct Was<T> {
pub(super) key: &'static str,
read: fn(&Store, &str) -> Option<T>,
}
impl<T: Schema> Was<T> {
pub(super) fn read(&self, store: &Store, key: &str) -> Option<T> {
(self.read)(store, key)
}
pub const fn renamed(key: &'static str) -> Self {
Self::from::<T>(key)
}
pub const fn from<Old: Schema + Into<T>>(key: &'static str) -> Self {
Was {
key,
read: read_as::<Old, T>,
}
}
}
fn read_as<Old: Schema + Into<T>, T>(store: &Store, key: &str) -> Option<T> {
store.get::<Old>(key).map(Into::into)
}
struct Owned<K: 'static, T> {
key: &'static str,
slots: Mutex<Option<HashMap<K, Slot<T>>>>,
owners: &'static Owners<K>,
synced: Option<Audience>,
was: Option<Was<T>>,
}
impl<K: Hash + Eq + Clone + Send + Sync + 'static, T: Schema + Default + Send + 'static>
Owned<K, T>
{
const fn new(key: &'static str, owners: &'static Owners<K>, synced: Option<Audience>) -> Self {
Owned {
key,
slots: Mutex::new(None),
owners,
synced,
was: None,
}
}
fn with<R>(&'static self, owner: K, read: impl FnOnce(&T) -> R) -> R {
self.at(owner, |slot| read(&slot.value))
}
fn with_mut<R>(&'static self, owner: K, change: impl FnOnce(&mut T) -> R) -> R {
self.at(owner, |slot| {
slot.changed = true;
slot.unsent = true;
change(&mut slot.value)
})
}
fn at<R>(&'static self, owner: K, f: impl FnOnce(&mut Slot<T>) -> R) -> R {
register(self);
let mut slots = self.slots.lock();
let slot = slots
.get_or_insert_with(HashMap::new)
.entry(owner)
.or_insert_with_key(|owner| {
let (value, changed) = self.first(owner);
Slot {
value,
changed,
unsent: false,
}
});
f(slot)
}
fn first(&self, owner: &K) -> (T, bool) {
let arrived = self
.synced
.and_then(|_| synced::pending(self.key, &(self.owners.text)(owner)))
.and_then(|bytes| yog_data::read_bytes(&bytes).ok());
if let Some(value) = arrived {
return (value, false);
}
let Some((store, key)) = (self.owners.place)(owner, self.key) else {
return (T::default(), false);
};
if let Some(value) = store.get(&key) {
return (value, false);
}
let old = self.was.as_ref().and_then(|was| {
let (store, old_key) = (self.owners.place)(owner, was.key)?;
let value = was.read(&store, &old_key)?;
store.remove(&old_key);
Some(value)
});
match old {
Some(value) => (value, true),
None => (T::default(), false),
}
}
}
impl<K: Hash + Eq + Clone + Send + Sync + 'static, T: Schema + Default + Send + 'static> Kept
for Owned<K, T>
{
fn keep(&self) {
let mut slots = self.slots.lock();
for (owner, slot) in slots.iter_mut().flatten().filter(|(_, s)| s.changed) {
if let Some((store, key)) = (self.owners.place)(owner, self.key) {
store.set(&key, &slot.value);
slot.changed = false;
}
}
}
fn forget(&self) {
*self.slots.lock() = None;
}
fn gone(&self, owner: &Gone<'_>) {
let Some(owner) = (self.owners.whose)(owner) else {
return;
};
if let Some(slots) = self.slots.lock().as_mut() {
slots.remove(&owner);
}
if let Some((store, key)) = (self.owners.place)(&owner, self.key) {
store.remove(&key);
}
}
fn sync(&self, every: bool, out: &mut dyn FnMut(Outgoing)) {
let Some(audience) = self.synced else {
return;
};
let mut slots = self.slots.lock();
for (owner, slot) in slots
.iter_mut()
.flatten()
.filter(|(_, s)| every || s.unsent)
{
if !every {
slot.unsent = false;
}
let owner = (self.owners.text)(owner);
out(Outgoing {
to: matches!(audience, Audience::Owner).then(|| owner.clone()),
owner,
key: self.key,
bytes: yog_data::to_bytes(&slot.value),
});
}
}
fn take(&self, key: &str, owner: &str, bytes: &[u8]) -> bool {
if self.synced.is_none() || key != self.key {
return false;
}
let Ok(value) = yog_data::read_bytes::<T>(bytes) else {
return true;
};
let mut slots = self.slots.lock();
let slot = slots
.iter_mut()
.flatten()
.find(|(k, _)| (self.owners.text)(k) == owner);
match slot {
Some((_, slot)) => slot.value = value,
None => return false,
}
true
}
}
macro_rules! owned {
($(#[$doc:meta])* $name:ident, $key:ty, $owner:ty, $take:expr, $owners:expr, $audience:expr) => {
$(#[$doc])*
pub struct $name<T>(Owned<$key, T>);
impl<T: Schema + Default + Send + 'static> $name<T> {
pub const fn new(key: &'static str) -> Self {
$name(Owned::new(key, $owners, None))
}
pub const fn synced(key: &'static str) -> Self {
$name(Owned::new(key, $owners, Some($audience)))
}
pub const fn was(mut self, was: Was<T>) -> Self {
self.0.was = Some(was);
self
}
pub fn with<R>(&'static self, owner: $owner, read: impl FnOnce(&T) -> R) -> R {
self.0.with($take(owner), read)
}
pub fn with_mut<R>(&'static self, owner: $owner, change: impl FnOnce(&mut T) -> R) -> R {
self.0.with_mut($take(owner), change)
}
}
};
}
owned!(
PlayerData, String, &str, str::to_owned,
&Owners {
place: |uuid, key| Some((crate::storage::player(uuid)?, key.to_owned())),
whose: |_| None,
text: String::clone,
},
Audience::Owner
);
owned!(
BlockData, (String, [i32; 3]), (&str, BlockPos),
|(dim, pos): (&str, BlockPos)| (dim.to_owned(), [pos.x, pos.y, pos.z]),
&Owners {
place: block_place,
whose: |Gone::Block(dim, pos)| Some(((*dim).to_owned(), [pos.x, pos.y, pos.z])),
text: |(dim, [x, y, z])| format!("{dim}{SEP}{x}{SEP}{y}{SEP}{z}"),
},
Audience::All
);
fn block_place((dim, [x, y, z]): &(String, [i32; 3]), key: &str) -> Option<(Store, String)> {
let store = crate::storage::chunk(dim, x >> CHUNK_BITS, z >> CHUNK_BITS)?;
let owner = format!("{}{SEP}{y}{SEP}{}", x & CHUNK_MASK, z & CHUNK_MASK);
Some((store, yog_event::storage::record_key(&owner, key)))
}
static WORLD: Owners<()> = Owners {
place: |(), key| Some((crate::storage::world()?, key.to_owned())),
whose: |_| None,
text: |()| String::new(),
};
pub struct WorldData<T>(Owned<(), T>);
impl<T: Schema + Default + Send + 'static> WorldData<T> {
pub const fn new(key: &'static str) -> Self {
WorldData(Owned::new(key, &WORLD, None))
}
pub const fn synced(key: &'static str) -> Self {
WorldData(Owned::new(key, &WORLD, Some(Audience::All)))
}
pub const fn was(mut self, was: Was<T>) -> Self {
self.0.was = Some(was);
self
}
pub fn with<R>(&'static self, read: impl FnOnce(&T) -> R) -> R {
self.0.with((), read)
}
pub fn with_mut<R>(&'static self, change: impl FnOnce(&mut T) -> R) -> R {
self.0.with_mut((), change)
}
}