Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

utility crate: requestAnimationFrame #1

Open
2 of 4 tasks
fitzgen opened this issue Feb 26, 2019 · 13 comments
Open
2 of 4 tasks

utility crate: requestAnimationFrame #1

fitzgen opened this issue Feb 26, 2019 · 13 comments

Comments

@fitzgen
Copy link
Member

fitzgen commented Feb 26, 2019

We should have an idiomatic Rust utility crate for working with

@Pauan
Copy link
Contributor

Pauan commented Mar 13, 2019

A couple more things which I think would be useful:

  • For performance reasons, it's probably faster to use a single rAF and then use a Rust VecDeque to support multiple listeners. I've used this strategy in dominator, and I can explain in more detail if needed.

  • rAF is an API which works excellently with my futures-signals crate.

    For example, dominator has an API called timestamps() which returns a Signal of timestamps (which automatically updates every 60 FPS, by using rAF).

    Of course it supports automatic cancellation and automatic frame dropping, which makes it ideal for silky-smooth synchronized animations.

The above was pioneered by dominator, but it is absolutely not tied to dominator at all, so it can easily be put into a separate crate.

@fitzgen
Copy link
Member Author

fitzgen commented Mar 15, 2019

@Pauan are you interested in making a strawman API proposal / draft PR for some of this?

@fitzgen
Copy link
Member Author

fitzgen commented Mar 15, 2019

FWIW, when I said "debouncing rAF", this is the kind of thing I was thinking of: https://github.com/fitzgen/dodrio/blob/master/src/vdom.rs#L599-L625

@Aloso
Copy link

Aloso commented Mar 31, 2019

@fitzgen I don't think that dodrio::VdomWeak::render uses debouncing. If I understand it correctly, you want an API for requesting animation frames that returns a Future or a Stream.

I'd like to give this a try. Here's my design idea:

Low-level API

use gloo::ani_frames::Animation;

let af: AnimationIndex<_> = Animation::request_frame(|| { ... });
Animation::cancel_frame(af);

Higher-level API

struct T; // zero-sized type to distinguish animations

// create and start a requestAnimationFrame() loop!
// use Animation::<T>::paused() if you don't want to start it
let mut ani = Animation::<T>::new();

// add closure that is executed on every frame:
ani.add(|state| { ... });

// closures can be removed (only once, because AnimationIndex isn't Copy):
let index: AnimationIndex<T> = ani.add(|_| { ... });
ani.remove(index);

ani.pause();  // pauses the animation loop, using cancelAnimationFrame()
ani.once();   // requests one frame, if it's paused
ani.start();  // starts the animation loop, if it's paused

std::mem::drop(ani); // cancels the animation
// or
ani.forget(); // leaks the memory

Higher-level API using futures

struct T;

let mut ani: AnimationStream<T, &AnimationState> = Animation::stream(); // or paused_stream()

// add closure that is executed on every frame:
ani.add(|state| { ... });
// or
let ani = ani.map(|state| { ... });

Please tell me what you think!

This is roughly how I would implement the higher-level API:

pub struct Animation<C> {
    state: AnimationState,
    next_index: AnimationIndex<C>,
    callbacks: Vec<(AnimationIndex<C>, Box<dyn FnMut(AnimationState)>)>,
    // this could be done with a HashMap, but I think we want deterministic iteration order
}

pub enum AnimationState {
    Paused,
    Running(i64),
    RunningOnce(i64),
}

pub struct AnimationIndex<C> {
    index: i64,
    _marker: PhantomData<C>,
}

@fitzgen
Copy link
Member Author

fitzgen commented Apr 1, 2019

@fitzgen I don't think that dodrio::VdomWeak::render uses debouncing. If I understand it correctly, you want an API for requesting animation frames that returns a Future or a Stream.

If there is not an existing promise, then an animation frame is scheduled that will resolve the promise, and the promise is saved. If there is an existing promise, then it is re-used and no new animation frame is scheduled. Thus the requestAnimationFrame calls are "debounced" or "de-duplicated" or "reused"; whatever you want to call it.

@fitzgen
Copy link
Member Author

fitzgen commented Apr 1, 2019

For the low-level API, I would expect something very similar to the gloo_events API that we have a WIP PR for. That is:

  • Takes a callback (like what you've sketched out)
  • Returns an RAII type that
    • automatically cancels the animation frame on drop (not a separate static method, unlike what you've sketched out)
    • Has a forget method to make it uncancelable
struct T; // zero-sized type to distinguish animations

Is this the state passed to each callback? Wait it seems like this is a separate AnimationState thing. Is that user-defined or provided by the library?

@Aloso
Copy link

Aloso commented Apr 1, 2019

Is this the state passed to each callback? Wait it seems like this is a separate AnimationState thing. Is that user-defined or provided by the library?

The state isn't user-defined, it's one of Paused, Running(i32) or RunningOnce(i32). I thought it might be useful to pass it to the callbacks.

The struct T is just a way to allow distinguish animations, so the following doesn't compile:

struct T;
struct U;

let mut first = Animation::<T>::new();
let mut second = Animation::<U>::new();

let ix = first.add(|_| { ... });
second.remove(ix); // expected AnimationIndex<U>, found AnimationIndex<T>

Instead of T and U, you could also write [(); 1] and [(); 2], or &() and &&().

I'm trying to implement the whole thing ATM and struggling with the borrow checker. I fear I'll have to use a Rc<Mutex<Animation>> in the loop.

Here is my helper function. It compiles, but when I try to use it, I get all sorts of errors because of 'static:

fn request_af<F: FnOnce() + 'static>(callback: F) -> i32 {
    web_sys::window().unwrap_throw()
        .request_animation_frame(Closure::once(callback).as_ref().unchecked_ref())
        .unwrap_throw()
}

@Pauan
Copy link
Contributor

Pauan commented Apr 2, 2019

I fear I'll have to use a Rc<Mutex<Animation>> in the loop.

Yes, if you want looping in Rust, that is necessary.

If the looping is done in JS it isn't necessary, but then you need a local .js snippet.

Nit: it should be RefCell, not Mutex.

@dakom

This comment was marked as abuse.

@dakom

This comment was marked as abuse.

@dakom

This comment was marked as abuse.

@dakom

This comment was marked as abuse.

@ranile
Copy link
Collaborator

ranile commented Jun 24, 2021

1. and 4. were implemented in #126

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants