Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

raf-schd

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.

Installation

[dependencies]
raf-schd = "0.1"

Usage

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"]);

Behavior

  • 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.
  • cancel drops 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.

Why a scheduler trait

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.

License

Licensed under the MIT license.

About

Throttle a function to at most one call per animation frame, with the latest arguments.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages