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

Proposal: Unified API for callbacks called in regular intervals, which covers Interval and request_animation_frame #261

Open
Boscop opened this issue Oct 30, 2022 · 1 comment

Comments

@Boscop
Copy link

Boscop commented Oct 30, 2022

Summary

Both Interval and request_animation_frame can be used to schedule callbacks that get called in regular intervals, but they both have a very different API. This proposal unifies their APIs with one trait (FrameScheduler).

Motivation

A user might want to try different approaches for running a callback in regular intervals, and a unified API would make it easier.
In many use cases (when it's not an animation) the decision is not clear cut, because both approaches have different properties (setInterval has jitter but allows calling in faster intervals than requestAnimationFrame) and the user might want to test both for their use case. E.g. in my use case I was implementing a midi player and it needs to be stepped forward in regular intervals.

Detailed Explanation

It's quite self-explanatory:

pub trait FrameScheduler {
	fn start<CTX: 'static>(ctx: CTX, frame: impl Fn(&mut CTX, DOMHighResTimeStamp) -> bool + 'static)
	-> Self;

	fn stop(&mut self);
}

pub struct SetIntervalScheduler<const MS: u32 = 5>(Rc<RefCell<Option<Interval>>>);

impl<const MS: u32> FrameScheduler for SetIntervalScheduler<MS> {
	fn start<CTX: 'static>(
		mut ctx: CTX,
		frame: impl Fn(&mut CTX, DOMHighResTimeStamp) -> bool + 'static,
	) -> Self {
		let ms_between_frames = MS;
		let interval = Rc::new(RefCell::new(None));
		let interval_inner = interval.clone();
		*interval.borrow_mut() = Some(Interval::new(ms_between_frames, move || {
			let timestamp = window().performance().unwrap().now();
			if !frame(&mut ctx, timestamp) {
				*interval_inner.borrow_mut() = None;
			}
		}));
		Self(interval)
	}

	fn stop(&mut self) {
		*self.0.borrow_mut() = None;
	}
}

pub struct AnimationFrameScheduler(Rc<RefCell<Option<AnimationFrame>>>);

impl FrameScheduler for AnimationFrameScheduler {
	fn start<CTX: 'static>(
		ctx: CTX,
		frame: impl Fn(&mut CTX, DOMHighResTimeStamp) -> bool + 'static,
	) -> Self {
		use gloo::render::request_animation_frame;

		struct AnimationFrameContext<C> {
			animation_frame: Rc<RefCell<Option<AnimationFrame>>>,
			ctx: C,
		}

		fn cb<C: 'static>(
			frame: impl Fn(&mut C, DOMHighResTimeStamp) -> bool + 'static,
			mut outer: AnimationFrameContext<C>,
		) -> impl FnOnce(DOMHighResTimeStamp) + 'static {
			move |timestamp: DOMHighResTimeStamp| {
				if frame(&mut outer.ctx, timestamp) {
					let animation_frame = outer.animation_frame.clone();
					*animation_frame.borrow_mut() = Some(request_animation_frame(cb(frame, outer)));
				} else {
					*outer.animation_frame.borrow_mut() = None;
				}
			}
		}

		let outer = AnimationFrameContext { animation_frame: Rc::new(RefCell::new(None)), ctx };
		let animation_frame = outer.animation_frame.clone();
		*animation_frame.borrow_mut() = Some(request_animation_frame(cb(frame, outer)));
		Self(animation_frame)
	}

	fn stop(&mut self) {
		*self.0.borrow_mut() = None;
	}
}

pub type DOMHighResTimeStamp = f64;

Unresolved Questions

It would be nice to get the Context instance back when the "loop" terminates. If the loop terminates because of the callback returning false, the context could be preserved by storing it in the Rc. But if it's terminated via calling stop, the context can't be recovered because it gets dropped along with the closure that captured it. Maybe someone has an idea for how to do it though..

@hamza1311
Copy link
Collaborator

As you've correctly described that both approaches are very different, I don't think it makes sense to expose them under the same API. Introducing an API that is only helpful when the user is deciding which function to use doesn't sound right to me

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

No branches or pull requests

2 participants