Changing to use a Rust queue for the animations
This commit is contained in:
parent
dc877f0aa3
commit
4c25472712
|
@ -2,17 +2,15 @@
|
||||||
extern crate stdweb;
|
extern crate stdweb;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate dominator;
|
extern crate dominator;
|
||||||
#[macro_use]
|
|
||||||
extern crate signals;
|
extern crate signals;
|
||||||
|
|
||||||
use stdweb::traits::*;
|
use stdweb::traits::*;
|
||||||
use stdweb::web::{document, HtmlElement};
|
use stdweb::web::{document, HtmlElement};
|
||||||
use stdweb::web::event::{MouseDownEvent, MouseUpEvent};
|
use stdweb::web::event::{MouseDownEvent, MouseUpEvent};
|
||||||
use signals::signal::{always, Signal};
|
use signals::signal::Signal;
|
||||||
use signals::signal_vec::SignalVec;
|
|
||||||
use signals::signal_vec::unsync::MutableVec;
|
use signals::signal_vec::unsync::MutableVec;
|
||||||
use dominator::traits::*;
|
use dominator::traits::*;
|
||||||
use dominator::{text, Dom};
|
use dominator::Dom;
|
||||||
use dominator::animation::{Percentage, easing};
|
use dominator::animation::{Percentage, easing};
|
||||||
use dominator::animation::unsync::MutableAnimation;
|
use dominator::animation::unsync::MutableAnimation;
|
||||||
|
|
||||||
|
|
177
src/animation.rs
177
src/animation.rs
|
@ -1,36 +1,26 @@
|
||||||
use self::unsync::MutableAnimation;
|
use self::unsync::MutableAnimation;
|
||||||
use futures::Async;
|
use std::sync::{Arc, Weak, Mutex};
|
||||||
|
use futures::{Async, task};
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use signals::signal::{Signal, WaitFor};
|
use futures::task::Task;
|
||||||
|
use signals::signal::{Signal, State, WaitFor};
|
||||||
use signals::signal::unsync::MutableSignal;
|
use signals::signal::unsync::MutableSignal;
|
||||||
use signals::signal_vec::{SignalVec, VecChange};
|
use signals::signal_vec::{SignalVec, VecChange};
|
||||||
use stdweb::Value;
|
use stdweb::Value;
|
||||||
|
|
||||||
|
|
||||||
// TODO generalize this so it works for any target, not just JS
|
// TODO generalize this so it works for any target, not just JS
|
||||||
// TODO maybe keep a queue in Rust, so that way it only needs to call the callback once
|
|
||||||
struct Raf(Value);
|
struct Raf(Value);
|
||||||
|
|
||||||
impl Raf {
|
impl Raf {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn new<F>(callback: F) -> Self where F: FnMut(f64, f64) -> bool + 'static {
|
fn new<F>(callback: F) -> Self where F: FnMut(f64) + 'static {
|
||||||
Raf(js!(
|
Raf(js!(
|
||||||
var starting_time = null;
|
|
||||||
var callback = @{callback};
|
var callback = @{callback};
|
||||||
|
|
||||||
function loop(time) {
|
function loop(time) {
|
||||||
// TODO assign this immediately when the Raf is created ?
|
|
||||||
if (starting_time === null) {
|
|
||||||
starting_time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback(starting_time, time)) {
|
|
||||||
value.id = requestAnimationFrame(loop);
|
value.id = requestAnimationFrame(loop);
|
||||||
|
callback(time);
|
||||||
} else {
|
|
||||||
value.id = null;
|
|
||||||
callback.drop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = {
|
var value = {
|
||||||
|
@ -48,13 +38,102 @@ impl Drop for Raf {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
js! { @(no_return)
|
js! { @(no_return)
|
||||||
var self = @{&self.0};
|
var self = @{&self.0};
|
||||||
|
|
||||||
if (self.id !== null) {
|
|
||||||
cancelAnimationFrame(self.id);
|
cancelAnimationFrame(self.id);
|
||||||
self.callback.drop();
|
self.callback.drop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct TimestampsGlobal {
|
||||||
|
raf: Option<Raf>,
|
||||||
|
// TODO make this more efficient
|
||||||
|
states: Vec<Weak<Mutex<TimestampsState>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimestampsState {
|
||||||
|
first: bool,
|
||||||
|
value: Option<f64>,
|
||||||
|
task: Option<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this more efficient
|
||||||
|
pub struct Timestamps(Arc<Mutex<TimestampsState>>);
|
||||||
|
|
||||||
|
impl Signal for Timestamps {
|
||||||
|
type Item = Option<f64>;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> State<Self::Item> {
|
||||||
|
let mut lock = self.0.lock().unwrap();
|
||||||
|
|
||||||
|
let value = lock.value.take();
|
||||||
|
|
||||||
|
if lock.first {
|
||||||
|
lock.first = false;
|
||||||
|
State::Changed(value)
|
||||||
|
|
||||||
|
} else if value.is_some() {
|
||||||
|
State::Changed(value)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lock.task = Some(task::current());
|
||||||
|
State::NotChanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timestamps() -> Timestamps {
|
||||||
|
lazy_static! {
|
||||||
|
static ref TIMESTAMPS: Mutex<TimestampsGlobal> = Mutex::new(TimestampsGlobal {
|
||||||
|
raf: None,
|
||||||
|
states: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = Arc::new(Mutex::new(TimestampsState {
|
||||||
|
first: true,
|
||||||
|
value: None,
|
||||||
|
task: None,
|
||||||
|
}));
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut lock = TIMESTAMPS.lock().unwrap();
|
||||||
|
|
||||||
|
lock.states.push(Arc::downgrade(&state));
|
||||||
|
|
||||||
|
if let None = lock.raf {
|
||||||
|
lock.raf = Some(Raf::new(|time| {
|
||||||
|
// TODO is it possible for this to deadlock or error or whatever ?
|
||||||
|
let mut lock = TIMESTAMPS.lock().unwrap();
|
||||||
|
|
||||||
|
lock.states.retain(|state| {
|
||||||
|
if let Some(state) = state.upgrade() {
|
||||||
|
let mut lock = state.lock().unwrap();
|
||||||
|
|
||||||
|
// TODO it should always poll the most recent time, so this needs to be a has_changed boolean instead
|
||||||
|
lock.value = Some(time);
|
||||||
|
|
||||||
|
if let Some(task) = lock.task.take() {
|
||||||
|
drop(lock);
|
||||||
|
task.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if lock.states.len() == 0 {
|
||||||
|
lock.raf = None;
|
||||||
|
lock.states = vec![];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timestamps(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -347,10 +426,13 @@ fn range_inclusive(percentage: f64, low: f64, high: f64) -> f64 {
|
||||||
|
|
||||||
|
|
||||||
pub mod unsync {
|
pub mod unsync {
|
||||||
use super::{Raf, Percentage, range_inclusive};
|
use super::{Percentage, range_inclusive, timestamps};
|
||||||
|
use operations::spawn_future;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
|
use signals::signal::{Signal, CancelableFutureHandle};
|
||||||
use signals::signal::unsync::{Mutable, MutableSignal};
|
use signals::signal::unsync::{Mutable, MutableSignal};
|
||||||
|
use discard::DiscardOnDrop;
|
||||||
|
|
||||||
|
|
||||||
struct MutableAnimationState {
|
struct MutableAnimationState {
|
||||||
|
@ -358,7 +440,7 @@ pub mod unsync {
|
||||||
duration: Cell<f64>,
|
duration: Cell<f64>,
|
||||||
value: Mutable<Percentage>,
|
value: Mutable<Percentage>,
|
||||||
end: Cell<Percentage>,
|
end: Cell<Percentage>,
|
||||||
raf: RefCell<Option<Raf>>,
|
animating: RefCell<Option<DiscardOnDrop<CancelableFutureHandle>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -374,7 +456,7 @@ pub mod unsync {
|
||||||
duration: Cell::new(duration),
|
duration: Cell::new(duration),
|
||||||
value: Mutable::new(initial),
|
value: Mutable::new(initial),
|
||||||
end: Cell::new(initial),
|
end: Cell::new(initial),
|
||||||
raf: RefCell::new(None),
|
animating: RefCell::new(None),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,11 +466,11 @@ pub mod unsync {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn stop_raf(&self) {
|
fn stop_animating(&self) {
|
||||||
*self.0.raf.borrow_mut() = None;
|
*self.0.animating.borrow_mut() = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_raf(&self) {
|
fn start_animating(&self) {
|
||||||
if self.0.playing.get() {
|
if self.0.playing.get() {
|
||||||
// TODO use Copy constraint to make value.get() faster ?
|
// TODO use Copy constraint to make value.get() faster ?
|
||||||
let start: f64 = self.0.value.get().into();
|
let start: f64 = self.0.value.get().into();
|
||||||
|
@ -400,29 +482,44 @@ pub mod unsync {
|
||||||
if duration > 0.0 {
|
if duration > 0.0 {
|
||||||
let duration = (end - start).abs() * duration;
|
let duration = (end - start).abs() * duration;
|
||||||
|
|
||||||
let value = self.0.value.clone();
|
let state = self.clone();
|
||||||
|
|
||||||
|
let mut starting_time = None;
|
||||||
|
|
||||||
|
*self.0.animating.borrow_mut() = Some(spawn_future(
|
||||||
|
timestamps()
|
||||||
|
.map(move |current_time| {
|
||||||
|
if let Some(current_time) = current_time {
|
||||||
|
let starting_time = *starting_time.get_or_insert(current_time);
|
||||||
|
|
||||||
*self.0.raf.borrow_mut() = Some(Raf::new(move |starting_time, current_time| {
|
|
||||||
let diff = (current_time - starting_time) / duration;
|
let diff = (current_time - starting_time) / duration;
|
||||||
|
|
||||||
|
// TODO don't update if the new value is the same as the old value
|
||||||
if diff >= 1.0 {
|
if diff >= 1.0 {
|
||||||
value.set(Percentage::new_unchecked(end));
|
state.stop_animating();
|
||||||
false
|
state.0.value.set(Percentage::new_unchecked(end));
|
||||||
|
|
||||||
} else {
|
|
||||||
value.set(Percentage::new_unchecked(range_inclusive(diff, start, end)));
|
|
||||||
true
|
true
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.stop_raf();
|
state.0.value.set(Percentage::new_unchecked(range_inclusive(diff, start, end)));
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wait_for(true)
|
||||||
|
));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
self.stop_animating();
|
||||||
self.0.value.set(Percentage::new_unchecked(end));
|
self.0.value.set(Percentage::new_unchecked(end));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// TODO is this necessary ?
|
// TODO is this necessary ?
|
||||||
self.stop_raf();
|
self.stop_animating();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,24 +529,24 @@ pub mod unsync {
|
||||||
|
|
||||||
if self.0.duration.get() != duration {
|
if self.0.duration.get() != duration {
|
||||||
self.0.duration.set(duration);
|
self.0.duration.set(duration);
|
||||||
self.start_raf();
|
self.start_animating();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pause(&self) {
|
pub fn pause(&self) {
|
||||||
self.0.playing.set(false);
|
self.0.playing.set(false);
|
||||||
self.stop_raf();
|
self.stop_animating();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play(&self) {
|
pub fn play(&self) {
|
||||||
self.0.playing.set(true);
|
self.0.playing.set(true);
|
||||||
self.start_raf();
|
self.start_animating();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jump_to(&self, end: Percentage) {
|
pub fn jump_to(&self, end: Percentage) {
|
||||||
self.stop_raf();
|
self.stop_animating();
|
||||||
|
|
||||||
self.0.end.set(end);
|
self.0.end.set(end);
|
||||||
|
|
||||||
|
@ -466,7 +563,7 @@ pub mod unsync {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.0.end.set(end);
|
self.0.end.set(end);
|
||||||
self.start_raf();
|
self.start_animating();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/dom.rs
15
src/dom.rs
|
@ -1,5 +1,4 @@
|
||||||
use std;
|
use std;
|
||||||
use std::sync::Mutex;
|
|
||||||
use stdweb::{Reference, Value, ReferenceType};
|
use stdweb::{Reference, Value, ReferenceType};
|
||||||
use stdweb::unstable::{TryFrom, TryInto};
|
use stdweb::unstable::{TryFrom, TryInto};
|
||||||
use stdweb::web::{IEventTarget, INode, IElement, IHtmlElement, Node};
|
use stdweb::web::{IEventTarget, INode, IElement, IHtmlElement, Node};
|
||||||
|
@ -9,7 +8,7 @@ use traits::*;
|
||||||
use dom_operations;
|
use dom_operations;
|
||||||
use operations::{BoxDiscard, spawn_future};
|
use operations::{BoxDiscard, spawn_future};
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use discard::Discard;
|
use discard::{Discard, DiscardOnDrop};
|
||||||
|
|
||||||
|
|
||||||
pub struct Dynamic<A>(pub(crate) A);
|
pub struct Dynamic<A>(pub(crate) A);
|
||||||
|
@ -126,7 +125,7 @@ pub struct HtmlBuilder<A> {
|
||||||
impl<A> HtmlBuilder<A> {
|
impl<A> HtmlBuilder<A> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn future<F>(mut self, future: F) -> Self where F: Future<Item = (), Error = ()> + 'static {
|
pub fn future<F>(mut self, future: F) -> Self where F: Future<Item = (), Error = ()> + 'static {
|
||||||
self.callbacks.after_remove(spawn_future(future));
|
self.callbacks.after_remove(DiscardOnDrop::leak(spawn_future(future)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,14 +329,16 @@ impl ClassBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let class_name = {
|
let class_name = {
|
||||||
|
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// TODO can this be made more efficient ?
|
// TODO can this be made more efficient ?
|
||||||
static ref CLASS_ID: Mutex<u32> = Mutex::new(0);
|
// TODO use AtomicU32 instead ?
|
||||||
|
static ref CLASS_ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut id = CLASS_ID.lock().unwrap();
|
// TODO check for overflow ?
|
||||||
|
let id = CLASS_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
*id += 1;
|
|
||||||
|
|
||||||
// TODO make this more efficient ?
|
// TODO make this more efficient ?
|
||||||
format!("__class_{}__", id)
|
format!("__class_{}__", id)
|
||||||
|
|
|
@ -16,14 +16,14 @@ use futures::future::Future;
|
||||||
|
|
||||||
// TODO this should probably be in stdweb
|
// TODO this should probably be in stdweb
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn spawn_future<F>(future: F) -> CancelableFutureHandle
|
pub fn spawn_future<F>(future: F) -> DiscardOnDrop<CancelableFutureHandle>
|
||||||
where F: Future<Item = (), Error = ()> + 'static {
|
where F: Future<Item = (), Error = ()> + 'static {
|
||||||
// TODO make this more efficient ?
|
// TODO make this more efficient ?
|
||||||
let (handle, future) = cancelable_future(future, |_| ());
|
let (handle, future) = cancelable_future(future, |_| ());
|
||||||
|
|
||||||
PromiseFuture::spawn(future);
|
PromiseFuture::spawn(future);
|
||||||
|
|
||||||
DiscardOnDrop::leak(handle)
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,10 +32,10 @@ fn for_each<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
||||||
where A: Signal + 'static,
|
where A: Signal + 'static,
|
||||||
B: FnMut(A::Item) + 'static {
|
B: FnMut(A::Item) + 'static {
|
||||||
|
|
||||||
spawn_future(signal.for_each(move |value| {
|
DiscardOnDrop::leak(spawn_future(signal.for_each(move |value| {
|
||||||
callback(value);
|
callback(value);
|
||||||
Ok(())
|
Ok(())
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,10 +44,10 @@ fn for_each_vec<A, B>(signal: A, mut callback: B) -> CancelableFutureHandle
|
||||||
where A: SignalVec + 'static,
|
where A: SignalVec + 'static,
|
||||||
B: FnMut(VecChange<A::Item>) + 'static {
|
B: FnMut(VecChange<A::Item>) + 'static {
|
||||||
|
|
||||||
spawn_future(signal.for_each(move |value| {
|
DiscardOnDrop::leak(spawn_future(signal.for_each(move |value| {
|
||||||
callback(value);
|
callback(value);
|
||||||
Ok(())
|
Ok(())
|
||||||
}))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue