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