diff --git a/crates/vertigo/src/computed/computed_box.rs b/crates/vertigo/src/computed/computed_box.rs index 6edac66c..41572cae 100644 --- a/crates/vertigo/src/computed/computed_box.rs +++ b/crates/vertigo/src/computed/computed_box.rs @@ -144,7 +144,7 @@ impl Computed { /// Note that the `callback` is fired only if the value *really* changes. This means that even if computation takes place with different source value, /// but the resulting value is the same as old one, the `callback` is not fired. pub fn subscribe R + 'static>(self, callback: F) -> DropResource { - let prev_value = ValueMut::new(None); + let prev_value = ValueMut::new_with_eq(None); let resource_box = ValueMut::new(None); diff --git a/crates/vertigo/src/computed/struct_mut/inner_value.rs b/crates/vertigo/src/computed/struct_mut/inner_value.rs index 9ada79b1..006669b0 100644 --- a/crates/vertigo/src/computed/struct_mut/inner_value.rs +++ b/crates/vertigo/src/computed/struct_mut/inner_value.rs @@ -1,27 +1,173 @@ #![allow(clippy::mut_from_ref)] use std::cell::UnsafeCell; +use std::marker::PhantomData; + +struct InnerValueErased { + data: *mut (), + drop_fn: unsafe fn(*mut ()), + eq_fn: Option bool>, + fmt_fn: Option) -> std::fmt::Result>, +} + +impl InnerValueErased { + fn new(value: T) -> Self { + let boxed = Box::new(UnsafeCell::new(value)); + let data = Box::into_raw(boxed) as *mut (); + + unsafe fn drop_ptr(ptr: *mut ()) { + let _ = unsafe { Box::from_raw(ptr as *mut UnsafeCell) }; + } + + Self { + data, + drop_fn: drop_ptr::, + eq_fn: None, + fmt_fn: None, + } + } + + fn new_with_eq(value: T) -> Self { + let boxed = Box::new(UnsafeCell::new(value)); + let data = Box::into_raw(boxed) as *mut (); + + unsafe fn drop_ptr(ptr: *mut ()) { + let _ = unsafe { Box::from_raw(ptr as *mut UnsafeCell) }; + } + + unsafe fn eq_ptr(ptr: *const (), other: *const ()) -> bool { + let this = unsafe { &*(ptr as *const UnsafeCell) }; + let other = unsafe { &*(other as *const T) }; + unsafe { *this.get() == *other } + } + + Self { + data, + drop_fn: drop_ptr::, + eq_fn: Some(eq_ptr::), + fmt_fn: None, + } + } + + fn new_with_eq_debug(value: T) -> Self { + let boxed = Box::new(UnsafeCell::new(value)); + let data = Box::into_raw(boxed) as *mut (); + + unsafe fn drop_ptr(ptr: *mut ()) { + let _ = unsafe { Box::from_raw(ptr as *mut UnsafeCell) }; + } + + unsafe fn eq_ptr(ptr: *const (), other: *const ()) -> bool { + let this = unsafe { &*(ptr as *const UnsafeCell) }; + let other = unsafe { &*(other as *const T) }; + unsafe { *this.get() == *other } + } + + unsafe fn fmt_ptr( + ptr: *const (), + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + let this = unsafe { &*(ptr as *const UnsafeCell) }; + unsafe { (*this.get()).fmt(f) } + } + + Self { + data, + drop_fn: drop_ptr::, + eq_fn: Some(eq_ptr::), + fmt_fn: Some(fmt_ptr::), + } + } + + unsafe fn get(&self) -> &T { + let ptr = self.data as *mut UnsafeCell; + unsafe { &*(*ptr).get() } + } + + unsafe fn get_mut(&self) -> &mut T { + let ptr = self.data as *mut UnsafeCell; + unsafe { &mut *(*ptr).get() } + } +} + +impl Drop for InnerValueErased { + fn drop(&mut self) { + unsafe { + (self.drop_fn)(self.data); + } + } +} -#[derive(Debug)] pub struct InnerValue { - value: UnsafeCell, + inner: InnerValueErased, + _phantom: PhantomData, } impl InnerValue { pub fn new(value: T) -> InnerValue { InnerValue { - value: UnsafeCell::new(value), + inner: InnerValueErased::new(value), + _phantom: PhantomData, + } + } + + pub fn new_with_eq(value: T) -> InnerValue + where + T: PartialEq, + { + InnerValue { + inner: InnerValueErased::new_with_eq(value), + _phantom: PhantomData, } } + + pub fn new_with_eq_debug(value: T) -> InnerValue + where + T: PartialEq + std::fmt::Debug, + { + InnerValue { + inner: InnerValueErased::new_with_eq_debug(value), + _phantom: PhantomData, + } + } + pub fn get(&self) -> &T { - unsafe { &*self.value.get() } + unsafe { self.inner.get::() } } pub fn get_mut(&self) -> &mut T { - unsafe { &mut *self.value.get() } + unsafe { self.inner.get_mut::() } + } + + pub fn is_eq(&self, other: &T) -> bool { + if let Some(eq_fn) = self.inner.eq_fn { + unsafe { eq_fn(self.inner.data, other as *const T as *const ()) } + } else { + // Fallback for cases where we didn't store eq_fn, but it shouldn't happen if we use ValueMut + false + } } pub fn into_inner(self) -> T { - self.value.into_inner() + let data = self.inner.data as *mut UnsafeCell; + let inner_value = unsafe { + let boxed = Box::from_raw(data); + boxed.into_inner() + }; + // Forget the erased part so it doesn't double-drop + std::mem::forget(self.inner); + inner_value + } +} + +impl std::fmt::Debug for InnerValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(fmt_fn) = self.inner.fmt_fn { + unsafe { fmt_fn(self.inner.data, f) } + } else { + f.debug_struct("InnerValue") + .field("value", self.get()) + .finish() + } } } diff --git a/crates/vertigo/src/computed/struct_mut/value_mut.rs b/crates/vertigo/src/computed/struct_mut/value_mut.rs index bbb29000..974a1f30 100644 --- a/crates/vertigo/src/computed/struct_mut/value_mut.rs +++ b/crates/vertigo/src/computed/struct_mut/value_mut.rs @@ -25,6 +25,14 @@ impl ValueMut { } } +impl ValueMut { + pub fn new_with_eq(value: T) -> ValueMut { + ValueMut { + value: InnerValue::new_with_eq(value), + } + } +} + impl Default for ValueMut { fn default() -> Self { Self { @@ -59,12 +67,11 @@ impl ValueMut { impl ValueMut { pub fn set_if_changed(&self, value: T) -> bool { - let state = self.value.get_mut(); - if *state != value { - *state = value; - true - } else { + if self.value.is_eq(&value) { false + } else { + *self.value.get_mut() = value; + true } } } diff --git a/crates/vertigo/src/computed/value_inner.rs b/crates/vertigo/src/computed/value_inner.rs index e8867fdd..b383d268 100644 --- a/crates/vertigo/src/computed/value_inner.rs +++ b/crates/vertigo/src/computed/value_inner.rs @@ -11,7 +11,7 @@ impl ValueInner { pub fn new(value: T) -> ValueInner { ValueInner { id: GraphId::new_value(), - value: ValueMut::new(value.clone()), + value: ValueMut::new_with_eq(value.clone()), events: EventEmitter::default(), } }