slint/sixtyfps_runtime/corelib/properties.rs
2020-09-23 14:06:08 +02:00

1314 lines
47 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
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<T> = Option<Pin<Box<SingleLinkedListPinNode<T>>>>;
struct SingleLinkedListPinNode<T> {
next: NodePtr<T>,
value: T,
}
pub struct SingleLinkedListPinHead<T>(NodePtr<T>);
impl<T> Default for SingleLinkedListPinHead<T> {
fn default() -> Self {
Self(None)
}
}
impl<T> SingleLinkedListPinHead<T> {
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<Item = Pin<&T>> + 'a {
struct I<'a, T>(&'a NodePtr<T>);
impl<'a, T> Iterator for I<'a, T> {
type Item = Pin<&'a T>;
fn next(&mut self) -> Option<Self::Item> {
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<i32>>(),
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<F: Fn(*mut ()) -> 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<B = ()> {
/// Access to the list of binding which depends on this binding
dependencies: Cell<usize>,
/// The binding own the nodes used in the dependencies lists of the properties
/// From which we depend.
dep_nodes: RefCell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
vtable: &'static BindingVTable,
/// The binding is dirty and need to be re_evaluated
dirty: Cell<bool>,
pinned: PhantomPinned,
binding: B,
}
fn alloc_binding_holder<B: BindingCallable + 'static>(binding: B) -> *mut BindingHolder {
/// Safety: _self must be a pointer that comes from a `Box<BindingHolder<B>>::into_raw()`
unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
Box::from_raw(_self as *mut BindingHolder<B>);
}
/// Safety: _self must be a pointer to a `BindingHolder<B>`
/// and value must be a pointer to T
unsafe fn evaluate<B: BindingCallable>(
_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<B>)).binding)).evaluate(value)
})
}
/// Safety: _self must be a pointer to a `BindingHolder<B>`
unsafe fn mark_dirty<B: BindingCallable>(_self: *const BindingHolder) {
Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty()
}
trait HasBindingVTable {
const VT: &'static BindingVTable;
}
impl<B: BindingCallable> HasBindingVTable for B {
const VT: &'static BindingVTable = &BindingVTable {
drop: binding_drop::<B>,
evaluate: evaluate::<B>,
mark_dirty: mark_dirty::<B>,
};
}
let holder: BindingHolder<B> = BindingHolder {
dependencies: Cell::new(0),
dep_nodes: Default::default(),
vtable: <B as HasBindingVTable>::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<usize>);
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<usize>,
}
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<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> 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<B: BindingCallable + 'static>(&self, binding: B) {
self.remove_binding();
let binding = alloc_binding_holder::<B>(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<T>(&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<T>
pub trait Binding<T> {
/// Evaluate the binding and return the new value
fn evaluate(self: &Self, old_value: &T) -> T;
}
impl<T, F: Fn() -> T> Binding<T> 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<T> {
/// 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<T>,
pinned: PhantomPinned,
}
impl<T: Default> Default for Property<T> {
fn default() -> Self {
Self { handle: Default::default(), value: Default::default(), pinned: PhantomPinned }
}
}
impl<T: Clone> Property<T> {
/// 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::<i32>::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<T> 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::<i32>::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<T> + 'static) {
pub fn set_binding(&self, binding: impl Binding<T> + '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<T: Clone + InterpolatedPropertyValue + 'static> Property<T> {
/// 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<T> + 'static,
animation_data: &PropertyAnimation,
) {
self.handle.set_binding(AnimatedBindingCallable::<T> {
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<T> {
from_value: T,
to_value: T,
details: PropertyAnimation,
start_time: instant::Instant,
loop_iteration: i32,
}
impl<T: InterpolatedPropertyValue> PropertyValueAnimationData<T> {
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<T> {
original_binding: PropertyHandle,
state: Cell<AnimatedBindingState>,
animation_data: RefCell<PropertyValueAnimationData<T>>,
}
impl<T: InterpolatedPropertyValue> BindingCallable for AnimatedBindingCallable<T> {
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>) -> i32 {
unsafe { Pin::new_unchecked(prop).get() }
}
#[derive(Default)]
struct Component {
width: Property<i32>,
height: Property<i32>,
area: Property<i32>,
}
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<i32>,
width_times_two: Property<i32>,
feed_property: Property<i32>, // used by binding to feed values into width
}
impl Component {
fn new_test_component() -> Rc<Self> {
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<T: Clone>(prop: &Property<T>) -> 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<R>(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<extern "C" fn(*mut c_void)>,
) -> impl Fn(*mut ()) -> BindingResult {
struct CFunctionBinding<T> {
binding_function: extern "C" fn(*mut c_void, *mut T),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
}
impl<T> Drop for CFunctionBinding<T> {
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<extern "C" fn(*mut c_void)>,
) {
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<T: InterpolatedPropertyValue>(
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<T: InterpolatedPropertyValue>(
handle: &PropertyHandleOpaque,
binding: extern "C" fn(*mut c_void, *mut T),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
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::<T> {
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<extern "C" fn(*mut c_void)>,
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<extern "C" fn(*mut c_void)>,
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<extern "C" fn(*mut c_void)>,
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);
}
}