gloo icon indicating copy to clipboard operation
gloo copied to clipboard

Standard future factories for queueing actions at various points.

Open richard-uk1 opened this issue 5 years ago • 4 comments

Summary

Note: I've written this using futures 0.3, but this is something that one could change.

Provide a set of async functions with the following signature

fn example() -> impl Future<Output = ()>

where the futures resolves in the following places

  • on the next tick of the microtask queue (Promise.then)
  • at the end of the task queue (i.e. after scheduled repaints) (setTimeout(0))
  • when the browser thinks we should update an animation (requestAnimationFrame)
  • after a certain amount of time, using setTimeout
  • possibly more places

This post about Tasks, microtasks etc. is how I learnt about all this.

Motivation

This library would serve 2 purposes.

It would make it easy to select the right time to do your operation, and it also educates the user of the library about the intricacies of the JavaScript event loop. I think grouping all these together because they are all futures that resolve to nothing at some point make them much easier to understand than how they are arranged in JavaScript (scattered everywhere).

Detailed Explanation

I would envisage the functions looking like

/// This future will resolve after current execution has finished, but before the browser does 
/// anything else (e.g. screen repaints). This is the fastest choice.
fn next_microtask() -> impl Future<Output = ()>;

/// This future will resolve after the browser has finished the current execution, and after any 
/// other scheduled events (e.g. screen repaints)
fn next_task() -> impl Future<Output = ()>;

/// This future will resolve when the browser determines that another frame of an animation 
/// should be drawn. This will be in general slower than `next_task`, as the browser can process 
/// events faster than the screen will refresh.
fn next_animation_frame() -> impl Future<Output = ()>;

/// This future will resolve in the future, at least `ms` milliseconds in the future. If you set `ms`
/// to `0`, then this future behaves like `next_task`.
///
/// # Panics
///
/// This function will panic if `ms` is greater than the maximum safe JavaScript integer.
/// Fortunately, this number is so big that waiting for the future to resolve would take 285 
/// thousand years.
fn next_ms(ms: u64) -> impl Future<Output = ()>;

Drawbacks, Rationale, and Alternatives

The main drawback is that this is different to the way that JavaScript itself handles all these things. The alternative is to not do it.

Unresolved Questions

Callbacks are executed before promises, so in theory there could be another function next_callback that executes before next_microtask, but since they both execute before any browser events are handled, I think that would be overkill.

The setTimeout wrapper could not panic, instead using the nearest f64. That method could also take a Duration.

EDIT Formatting

richard-uk1 avatar Jul 06 '19 20:07 richard-uk1

after a certain amount of time, using setTimeout

That's already handled by the gloo-timers crate. In fact, I think all of these would fit in pretty well in gloo-timers.

The setTimeout wrapper could not panic, instead using the nearest f64.

I don't think saturating behavior is good.

Also, it should be u32, not u64, because the biggest ms for setTimeout is 2147483647 (which is std::i32::MAX)

This future will resolve after the browser has finished the current execution, and after any other scheduled events (e.g. screen repaints)

I don't think it's guaranteed that macrotasks execute after everything else. That is especially true with screen repaints, which are only guaranteed after requestAnimationFrame.

The ordering is also not guaranteed (the browser can rearrange macrotask events). The only guarantee is that it will execute after all of the microtasks.

next_animation_frame

This should return f64, not ().

We should also have an fn animation_frames() -> AnimationFrames, where AnimationFrames is a Stream<Output = f64>, making it easy to create animation loops.


As a meta note, we've changed the proposal process so that RFCs must now be made as pull requests, because this makes them a lot easier to review.

Pauan avatar Jul 07 '19 06:07 Pauan

Oh, also, we should have an fn every_ms(ms: u32) -> impl Stream<Output = ()>, which would be a wrapper for setInterval

Pauan avatar Jul 07 '19 07:07 Pauan

I've been thinking about this again. How about using the Timeout object for all of these things, by providing some extra constructors: as well as new there would be new_microtask, new_task (same as new(0, ...), and new_animation_frame. They would then dispatch to the appropriate js functions.

richard-uk1 avatar Nov 30 '19 13:11 richard-uk1

How about using the Timeout object for all of these things ... there would be new_microtask

queueMicrotask() does not return anything similar to timeoutID and cannot be cancelled, so unless you're not planning on using queueMicrotask(), the functionality will be pretty different. I'm not sure if it makes sense to add microtask code to Timeout.

heilhead avatar Dec 06 '19 09:12 heilhead