use super::{LocalProperty, PropertyBinding, PropertyValue, UntypedProperty};
use crate::Interpolatable;
use serde::{Deserialize, Serialize};
use std::{
cell::Cell,
collections::HashMap,
sync::{
atomic::{AtomicU64, Ordering},
Arc, Mutex, MutexGuard, Weak,
},
};
use super::shared_graph::{with_current_graph, GraphInbox};
pub trait SharedPropertyValue: PropertyValue + Send + Sync {}
impl<T: PropertyValue + Send + Sync> SharedPropertyValue for T {}
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
pub(super) fn next_id() -> u64 {
NEXT_ID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
.expect("property identity space exhausted")
}
pub(super) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
thread_local! {
static MUTATING: Cell<bool> = const { Cell::new(false) };
}
struct MutationGuard;
impl MutationGuard {
fn enter() -> Self {
MUTATING.with(|active| {
assert!(
!active.replace(true),
"property mutation callbacks must not mutate another property recursively"
);
});
Self
}
}
impl Drop for MutationGuard {
fn drop(&mut self) {
MUTATING.with(|active| active.set(false));
}
}
pub(super) struct Snapshot<T> {
pub revision: u64,
pub value: Arc<T>,
}
pub(super) struct SharedCell<T> {
pub id: u64,
value: Mutex<Snapshot<T>>,
writer: Mutex<()>,
subscribers: Mutex<HashMap<u64, Weak<GraphInbox>>>,
}
impl<T> Drop for SharedCell<T> {
fn drop(&mut self) {
let subscribers = self
.subscribers
.get_mut()
.unwrap_or_else(|e| e.into_inner());
for inbox in subscribers.values().filter_map(Weak::upgrade) {
inbox.publish(self.id);
}
}
}
impl<T: SharedPropertyValue> SharedCell<T> {
pub fn snapshot(&self) -> Snapshot<T> {
let snapshot = lock(&self.value);
Snapshot {
revision: snapshot.revision,
value: snapshot.value.clone(),
}
}
fn change(&self, origin: Option<u64>, update: impl FnOnce(&T) -> Option<T>) -> (bool, u64) {
let mutation = MutationGuard::enter();
let writer = lock(&self.writer);
let before = self.snapshot();
let Some(value) = update(&before.value) else {
return (false, before.revision);
};
let revision = before
.revision
.checked_add(1)
.expect("property revision exhausted");
let value = Arc::new(value);
let previous = {
let mut current = lock(&self.value);
std::mem::replace(&mut *current, Snapshot { revision, value })
};
drop(writer);
drop(mutation);
drop(previous);
drop(before);
self.notify(origin);
(true, revision)
}
fn notify(&self, origin: Option<u64>) {
let subscribers: Vec<_> = {
let mut subscribers = lock(&self.subscribers);
subscribers.retain(|_, inbox| inbox.strong_count() != 0);
subscribers
.iter()
.filter(|(id, _)| Some(**id) != origin)
.filter_map(|(_, inbox)| inbox.upgrade())
.collect()
};
for inbox in subscribers {
inbox.publish(self.id);
}
}
pub fn publish_local(&self, value: T, graph: u64) -> u64 {
self.change(Some(graph), |_| Some(value)).1
}
pub fn subscribe(&self, id: u64, inbox: &Arc<GraphInbox>) {
lock(&self.subscribers).insert(id, Arc::downgrade(inbox));
}
pub fn unsubscribe(&self, id: u64) {
lock(&self.subscribers).remove(&id);
}
}
pub struct Property<T> {
pub(super) cell: Arc<SharedCell<T>>,
}
impl<T> Clone for Property<T> {
fn clone(&self) -> Self {
Self {
cell: self.cell.clone(),
}
}
}
impl<T: SharedPropertyValue> Property<T> {
pub fn new(value: T) -> Self {
Self {
cell: Arc::new(SharedCell {
id: next_id(),
value: Mutex::new(Snapshot {
revision: 0,
value: Arc::new(value),
}),
writer: Mutex::new(()),
subscribers: Mutex::new(HashMap::new()),
}),
}
}
pub fn published(&self) -> Published<T> {
Published(self.clone())
}
pub fn get(&self) -> T {
self.read(Clone::clone)
}
pub fn read<R>(&self, f: impl FnOnce(&T) -> R) -> R {
let snapshot = self.cell.snapshot();
f(&snapshot.value)
}
pub fn observed_revision(&self) -> Option<u64> {
with_current_graph(|graph| graph.observed_revision(self))
}
pub fn set(&self, value: T) {
self.cell.change(None, |_| Some(value));
}
pub(crate) fn publish_guarded(
&self,
value: T,
authority: &crate::async_runtime::Authority,
) -> Result<(), crate::async_runtime::Closed<T>> {
let mutation = MutationGuard::enter();
let Some(authority) = authority.acquire() else {
return Err(crate::async_runtime::Closed(value));
};
let writer = lock(&self.cell.writer);
let previous = {
let mut current = lock(&self.cell.value);
let revision = current
.revision
.checked_add(1)
.expect("property revision exhausted");
std::mem::replace(
&mut *current,
Snapshot {
revision,
value: Arc::new(value),
},
)
};
drop(writer);
drop(authority);
drop(mutation);
drop(previous);
self.cell.notify(None);
Ok(())
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
self.cell.change(None, |old| {
let mut draft = old.clone();
f(&mut draft);
Some(draft)
});
}
pub fn set_if_neq(&self, value: T) -> bool
where
T: PartialEq,
{
self.cell
.change(None, |old| (old != &value).then_some(value))
.0
}
pub fn revision(&self) -> u64 {
self.cell.snapshot().revision
}
pub fn local(&self) -> LocalProperty<T> {
with_current_graph(|graph| graph.attach(self))
}
pub fn untyped(&self) -> UntypedProperty {
self.local().untyped()
}
pub fn replace_with(&self, source: LocalProperty<T>) {
let local = self.local();
local.replace_with(source);
}
}
impl<T: SharedPropertyValue> Default for Property<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: SharedPropertyValue + std::fmt::Debug> std::fmt::Debug for Property<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.read(|value| f.debug_tuple("Property").field(value).finish())
}
}
impl<T: SharedPropertyValue> Interpolatable for Property<T> {
fn interpolate(&self, other: &Self, t: f64) -> Self {
Self::new(self.get().interpolate(&other.get(), t))
}
}
impl<T: SharedPropertyValue + Serialize> Serialize for Property<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.read(|value| value.serialize(serializer))
}
}
impl<'de, T: SharedPropertyValue + Deserialize<'de>> Deserialize<'de> for Property<T> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
T::deserialize(deserializer).map(Self::new)
}
}
impl<T: SharedPropertyValue> PropertyBinding<T> for Property<T> {
fn from_value(value: T) -> Self {
Self::new(value)
}
fn local(&self) -> LocalProperty<T> {
self.local()
}
fn from_local(source: LocalProperty<T>) -> Self {
with_current_graph(|graph| graph.share(source))
}
fn value_for_conversion(&self) -> T {
if super::GRAPH_CONVERSION.with(Cell::get) {
self.local().get()
} else {
self.get()
}
}
}
#[derive(Clone)]
pub struct Published<T>(Property<T>);
impl<T: SharedPropertyValue> Published<T> {
pub fn get(&self) -> T {
self.0.get()
}
pub fn read<R>(&self, callback: impl FnOnce(&T) -> R) -> R {
self.0.read(callback)
}
pub fn revision(&self) -> u64 {
self.0.revision()
}
pub fn local(&self) -> LocalProperty<T> {
let source = self.0.local();
let deps = [source.untyped()];
LocalProperty::computed(move || source.get(), &deps)
}
}