npm install --save @playframe/oversyncimport oversync from '@playframe/oversync'
const sync = oversync(Date.now, requestAnimationFrame)Each method schedules given function to be executed in specific time and order
sync.next(fn) // events handling and dom read
sync.catch(fn) // error handling
sync.then(fn) // data work is done here
sync.finally(fn) // finalizing data
sync.render(fn) // dom manipulation
// Actual requestAnimationFrame callback
// No work should be done here
sync.frame(fn)Render a frame_0 first, then request a new frame_1 and immediately do work. After work is done VM is idling for up to 10ms until frame callback is fired and frame_1 finally rendered. Any event occuring after work is done but before frame_1 is rendered will schedule actual work to be done onlly after frame_1 is rendered
1ms Request frame_0 and setTimeout(work_for_frame_1)
2ms frame_0 is rendered by browser
3ms Request frame_1
4ms work_for_frame_1: read dom, do work, write dom
... idle
8ms Click: sync.next(click_handler) for frame_2
... idle
10ms Fetch: sync.then(fetch_handler) for frame_2
...idle
15ms Animation callback: setTimeout(work_for_frame_2)
16ms frame_1 is rendered
17ms Request frame_2
18ms work_for_frame_2: read dom, do work, write dom
...Let's define a higher order function
that would take a now timestamp function,
scheduling next function and optionally
a list steps of desired execution order and method names
and an optional step method name.
module.exports = (now, next, steps=[
  'next', 'catch', 'then', 'finally', 'render'
], step = 'frame')=>For each step we would prepare and empty array
  step_ops = []
  steps_ops = steps.map => []For measuring time deltas we would have a fancy runner function
  delta_runner = delta(now) runnerschedule function for requesting next frame
in which we would run our frame operations and
schedule work for the rest of the steps
  schedule = scheduler(next) =>
    run = delta_runner()
    run step_ops
    setTimeout => steps_ops.forEach runA pusher function that will schedule on every push
  push_and_run = pusher scheduleDynamically creating methods that would push operations
and schedule execution and returning sync
  sync = {}
  sync[step] = push_and_run step_ops
  steps.forEach (step, i)=>
    sync[step] = push_and_run steps_ops[i]
  syncOur scheduler is creating a throttled schedule
scheduler = (next)=>(f)=>
  _scheduled = false
  g = (x)=> _scheduled = false; f x
  => unless _scheduled then _scheduled = true; next g; returnThis pusher is creating a function that will run task
before pushing op to ops
pusher = (task)=>(ops)=>(op)=> do task; ops.push op; returnFeeding timestamps produced by now to a given f
like our runner
delta = (now)=>(f)=>
  _prev_ts = now()
  => f delta: (ts = now()) - _prev_ts, ts: (_prev_ts = ts)This runner will feed x to a list of given ops.
It will recover if any operation fails.
Clearing ops list at the end
runner = (x)=>(ops)=>
  i = 0
  # Rechecking length in outer loop
  # could push more ops while running
  while (i < length = ops.length)
    try
      ops[i++] x while i < length
    catch e
      console.error e
      recover e if recover = ops[i - 1].r # recovering
  ops.length = 0 # mutating 👹
  return