Skip to content

playframe/oversync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PlayFrame

OverSync

0.4 kB Frame Rendering Engine

Installation

npm install --save @playframe/oversync

Functionality Overview

import 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)

Execution strategy

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
...

Annotated Source

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) runner

schedule 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 run

A pusher function that will schedule on every push

  push_and_run = pusher schedule

Dynamically 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]

  sync

Abstract functions

Our scheduler is creating a throttled schedule

scheduler = (next)=>(f)=>
  _scheduled = false
  g = (x)=> _scheduled = false; f x
  => unless _scheduled then _scheduled = true; next g; return

This pusher is creating a function that will run task before pushing op to ops

pusher = (task)=>(ops)=>(op)=> do task; ops.push op; return

Feeding 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