Giter VIP home page Giter VIP logo

Comments (13)

Pauan avatar Pauan commented on July 30, 2024 1

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.

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

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

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

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

from gloo.

Aloso avatar Aloso commented on July 30, 2024

@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>,
}

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

@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.

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

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?

from gloo.

Aloso avatar Aloso commented on July 30, 2024

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()
}

from gloo.

Pauan avatar Pauan commented on July 30, 2024

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.

from gloo.

dakom avatar dakom commented on July 30, 2024

Fwiw below is what I'm currently using for a cancellable rAF loop.

At a high level it takes a closure (which gets the timestamp) and gives back a function that can be used to cancel.

This allows wrapping the closure in order to support debouncing (or in this demo, turning it into a struct with elapsedTime and deltaTime)

It's not much more than a modified version of the reference implementation:

/// Kick off a rAF loop. The returned function can be called to cancel it
pub fn start_raf_ticker<F>(mut on_tick:F) -> Result<impl (FnOnce() -> ()), JsValue> 
where F: (FnMut(f64) -> ()) + 'static
{

    let f = Rc::new(RefCell::new(None));
    let g = f.clone();

    //the main closure must be static - and so it needs to take in its deps via move
    //but keep_alive also exists in cancel() - so we're left with multiple owners
    let keep_alive = Rc::new(Cell::new(true));
    
    let mut raf_id:Option<i32> = None;

    //this window is passed into the loop
    let window = web_sys::window().expect("couldn't get window!");
    {
        let keep_alive = Rc::clone(&keep_alive);
        *g.borrow_mut() = Some(Closure::wrap(Box::new(move |time| {
           
            if !keep_alive.get() {
                if let Some(id) = raf_id {
                    info!("clearing raf id: {}", id);
                    window.cancel_animation_frame(id).unwrap();
                }
                //stopping tick loop
                f.borrow_mut().take();
            } else {
                on_tick(time);
                raf_id = request_animation_frame(&window, f.borrow().as_ref().unwrap()).ok();
            }
        }) as Box<FnMut(f64)-> ()>));
    }

    //this is just used to create the first invocation
    let window = web_sys::window().expect("couldn't get window!");
    request_animation_frame(&window, g.borrow().as_ref().unwrap())?;
   
    let cancel = move || keep_alive.set(false);

    Ok(cancel)
}

fn request_animation_frame(window:&Window, f: &Closure<FnMut(f64) -> ()>) -> Result<i32, JsValue> {
    window.request_animation_frame(f.as_ref().unchecked_ref())
}

from gloo.

dakom avatar dakom commented on July 30, 2024

Now that work has been done to bring the other crates in line with std::Future, perhaps this can be resurrected... rAF could give a Stream?

from gloo.

dakom avatar dakom commented on July 30, 2024

Btw looking at @Pauan 's Raf struct here it looks like it would be really straightforward to bring as-is into gloo... any reason not to do this now?

from gloo.

dakom avatar dakom commented on July 30, 2024

RFC here: #107

from gloo.

hamza1311 avatar hamza1311 commented on July 30, 2024

1. and 4. were implemented in #126

from gloo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.