Comments (13)
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 RustVecDeque
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 myfutures-signals
crate.For example, dominator has an API called
timestamps()
which returns aSignal
of timestamps (which automatically updates every 60 FPS, by usingrAF
).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.
@Pauan are you interested in making a strawman API proposal / draft PR for some of this?
from gloo.
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.
@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 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 aFuture
or aStream
.
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.
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.
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.
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.
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.
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.
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.
RFC here: #107
from gloo.
1. and 4. were implemented in #126
from gloo.
Related Issues (20)
- gloo-worker: incorrect worker path loading when root url is different HOT 1
- [Draft RFC] Make `gloo_net` more idiomatic
- Patch required to fix confusing docs that still show `from_serde` and `into_serde` examples the wrong way around
- `gloo_net`'s `RequestBuilder` is not public
- Use OnceCell for gloo-history HOT 2
- gloo-histroy Support custom query decoder / encoder HOT 9
- Retries for EventSource HOT 1
- async wasm tests don't seem to actually do anything HOT 2
- [history] BrowserHistory: Loaded wrong state HOT 2
- gloo-net: Allow RequestBuilder.query to accept a struct that implements serde::Serialize as an argument HOT 1
- How to close a WebSocket after calling `.split()` ?
- Cloning gloo-worker bridges does not assign new HandlerIds
- Not working with recent yew-0.21.0 HOT 1
- Blob & ObjecUrl generate invalid dowload link HOT 3
- Allow calling `terminate` on workers
- [history] Inconsitent type between gloo_history and gloo_utils HOT 10
- Remove event in another event HOT 2
- Complete gloo-worker webassembly example running in a browser HOT 2
- Feature request: MissedTickBehavior for gloo_timers::future::IntervalStream
- Documentation - broken method reference
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from gloo.