/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ /*! Property binding engine. The current implementation uses lots of heap allocation but that can be optimized later using thin dst container, and intrusive linked list */ #![allow(unsafe_code)] #![warn(missing_docs)] mod single_linked_list_pin { #![allow(unsafe_code)] ///! A singled linked list whose nodes are pinned use core::pin::Pin; type NodePtr = Option>>>; struct SingleLinkedListPinNode { next: NodePtr, value: T, } pub struct SingleLinkedListPinHead(NodePtr); impl Default for SingleLinkedListPinHead { fn default() -> Self { Self(None) } } impl SingleLinkedListPinHead { pub fn push_front(&mut self, value: T) -> Pin<&T> { self.0 = Some(Box::pin(SingleLinkedListPinNode { next: self.0.take(), value })); // Safety: we can project from SingleLinkedListPinNode unsafe { Pin::new_unchecked(&self.0.as_ref().unwrap().value) } } #[allow(unused)] pub fn iter<'a>(&'a self) -> impl Iterator> + 'a { struct I<'a, T>(&'a NodePtr); impl<'a, T> Iterator for I<'a, T> { type Item = Pin<&'a T>; fn next(&mut self) -> Option { if let Some(x) = &self.0 { let r = unsafe { Pin::new_unchecked(&x.value) }; self.0 = &x.next; Some(r) } else { None } } } I(&self.0) } } #[test] fn test_list() { let mut head = SingleLinkedListPinHead::default(); head.push_front(1); head.push_front(2); head.push_front(3); assert_eq!( head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::>(), vec![3, 2, 1] ); } } use core::cell::{Cell, RefCell, UnsafeCell}; use core::{marker::PhantomPinned, pin::Pin}; use crate::graphics::Color; use crate::items::PropertyAnimation; /// The return value of a binding #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum BindingResult { /// The binding is a normal binding, and we keep it to re-evaluate it ince it is dirty KeepBinding, /// The value of the property is now constant after the binding was evaluated, so /// the binding can be removed. RemoveBinding, } struct BindingVTable { drop: unsafe fn(_self: *mut BindingHolder), evaluate: unsafe fn(_self: *mut BindingHolder, value: *mut ()) -> BindingResult, mark_dirty: unsafe fn(_self: *const BindingHolder), } /// A binding trait object can be used to dynamically produces values for a property. trait BindingCallable { /// This function is called by the property to evaluate the binding and produce a new value. The /// previous property value is provided in the value parameter. unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult; /// This function is used to notify the binding that one of the dependencies was changed /// and therefore this binding may evaluate to a different value, too. fn mark_dirty(self: Pin<&Self>) {} } impl BindingResult> BindingCallable for F { unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { self(value) } } scoped_tls_hkt::scoped_thread_local!(static CURRENT_BINDING : for<'a> Pin<&'a BindingHolder>); #[repr(C)] struct BindingHolder { /// Access to the list of binding which depends on this binding dependencies: Cell, /// The binding own the nodes used in the dependencies lists of the properties /// From which we depend. dep_nodes: RefCell>, vtable: &'static BindingVTable, /// The binding is dirty and need to be re_evaluated dirty: Cell, pinned: PhantomPinned, binding: B, } fn alloc_binding_holder(binding: B) -> *mut BindingHolder { /// Safety: _self must be a pointer that comes from a `Box>::into_raw()` unsafe fn binding_drop(_self: *mut BindingHolder) { Box::from_raw(_self as *mut BindingHolder); } /// Safety: _self must be a pointer to a `BindingHolder` /// and value must be a pointer to T unsafe fn evaluate( _self: *mut BindingHolder, value: *mut (), ) -> BindingResult { let pinned_holder = Pin::new_unchecked(&*_self); CURRENT_BINDING.set(pinned_holder, || { Pin::new_unchecked(&((*(_self as *mut BindingHolder)).binding)).evaluate(value) }) } /// Safety: _self must be a pointer to a `BindingHolder` unsafe fn mark_dirty(_self: *const BindingHolder) { Pin::new_unchecked(&((*(_self as *const BindingHolder)).binding)).mark_dirty() } trait HasBindingVTable { const VT: &'static BindingVTable; } impl HasBindingVTable for B { const VT: &'static BindingVTable = &BindingVTable { drop: binding_drop::, evaluate: evaluate::, mark_dirty: mark_dirty::, }; } let holder: BindingHolder = BindingHolder { dependencies: Cell::new(0), dep_nodes: Default::default(), vtable: ::VT, dirty: Cell::new(true), // starts dirty so it evaluates the property when used pinned: PhantomPinned, binding, }; Box::into_raw(Box::new(holder)) as *mut BindingHolder } #[repr(transparent)] struct DependencyListHead(Cell); impl DependencyListHead { unsafe fn mem_move(from: *mut Self, to: *mut Self) { (*to).0.set((*from).0.get()); if let Some(next) = ((*from).0.get() as *const DependencyNode).as_ref() { next.debug_assert_valid(); next.prev.set(to as *const _); next.debug_assert_valid(); } } unsafe fn drop(_self: *mut Self) { if let Some(next) = ((*_self).0.get() as *const DependencyNode).as_ref() { next.debug_assert_valid(); next.prev.set(core::ptr::null()); next.debug_assert_valid(); } } unsafe fn append(_self: *mut Self, node: *const DependencyNode) { (*node).debug_assert_valid(); let old = (*_self).0.get() as *const DependencyNode; old.as_ref().map(|x| x.debug_assert_valid()); (*_self).0.set(node as usize); let node = &*node; node.next.set(old); node.prev.set(_self as *const _); if let Some(old) = old.as_ref() { old.prev.set((&node.next) as *const _); old.debug_assert_valid(); } (*node).debug_assert_valid(); } } /// The node is owned by the binding; so the binding is always valid /// The next and pref struct DependencyNode { next: Cell<*const DependencyNode>, /// This is either null, or a pointer to a pointer to ourself prev: Cell<*const Cell<*const DependencyNode>>, binding: *const BindingHolder, } impl DependencyNode { fn for_binding(binding: Pin<&BindingHolder>) -> Self { Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding: binding.get_ref() as *const _, } } /// Assert that the invariant of `next` and `prev` are met. fn debug_assert_valid(&self) { unsafe { debug_assert!( self.prev.get().is_null() || (*self.prev.get()).get() == self as *const DependencyNode ); debug_assert!( self.next.get().is_null() || (*self.next.get()).prev.get() == (&self.next) as *const Cell<*const DependencyNode> ); // infinite loop? debug_assert_ne!(self.next.get(), self as *const DependencyNode); debug_assert_ne!(self.prev.get(), (&self.next) as *const Cell<*const DependencyNode>); } } fn remove(&self) { self.debug_assert_valid(); unsafe { if let Some(prev) = self.prev.get().as_ref() { prev.set(self.next.get()); } if let Some(next) = self.next.get().as_ref() { next.prev.set(self.prev.get()); next.debug_assert_valid(); } } self.prev.set(std::ptr::null()); self.next.set(std::ptr::null()); } } impl Drop for DependencyNode { fn drop(&mut self) { self.remove(); } } #[repr(transparent)] #[derive(Debug, Default)] struct PropertyHandle { /// The handle can either be a pointer to a binding, or a pointer to the list of dependent properties. /// The two least significant bit of the pointer are flags, as the pointer will be aligned. /// The least significant bit (`0b01`) tells that the binding is borrowed. So no two reference to the /// binding exist at the same time. /// The decond to last bit (`0b10`) tells that the pointer points to a binding. Otherwise, it is the head /// node of the linked list of dependent binding handle: Cell, } impl PropertyHandle { /// The lock flag specify that we can get reference to the Cell or unsafe cell fn lock_flag(&self) -> bool { self.handle.get() & 0b1 == 1 } /// Sets the lock_flag. /// Safety: the lock flag must not be unsat if there exist reference to what's inside the cell unsafe fn set_lock_flag(&self, set: bool) { self.handle.set(if set { self.handle.get() | 0b1 } else { self.handle.get() & !0b1 }) } /// Access the value. /// Panics if the function try to recursively access the value fn access(&self, f: impl FnOnce(Option>) -> R) -> R { assert!(!self.lock_flag(), "Recursion detected"); unsafe { self.set_lock_flag(true); let handle = self.handle.get(); let binding = if handle & 0b10 == 0b10 { Some(Pin::new_unchecked(&mut *((handle & !0b11) as *mut BindingHolder))) } else { None }; let r = f(binding); self.set_lock_flag(false); r } } fn remove_binding(&self) { assert!(!self.lock_flag(), "Recursion detected"); let val = self.handle.get(); if val & 0b10 == 0b10 { unsafe { self.set_lock_flag(true); let binding = (val & !0b11) as *mut BindingHolder; DependencyListHead::mem_move( (&mut (*binding).dependencies) as *mut _ as *mut _, self.handle.as_ptr() as *mut _, ); ((*binding).vtable.drop)(binding); } debug_assert!(self.handle.get() & 0b11 == 0); } } fn set_binding(&self, binding: B) { self.remove_binding(); let binding = alloc_binding_holder::(binding); debug_assert!((binding as usize) & 0b11 == 0); debug_assert!(self.handle.get() & 0b11 == 0); unsafe { DependencyListHead::mem_move( self.handle.as_ptr() as *mut _, (&mut (*binding).dependencies) as *mut _ as *mut _, ); self.handle.set((binding as usize) | 0b10); } } fn dependencies(&self) -> *mut DependencyListHead { assert!(!self.lock_flag(), "Recursion detected"); if (self.handle.get() & 0b10) != 0 { self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead) } else { self.handle.as_ptr() as *mut DependencyListHead } } // `value` is the content of the unsafe cell and will be only dereferenced if the // handle is not locked. (Upholding the requirements of UnsafeCell) unsafe fn update(&self, value: *mut T) { let remove = self.access(|binding| { if let Some(mut binding) = binding { if binding.dirty.get() { // clear all the nodes so that we can start from scratch *binding.dep_nodes.borrow_mut() = Default::default(); let r = (binding.vtable.evaluate)( binding.as_mut().get_unchecked_mut() as *mut BindingHolder, value as *mut (), ); binding.dirty.set(false); if r == BindingResult::RemoveBinding { return true; } } } false }); if remove { self.remove_binding() } } /// Register this property as a dependency to the current binding being evaluated fn register_as_dependency_to_current_binding(&self) { if CURRENT_BINDING.is_set() { CURRENT_BINDING.with(|cur_binding| { let node = DependencyNode::for_binding(cur_binding); let mut dep_nodes = cur_binding.dep_nodes.borrow_mut(); let node = dep_nodes.push_front(node); unsafe { DependencyListHead::append(self.dependencies(), node.get_ref() as *const _) } }); } } fn mark_dirty(&self) { unsafe { mark_dependencies_dirty(self.dependencies()) }; } } impl Drop for PropertyHandle { fn drop(&mut self) { self.remove_binding(); debug_assert!(self.handle.get() & 0b11 == 0); unsafe { DependencyListHead::drop(self.handle.as_ptr() as *mut _); } } } /// Safety: the dependency list must be valid and consistant unsafe fn mark_dependencies_dirty(deps: *mut DependencyListHead) { let mut next = (*deps).0.get() as *const DependencyNode; while let Some(node) = next.as_ref() { node.debug_assert_valid(); next = node.next.get(); let binding = &*node.binding; binding.dirty.set(true); (binding.vtable.mark_dirty)(node.binding); mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead) } } /// Types that can be set as bindings for a Property pub trait Binding { /// Evaluate the binding and return the new value fn evaluate(self: &Self, old_value: &T) -> T; } impl T> Binding for F { fn evaluate(self: &Self, _value: &T) -> T { self() } } /// A Property that allow binding that track changes /// /// Property van have be assigned value, or bindings. /// When a binding is assigned, it is lazily evaluated on demand /// when calling `get()`. /// When accessing another property from a binding evaluation, /// a dependency will be registered, such that when the property /// change, the binding will automatically be updated #[repr(C)] #[derive(Debug)] pub struct Property { /// This is usually a pointer, but the least significant bit tells what it is handle: PropertyHandle, /// This is only safe to access when the lock flag is not set on the handle. value: UnsafeCell, pinned: PhantomPinned, } impl Default for Property { fn default() -> Self { Self { handle: Default::default(), value: Default::default(), pinned: PhantomPinned } } } impl Property { /// Create a new property with this value pub fn new(value: T) -> Self { Self { handle: Default::default(), value: UnsafeCell::new(value), pinned: PhantomPinned } } /// Get the value of the property /// /// This may evaluate the binding if there is a binding and it is dirty /// /// If the function is called directly or indirectly from a binding evaluation /// of another Property, a dependency will be registered. /// /// Panics if this property is get while evaluating its own binding or /// cloning the value. pub fn get(self: Pin<&Self>) -> T { unsafe { self.handle.update(self.value.get()) }; self.handle.register_as_dependency_to_current_binding(); self.get_internal() } /// Same as get() but without registering a dependency /// /// This allow to optimize bindings that know that they might not need to /// re_evaluate themself when the property change or that have registered /// the dependency in another way. /// /// ## Example /// ``` /// use std::rc::Rc; /// use sixtyfps_corelib::Property; /// let prop1 = Rc::pin(Property::new(100)); /// let prop2 = Rc::pin(Property::::default()); /// prop2.as_ref().set_binding({ /// let prop1 = prop1.clone(); // in order to move it into the closure. /// move || { prop1.as_ref().get_untracked() + 30 } /// }); /// assert_eq!(prop2.as_ref().get(), 130); /// prop1.set(200); /// // changing prop1 do not affect the prop2 binding because no dependency was registered /// assert_eq!(prop2.as_ref().get(), 130); /// ``` pub fn get_untracked(self: Pin<&Self>) -> T { unsafe { self.handle.update(self.value.get()) }; self.get_internal() } /// Get the value without registering any dependencies or executing any binding fn get_internal(&self) -> T { self.handle.access(|_| { // Safety: PropertyHandle::access ensure that the value is locked unsafe { (*self.value.get()).clone() } }) } /// Change the value of this property /// /// If other properties have binding depending of this property, these properties will /// be marked as dirty. // FIXME pub fn set(self: Pin<&Self>, t: T) { pub fn set(&self, t: T) { self.handle.remove_binding(); // Safety: PropertyHandle::access ensure that the value is locked self.handle.access(|_| unsafe { *self.value.get() = t }); self.handle.mark_dirty(); } /// Set a binding to this property. /// /// Bindings are evaluated lazily from calling get, and the return value of the binding /// is the new value. /// /// If other properties have bindings depending of this property, these properties will /// be marked as dirty. /// /// Clausures of type `Fn()->T` implements Binding and can be used as a binding /// /// ## Example /// ``` /// use std::rc::Rc; /// use sixtyfps_corelib::Property; /// let prop1 = Rc::pin(Property::new(100)); /// let prop2 = Rc::pin(Property::::default()); /// prop2.as_ref().set_binding({ /// let prop1 = prop1.clone(); // in order to move it into the closure. /// move || { prop1.as_ref().get() + 30 } /// }); /// assert_eq!(prop2.as_ref().get(), 130); /// prop1.set(200); /// // A change in prop1 forced the binding on prop2 to re_evaluate /// assert_eq!(prop2.as_ref().get(), 230); /// ``` //FIXME pub fn set_binding(self: Pin<&Self>, f: impl Binding + 'static) { pub fn set_binding(&self, binding: impl Binding + 'static) { self.handle.set_binding(move |val: *mut ()| unsafe { let val = &mut *(val as *mut T); *val = binding.evaluate(val); BindingResult::KeepBinding }); self.handle.mark_dirty(); } /// Any of the properties accessed during the last evaluation of the closure called /// from the last call to evaluate is pottentially dirty. pub fn is_dirty(&self) -> bool { self.handle.access(|binding| binding.map_or(false, |b| b.dirty.get())) } } impl Property { /// Change the value of this property, by animating (interpolating) from the current property's value /// to the specified parameter value. The animation is done according to the parameters described by /// the PropertyAnimation object. /// /// If other properties have binding depending of this property, these properties will /// be marked as dirty. pub fn set_animated_value(&self, value: T, animation_data: &PropertyAnimation) { // FIXME if the current value is a dirty binding, we must run it, but we do not have the context let d = RefCell::new(PropertyValueAnimationData::new( self.get_internal(), value, animation_data.clone(), )); self.handle.set_binding(move |val: *mut ()| unsafe { let (value, finished) = d.borrow_mut().compute_interpolated_value(); *(val as *mut T) = value; if finished { BindingResult::RemoveBinding } else { crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.set_has_active_animations()); BindingResult::KeepBinding } }); } /// Set a binding to this property. /// pub fn set_animated_binding( &self, binding: impl Binding + 'static, animation_data: &PropertyAnimation, ) { self.handle.set_binding(AnimatedBindingCallable:: { original_binding: PropertyHandle { handle: Cell::new( (alloc_binding_holder(move |val: *mut ()| unsafe { let val = &mut *(val as *mut T); *(val as *mut T) = binding.evaluate(val); BindingResult::KeepBinding }) as usize) | 0b10, ), }, state: Cell::new(AnimatedBindingState::NotAnimating), animation_data: RefCell::new(PropertyValueAnimationData::new( T::default(), T::default(), animation_data.clone(), )), }); self.handle.mark_dirty(); } } struct PropertyValueAnimationData { from_value: T, to_value: T, details: PropertyAnimation, start_time: instant::Instant, loop_iteration: i32, } impl PropertyValueAnimationData { fn new(from_value: T, to_value: T, details: PropertyAnimation) -> Self { let start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); Self { from_value, to_value, details, start_time, loop_iteration: 0 } } fn compute_interpolated_value(&mut self) -> (T, bool) { let duration = self.details.duration as u128; let new_tick = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); let mut time_progress = new_tick.duration_since(self.start_time).as_millis(); if time_progress >= duration { if self.loop_iteration < self.details.loop_count || self.details.loop_count < 0 { self.loop_iteration += (time_progress / duration) as i32; time_progress = time_progress % duration; self.start_time = new_tick - std::time::Duration::from_millis(time_progress as u64); } else { return (self.to_value.clone(), true); } } let progress = time_progress as f32 / self.details.duration as f32; assert!(progress <= 1.); let t = crate::animations::easing_curve(&self.details.easing, progress); let val = self.from_value.interpolate(self.to_value, t); (val, false) } } #[derive(Clone, Copy, Eq, PartialEq, Debug)] enum AnimatedBindingState { Animating, NotAnimating, ShouldStart, } struct AnimatedBindingCallable { original_binding: PropertyHandle, state: Cell, animation_data: RefCell>, } impl BindingCallable for AnimatedBindingCallable { unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { self.original_binding.register_as_dependency_to_current_binding(); match self.state.get() { AnimatedBindingState::Animating => { let (val, finished) = self.animation_data.borrow_mut().compute_interpolated_value(); *(value as *mut T) = val; if finished { self.state.set(AnimatedBindingState::NotAnimating) } else { crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.set_has_active_animations()); } } AnimatedBindingState::NotAnimating => { self.original_binding.update(value); } AnimatedBindingState::ShouldStart => { let value = &mut *(value as *mut T); self.state.set(AnimatedBindingState::Animating); let mut animation_data = self.animation_data.borrow_mut(); animation_data.from_value = value.clone(); self.original_binding.update((&mut animation_data.to_value) as *mut T as *mut ()); let (val, finished) = animation_data.compute_interpolated_value(); *value = val; if finished { self.state.set(AnimatedBindingState::NotAnimating) } else { crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.set_has_active_animations()); } } }; BindingResult::KeepBinding } fn mark_dirty(self: Pin<&Self>) { if self.state.get() == AnimatedBindingState::ShouldStart { return; } let original_dirty = self.original_binding.access(|b| b.unwrap().dirty.get()); if original_dirty { self.state.set(AnimatedBindingState::ShouldStart); self.animation_data.borrow_mut().start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); } } } #[test] fn properties_simple_test() { use pin_weak::rc::PinWeak; use std::rc::Rc; fn g(prop: &Property) -> i32 { unsafe { Pin::new_unchecked(prop).get() } } #[derive(Default)] struct Component { width: Property, height: Property, area: Property, } let compo = Rc::pin(Component::default()); let w = PinWeak::downgrade(compo.clone()); compo.area.set_binding(move || { let compo = w.upgrade().unwrap(); g(&compo.width) * g(&compo.height) }); compo.width.set(4); compo.height.set(8); assert_eq!(g(&compo.width), 4); assert_eq!(g(&compo.height), 8); assert_eq!(g(&compo.area), 4 * 8); let w = PinWeak::downgrade(compo.clone()); compo.width.set_binding(move || { let compo = w.upgrade().unwrap(); g(&compo.height) * 2 }); assert_eq!(g(&compo.width), 8 * 2); assert_eq!(g(&compo.height), 8); assert_eq!(g(&compo.area), 8 * 8 * 2); } /// InterpolatedPropertyValue is a trait used to enable properties to be used with /// animations that interpolate values. The basic requirement is the ability to apply /// a progress that's typically between 0 and 1 to a range. pub trait InterpolatedPropertyValue: PartialEq + Clone + Copy + std::fmt::Display + Default + 'static { /// Returns the interpolated value between self and target_value according to the /// progress parameter t that's usually between 0 and 1. With certain animation /// easing curves it may over- or undershoot though. fn interpolate(self, target_value: Self, t: f32) -> Self; } impl InterpolatedPropertyValue for f32 { fn interpolate(self, target_value: Self, t: f32) -> Self { self + t * (target_value - self) } } impl InterpolatedPropertyValue for i32 { fn interpolate(self, target_value: Self, t: f32) -> Self { self + (t * (target_value - self) as f32) as i32 } } impl InterpolatedPropertyValue for i64 { fn interpolate(self, target_value: Self, t: f32) -> Self { self + (t * (target_value - self) as f32) as Self } } impl InterpolatedPropertyValue for u8 { fn interpolate(self, target_value: Self, t: f32) -> Self { ((self as f32) + (t * ((target_value as f32) - (self as f32)))).min(255.).max(0.) as u8 } } #[cfg(test)] mod animation_tests { use super::*; use crate::items::PropertyAnimation; use std::rc::Rc; #[derive(Default)] struct Component { width: Property, width_times_two: Property, feed_property: Property, // used by binding to feed values into width } impl Component { fn new_test_component() -> Rc { let compo = Rc::new(Component::default()); let w = Rc::downgrade(&compo); compo.width_times_two.set_binding(move || { let compo = w.upgrade().unwrap(); get_prop_value(&compo.width) * 2 }); compo } } const DURATION: instant::Duration = instant::Duration::from_millis(10000); // Helper just for testing fn get_prop_value(prop: &Property) -> T { unsafe { Pin::new_unchecked(prop).get() } } #[test] fn properties_test_animation_triggered_by_set() { let compo = Component::new_test_component(); let animation_details = PropertyAnimation { duration: DURATION.as_millis() as _, ..PropertyAnimation::default() }; compo.width.set(100); assert_eq!(get_prop_value(&compo.width), 100); assert_eq!(get_prop_value(&compo.width_times_two), 200); let start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); compo.width.set_animated_value(200, &animation_details); assert_eq!(get_prop_value(&compo.width), 100); assert_eq!(get_prop_value(&compo.width_times_two), 200); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); assert_eq!(get_prop_value(&compo.width_times_two), 300); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION)); assert_eq!(get_prop_value(&compo.width), 200); assert_eq!(get_prop_value(&compo.width_times_two), 400); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION * 2)); assert_eq!(get_prop_value(&compo.width), 200); assert_eq!(get_prop_value(&compo.width_times_two), 400); // the binding should be removed compo.width.handle.access(|binding| assert!(binding.is_none())); } #[test] fn properties_test_animation_triggered_by_binding() { let compo = Component::new_test_component(); let start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); let animation_details = PropertyAnimation { duration: DURATION.as_millis() as _, ..PropertyAnimation::default() }; let w = Rc::downgrade(&compo); compo.width.set_animated_binding( move || { let compo = w.upgrade().unwrap(); get_prop_value(&compo.feed_property) }, &animation_details, ); compo.feed_property.set(100); assert_eq!(get_prop_value(&compo.width), 100); assert_eq!(get_prop_value(&compo.width_times_two), 200); compo.feed_property.set(200); assert_eq!(get_prop_value(&compo.width), 100); assert_eq!(get_prop_value(&compo.width_times_two), 200); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); assert_eq!(get_prop_value(&compo.width_times_two), 300); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION)); assert_eq!(get_prop_value(&compo.width), 200); assert_eq!(get_prop_value(&compo.width_times_two), 400); } #[test] fn test_loop() { let compo = Component::new_test_component(); let animation_details = PropertyAnimation { duration: DURATION.as_millis() as _, loop_count: 2, ..PropertyAnimation::default() }; compo.width.set(100); let start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); compo.width.set_animated_value(200, &animation_details); assert_eq!(get_prop_value(&compo.width), 100); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION)); assert_eq!(get_prop_value(&compo.width), 100); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION * 2)); assert_eq!(get_prop_value(&compo.width), 100); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION * 2 + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION * 3)); assert_eq!(get_prop_value(&compo.width), 200); // the binding should be removed compo.width.handle.access(|binding| assert!(binding.is_none())); } #[test] fn test_loop_overshoot() { let compo = Component::new_test_component(); let animation_details = PropertyAnimation { duration: DURATION.as_millis() as _, loop_count: 2, ..PropertyAnimation::default() }; compo.width.set(100); let start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); compo.width.set_animated_value(200, &animation_details); assert_eq!(get_prop_value(&compo.width), 100); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION * 2 + DURATION / 2)); assert_eq!(get_prop_value(&compo.width), 150); crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.update_animations(start_time + DURATION * 3)); assert_eq!(get_prop_value(&compo.width), 200); // the binding should be removed compo.width.handle.access(|binding| assert!(binding.is_none())); } } /// This structure allow to run a closure that queries properties, and can report /// if any property we accessed have become dirty pub struct PropertyTracker { holder: BindingHolder<()>, } impl Default for PropertyTracker { fn default() -> Self { static VT: &'static BindingVTable = &BindingVTable { drop: |_| (), evaluate: |_, _| BindingResult::KeepBinding, mark_dirty: |_| (), }; let holder = BindingHolder { dependencies: Cell::new(0), dep_nodes: Default::default(), vtable: VT, dirty: Cell::new(true), // starts dirty so it evaluates the property when used pinned: PhantomPinned, binding: (), }; Self { holder } } } impl PropertyTracker { /// Any of the properties accessed during the last evaluation of the closure called /// from the last call to evaluate is pottentially dirty. pub fn is_dirty(&self) -> bool { self.holder.dirty.get() } /// Evaluate the function, and record dependencies of properties accessed whithin this function. pub fn evaluate(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { // clear all the nodes so that we can start from scratch *self.holder.dep_nodes.borrow_mut() = Default::default(); // Safety: it is safe to project the holder as we don't implement drop or unpin let pinned_holder = unsafe { self.map_unchecked(|s| &s.holder) }; let r = CURRENT_BINDING.set(pinned_holder, f); self.holder.dirty.set(false); r } /// Mark this PropertyTracker as dirty pub fn set_dirty(&self) { self.holder.dirty.set(true); } } #[test] fn test_property_listener_scope() { let scope = Box::pin(PropertyTracker::default()); let prop1 = Box::pin(Property::new(42)); assert!(scope.is_dirty()); // It is dirty at the beginning let r = scope.as_ref().evaluate(|| prop1.as_ref().get()); assert_eq!(r, 42); assert!(!scope.is_dirty()); // It is no longer dirty prop1.as_ref().set(88); assert!(scope.is_dirty()); // now dirty for prop1 changed. let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1); assert_eq!(r, 89); assert!(!scope.is_dirty()); let r = scope.as_ref().evaluate(|| 12); assert_eq!(r, 12); assert!(!scope.is_dirty()); prop1.as_ref().set(1); assert!(!scope.is_dirty()); } pub(crate) mod ffi { use super::*; #[allow(non_camel_case_types)] type c_void = (); #[repr(C)] /// Has the same layout as PropertyHandle pub struct PropertyHandleOpaque(PropertyHandle); /// Initialize the first pointer of the Property. Does not initialize the content. /// `out` is assumed to be uninitialized #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_init(out: *mut PropertyHandleOpaque) { core::ptr::write(out, PropertyHandleOpaque(PropertyHandle::default())); } /// To be called before accessing the value #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_update( handle: &PropertyHandleOpaque, val: *mut c_void, ) { handle.0.update(val); handle.0.register_as_dependency_to_current_binding(); } /// Mark the fact that the property was changed and that its binding need to be removed, and /// The dependencies marked dirty #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_changed(handle: &PropertyHandleOpaque) { handle.0.remove_binding(); handle.0.mark_dirty(); } fn make_c_function_binding( binding: extern "C" fn(*mut c_void, *mut c_void), user_data: *mut c_void, drop_user_data: Option, ) -> impl Fn(*mut ()) -> BindingResult { struct CFunctionBinding { binding_function: extern "C" fn(*mut c_void, *mut T), user_data: *mut c_void, drop_user_data: Option, } impl Drop for CFunctionBinding { fn drop(&mut self) { if let Some(x) = self.drop_user_data { x(self.user_data) } } } let b = CFunctionBinding { binding_function: binding, user_data, drop_user_data }; move |value_ptr| { (b.binding_function)(b.user_data, value_ptr); BindingResult::KeepBinding } } /// Set a binding /// /// The current implementation will do usually two memory alocation: /// 1. the allocation from the calling code to allocate user_data /// 2. the box allocation within this binding /// It might be possible to reduce that by passing something with a /// vtable, so there is the need for less memory allocation. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_binding( handle: &PropertyHandleOpaque, binding: extern "C" fn(user_data: *mut c_void, pointer_to_value: *mut c_void), user_data: *mut c_void, drop_user_data: Option, ) { let binding = make_c_function_binding(binding, user_data, drop_user_data); handle.0.set_binding(binding); } /// Returns whether the property behind this handle is marked as dirty #[no_mangle] pub extern "C" fn sixtyfps_property_is_dirty(handle: &PropertyHandleOpaque) -> bool { handle.0.access(|binding| binding.map_or(false, |b| b.dirty.get())) } /// Destroy handle #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_drop(handle: *mut PropertyHandleOpaque) { core::ptr::read(handle); } fn c_set_animated_value( handle: &PropertyHandleOpaque, from: T, to: T, animation_data: &PropertyAnimation, ) { let d = RefCell::new(PropertyValueAnimationData::new(from, to, animation_data.clone())); handle.0.set_binding(move |val: *mut ()| { let (value, finished) = d.borrow_mut().compute_interpolated_value(); unsafe { *(val as *mut T) = value; } if finished { BindingResult::RemoveBinding } else { crate::animations::CURRENT_ANIMATION_DRIVER .with(|driver| driver.set_has_active_animations()); BindingResult::KeepBinding } }); } /// Internal function to set up a property animation to the specified target value for an integer property. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_animated_value_int( handle: &PropertyHandleOpaque, from: i32, to: i32, animation_data: &PropertyAnimation, ) { c_set_animated_value(handle, from, to, animation_data) } /// Internal function to set up a property animation to the specified target value for a float property. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_animated_value_float( handle: &PropertyHandleOpaque, from: f32, to: f32, animation_data: &PropertyAnimation, ) { c_set_animated_value(handle, from, to, animation_data) } /// Internal function to set up a property animation to the specified target value for a color property. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_animated_value_color( handle: &PropertyHandleOpaque, from: Color, to: Color, animation_data: &PropertyAnimation, ) { c_set_animated_value(handle, from, to, animation_data); } unsafe fn c_set_animated_binding( handle: &PropertyHandleOpaque, binding: extern "C" fn(*mut c_void, *mut T), user_data: *mut c_void, drop_user_data: Option, animation_data: &PropertyAnimation, ) { let binding = core::mem::transmute::< extern "C" fn(*mut c_void, *mut T), extern "C" fn(*mut c_void, *mut ()), >(binding); handle.0.set_binding(AnimatedBindingCallable:: { original_binding: PropertyHandle { handle: Cell::new( (alloc_binding_holder(make_c_function_binding( binding, user_data, drop_user_data, )) as usize) | 0b10, ), }, state: Cell::new(AnimatedBindingState::NotAnimating), animation_data: RefCell::new(PropertyValueAnimationData::new( T::default(), T::default(), animation_data.clone(), )), }); handle.0.mark_dirty(); } /// Internal function to set up a property animation between values produced by the specified binding for an integer property. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_animated_binding_int( handle: &PropertyHandleOpaque, binding: extern "C" fn(*mut c_void, *mut i32), user_data: *mut c_void, drop_user_data: Option, animation_data: &PropertyAnimation, ) { c_set_animated_binding(handle, binding, user_data, drop_user_data, animation_data); } /// Internal function to set up a property animation between values produced by the specified binding for a float property. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_animated_binding_float( handle: &PropertyHandleOpaque, binding: extern "C" fn(*mut c_void, *mut f32), user_data: *mut c_void, drop_user_data: Option, animation_data: &PropertyAnimation, ) { c_set_animated_binding(handle, binding, user_data, drop_user_data, animation_data); } /// Internal function to set up a property animation between values produced by the specified binding for a color property. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_set_animated_binding_color( handle: &PropertyHandleOpaque, binding: extern "C" fn(*mut c_void, *mut Color), user_data: *mut c_void, drop_user_data: Option, animation_data: &PropertyAnimation, ) { c_set_animated_binding(handle, binding, user_data, drop_user_data, animation_data); } #[repr(C)] /// Opaque type representing the PropertyTracker pub struct PropertyTrackerOpaque { dependencies: usize, dep_nodes: [usize; 2], vtable: usize, dirty: bool, } static_assertions::assert_eq_align!(PropertyTrackerOpaque, PropertyTracker); static_assertions::assert_eq_size!(PropertyTrackerOpaque, PropertyTracker); /// Initialize the first pointer of the PropertyTracker. /// `out` is assumed to be uninitialized /// sixtyfps_property_tracker_drop need to be called after that #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_tracker_init(out: *mut PropertyTrackerOpaque) { core::ptr::write(out as *mut PropertyTracker, PropertyTracker::default()); } /// Call the callback with the user data. Any properties access within the callback will be registered. #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_tracker_evaluate( handle: *const PropertyTrackerOpaque, callback: extern "C" fn(user_data: *mut c_void), user_data: *mut c_void, ) { Pin::new_unchecked(&*(handle as *const PropertyTracker)).evaluate(|| callback(user_data)) } /// Query if the property tracker is dirty #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_tracker_is_dirty( handle: *const PropertyTrackerOpaque, ) -> bool { (*(handle as *const PropertyTracker)).is_dirty() } /// Destroy handle #[no_mangle] pub unsafe extern "C" fn sixtyfps_property_tracker_drop(handle: *mut PropertyTrackerOpaque) { core::ptr::read(handle as *mut PropertyTracker); } }