Throttle a function to at most one call per animation frame, with the latest arguments.
Wrap a function and get back a handle. Call the handle as often as you like. The wrapped function runs at most once per frame, with the most recent arguments seen before the frame fires. Use this for cheap-to-trigger, expensive-to-apply work driven by high-frequency events such as scroll, pointer move, resize, or drag.
This is a frame-paced throttle. It schedules work on the next animation frame through a scheduler you supply. The scheduler decides when a frame fires. On the web, wire it to the browser animation frame loop so the cadence tracks the display refresh rate and slows down when the browser drops frames.
[dependencies]
raf-schd = "0.1"use std::cell::RefCell;
use std::rc::Rc;
use raf_schd::{raf_schd, FrameScheduler, FrameId};
// A scheduler that runs queued callbacks when you call `tick`.
#[derive(Default)]
struct Manual {
queue: RefCell<Vec<(FrameId, Box<dyn FnOnce()>)>>,
next: RefCell<u64>,
}
impl Manual {
fn tick(&self) {
let frame: Vec<_> = self.queue.borrow_mut().drain(..).collect();
for (_id, cb) in frame {
cb();
}
}
}
impl FrameScheduler for Manual {
fn request(&self, cb: Box<dyn FnOnce()>) -> FrameId {
let mut next = self.next.borrow_mut();
*next += 1;
let id = FrameId(*next);
self.queue.borrow_mut().push((id, cb));
id
}
fn cancel(&self, id: FrameId) {
self.queue.borrow_mut().retain(|(queued, _)| *queued != id);
}
}
let scheduler = Rc::new(Manual::default());
let log = Rc::new(RefCell::new(Vec::new()));
let sink = log.clone();
let schedule = raf_schd(scheduler.clone(), move |arg: &'static str| {
sink.borrow_mut().push(arg);
});
schedule.call("foo");
schedule.call("bar");
schedule.call("baz");
scheduler.tick();
// The wrapped function ran once, with the last arguments.
assert_eq!(*log.borrow(), vec!["baz"]);- A call records the latest arguments and schedules a frame if none is pending.
- The wrapped function runs once per frame, with the most recent arguments.
- Calling again while a frame is pending replaces the stored arguments for that frame.
canceldrops a pending frame. It is a no-op when nothing is pending and is idempotent.- Cancel leaves the stored arguments alone. The wrapper stays usable for later frames.
- A panic from the wrapped function propagates out of the frame run.
Native Rust has no animation frame API. The core takes a FrameScheduler so the same logic runs
anywhere. Tests drive it with an in-memory scheduler. On the web, implement the trait
over Window::request_animation_frame and Window::cancel_animation_frame.
Licensed under the MIT license.