refactor: document new scheduler
This commit is contained in:
		
							parent
							
								
									cb7ad12ed4
								
							
						
					
					
						commit
						2f3ddf20b5
					
				@ -9,8 +9,8 @@ import {
 | 
			
		||||
  queueJob,
 | 
			
		||||
  handleSchedulerError,
 | 
			
		||||
  nextTick,
 | 
			
		||||
  queuePostCommitCb,
 | 
			
		||||
  flushPostCommitCbs,
 | 
			
		||||
  queueEffect,
 | 
			
		||||
  flushEffects,
 | 
			
		||||
  queueNodeOp
 | 
			
		||||
} from '@vue/scheduler'
 | 
			
		||||
import { VNodeFlags, ChildrenFlags } from './flags'
 | 
			
		||||
@ -188,12 +188,12 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
      insertOrAppend(container, el, endNode)
 | 
			
		||||
    }
 | 
			
		||||
    if (ref) {
 | 
			
		||||
      queuePostCommitCb(() => {
 | 
			
		||||
      queueEffect(() => {
 | 
			
		||||
        ref(el)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (data != null && data.vnodeMounted) {
 | 
			
		||||
      queuePostCommitCb(() => {
 | 
			
		||||
      queueEffect(() => {
 | 
			
		||||
        data.vnodeMounted(vnode)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
@ -268,7 +268,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
            const subTree = (handle.prevTree = vnode.children = renderFunctionalRoot(
 | 
			
		||||
              vnode
 | 
			
		||||
            ))
 | 
			
		||||
            queuePostCommitCb(() => {
 | 
			
		||||
            queueEffect(() => {
 | 
			
		||||
              vnode.el = subTree.el as RenderNode
 | 
			
		||||
            })
 | 
			
		||||
            mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
 | 
			
		||||
@ -308,7 +308,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
    const nextTree = (handle.prevTree = current.children = renderFunctionalRoot(
 | 
			
		||||
      current
 | 
			
		||||
    ))
 | 
			
		||||
    queuePostCommitCb(() => {
 | 
			
		||||
    queueEffect(() => {
 | 
			
		||||
      current.el = nextTree.el
 | 
			
		||||
    })
 | 
			
		||||
    patch(
 | 
			
		||||
@ -344,7 +344,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
    const { children, childFlags } = vnode
 | 
			
		||||
    switch (childFlags) {
 | 
			
		||||
      case ChildrenFlags.SINGLE_VNODE:
 | 
			
		||||
        queuePostCommitCb(() => {
 | 
			
		||||
        queueEffect(() => {
 | 
			
		||||
          vnode.el = (children as MountedVNode).el
 | 
			
		||||
        })
 | 
			
		||||
        mount(children as VNode, container, contextVNode, isSVG, endNode)
 | 
			
		||||
@ -355,7 +355,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
        vnode.el = placeholder.el
 | 
			
		||||
        break
 | 
			
		||||
      default:
 | 
			
		||||
        queuePostCommitCb(() => {
 | 
			
		||||
        queueEffect(() => {
 | 
			
		||||
          vnode.el = (children as MountedVNode[])[0].el
 | 
			
		||||
        })
 | 
			
		||||
        mountArrayChildren(
 | 
			
		||||
@ -392,7 +392,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    if (ref) {
 | 
			
		||||
      queuePostCommitCb(() => {
 | 
			
		||||
      queueEffect(() => {
 | 
			
		||||
        ref(target)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
@ -607,7 +607,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
    // then retrieve its next sibling to use as the end node for patchChildren.
 | 
			
		||||
    const endNode = platformNextSibling(getVNodeLastEl(prevVNode))
 | 
			
		||||
    const { childFlags, children } = nextVNode
 | 
			
		||||
    queuePostCommitCb(() => {
 | 
			
		||||
    queueEffect(() => {
 | 
			
		||||
      switch (childFlags) {
 | 
			
		||||
        case ChildrenFlags.SINGLE_VNODE:
 | 
			
		||||
          nextVNode.el = (children as MountedVNode).el
 | 
			
		||||
@ -1280,7 +1280,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
 | 
			
		||||
        instance.$vnode = renderInstanceRoot(instance) as MountedVNode
 | 
			
		||||
 | 
			
		||||
        queuePostCommitCb(() => {
 | 
			
		||||
        queueEffect(() => {
 | 
			
		||||
          vnode.el = instance.$vnode.el
 | 
			
		||||
          if (__COMPAT__) {
 | 
			
		||||
            // expose __vue__ for devtools
 | 
			
		||||
@ -1337,7 +1337,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
 | 
			
		||||
    const nextVNode = renderInstanceRoot(instance) as MountedVNode
 | 
			
		||||
 | 
			
		||||
    queuePostCommitCb(() => {
 | 
			
		||||
    queueEffect(() => {
 | 
			
		||||
      instance.$vnode = nextVNode
 | 
			
		||||
      const el = nextVNode.el as RenderNode
 | 
			
		||||
      if (__COMPAT__) {
 | 
			
		||||
@ -1426,7 +1426,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
    if (__DEV__) {
 | 
			
		||||
      popWarningContext()
 | 
			
		||||
    }
 | 
			
		||||
    queuePostCommitCb(() => {
 | 
			
		||||
    queueEffect(() => {
 | 
			
		||||
      callActivatedHook(instance, true)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
@ -1510,7 +1510,7 @@ export function createRenderer(options: RendererOptions) {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (__COMPAT__) {
 | 
			
		||||
      flushPostCommitCbs()
 | 
			
		||||
      flushEffects()
 | 
			
		||||
      return vnode && vnode.flags & VNodeFlags.COMPONENT_STATEFUL
 | 
			
		||||
        ? (vnode.children as ComponentInstance).$proxy
 | 
			
		||||
        : null
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { queueJob, queuePostCommitCb, nextTick } from '../src/index'
 | 
			
		||||
import { queueJob, queueEffect, nextTick } from '../src/index'
 | 
			
		||||
 | 
			
		||||
describe('scheduler', () => {
 | 
			
		||||
  it('queueJob', async () => {
 | 
			
		||||
@ -36,11 +36,11 @@ describe('scheduler', () => {
 | 
			
		||||
    const calls: any = []
 | 
			
		||||
    const job1 = () => {
 | 
			
		||||
      calls.push('job1')
 | 
			
		||||
      queuePostCommitCb(cb1)
 | 
			
		||||
      queueEffect(cb1)
 | 
			
		||||
    }
 | 
			
		||||
    const job2 = () => {
 | 
			
		||||
      calls.push('job2')
 | 
			
		||||
      queuePostCommitCb(cb2)
 | 
			
		||||
      queueEffect(cb2)
 | 
			
		||||
    }
 | 
			
		||||
    const cb1 = () => {
 | 
			
		||||
      calls.push('cb1')
 | 
			
		||||
@ -59,13 +59,13 @@ describe('scheduler', () => {
 | 
			
		||||
    const calls: any = []
 | 
			
		||||
    const job1 = () => {
 | 
			
		||||
      calls.push('job1')
 | 
			
		||||
      queuePostCommitCb(cb1)
 | 
			
		||||
      queueEffect(cb1)
 | 
			
		||||
      // job1 queues job2
 | 
			
		||||
      queueJob(job2)
 | 
			
		||||
    }
 | 
			
		||||
    const job2 = () => {
 | 
			
		||||
      calls.push('job2')
 | 
			
		||||
      queuePostCommitCb(cb2)
 | 
			
		||||
      queueEffect(cb2)
 | 
			
		||||
    }
 | 
			
		||||
    const cb1 = () => {
 | 
			
		||||
      calls.push('cb1')
 | 
			
		||||
@ -100,7 +100,7 @@ describe('scheduler', () => {
 | 
			
		||||
    const calls: any = []
 | 
			
		||||
    const job1 = () => {
 | 
			
		||||
      calls.push('job1')
 | 
			
		||||
      queuePostCommitCb(cb1)
 | 
			
		||||
      queueEffect(cb1)
 | 
			
		||||
    }
 | 
			
		||||
    const cb1 = () => {
 | 
			
		||||
      // queue another job in postFlushCb
 | 
			
		||||
@ -109,7 +109,7 @@ describe('scheduler', () => {
 | 
			
		||||
    }
 | 
			
		||||
    const job2 = () => {
 | 
			
		||||
      calls.push('job2')
 | 
			
		||||
      queuePostCommitCb(cb2)
 | 
			
		||||
      queueEffect(cb2)
 | 
			
		||||
    }
 | 
			
		||||
    const cb2 = () => {
 | 
			
		||||
      calls.push('cb2')
 | 
			
		||||
 | 
			
		||||
@ -1,51 +1,150 @@
 | 
			
		||||
// TODO infinite updates detection
 | 
			
		||||
 | 
			
		||||
// A data structure that stores a deferred DOM operation.
 | 
			
		||||
// the first element is the function to call, and the rest of the array
 | 
			
		||||
// stores up to 3 arguments.
 | 
			
		||||
type Op = [Function, ...any[]]
 | 
			
		||||
 | 
			
		||||
const enum Priorities {
 | 
			
		||||
  NORMAL = 500
 | 
			
		||||
// A "job" stands for a unit of work that needs to be performed.
 | 
			
		||||
// Typically, one job corresponds to the mounting or updating of one component
 | 
			
		||||
// instance (including functional ones).
 | 
			
		||||
interface Job<T extends Function = () => void> {
 | 
			
		||||
  // A job is itself a function that performs work. It can contain work such as
 | 
			
		||||
  // calling render functions, running the diff algorithm (patch), mounting new
 | 
			
		||||
  // vnodes, and tearing down old vnodes. However, these work needs to be
 | 
			
		||||
  // performed in several different phases, most importantly to separate
 | 
			
		||||
  // workloads that do not produce side-effects ("stage") vs. those that do
 | 
			
		||||
  // ("commit").
 | 
			
		||||
  // During the stage call it should not perform any direct sife-effects.
 | 
			
		||||
  // Instead, it buffers them. All side effects from multiple jobs queued in the
 | 
			
		||||
  // same tick are flushed together during the "commit" phase. This allows us to
 | 
			
		||||
  // perform side-effect-free work over multiple frames (yielding to the browser
 | 
			
		||||
  // in-between to keep the app responsive), and only flush all the side effects
 | 
			
		||||
  // together when all work is done (AKA time-slicing).
 | 
			
		||||
  (): T
 | 
			
		||||
  // A job's status changes over the different update phaes. See comments for
 | 
			
		||||
  // phases below.
 | 
			
		||||
  status: JobStatus
 | 
			
		||||
  // Any operations performed by the job that directly mutates the DOM are
 | 
			
		||||
  // buffered inside the job's ops queue, and only flushed in the commit phase.
 | 
			
		||||
  // These ops are queued by calling `queueNodeOp` inside the job function.
 | 
			
		||||
  ops: Op[]
 | 
			
		||||
  // Any post DOM mutation side-effects (updated / mounted hooks, refs) are
 | 
			
		||||
  // buffered inside the job's effects queue.
 | 
			
		||||
  // Effects are queued by calling `queueEffect` inside the job function.
 | 
			
		||||
  effects: Function[]
 | 
			
		||||
  // A job may queue other jobs (e.g. a parent component update triggers the
 | 
			
		||||
  // update of a child component). Jobs queued by another job is kept in the
 | 
			
		||||
  // parent's children array, so that in case the parent job is invalidated,
 | 
			
		||||
  // all its children can be invalidated as well (recursively).
 | 
			
		||||
  children: Job[]
 | 
			
		||||
  // Sometimes it's inevitable for a stage fn to produce some side effects
 | 
			
		||||
  // (e.g. a component instance sets up an Autorun). In those cases the stage fn
 | 
			
		||||
  // can return a cleanup function which will be called when the job is
 | 
			
		||||
  // invalidated.
 | 
			
		||||
  cleanup: T | null
 | 
			
		||||
  // The expiration time is a timestamp past which the job needs to
 | 
			
		||||
  // be force-committed regardless of frame budget.
 | 
			
		||||
  // Why do we need an expiration time? Because a job may get invalidated before
 | 
			
		||||
  // it is fully commited. If it keeps getting invalidated, we may "starve" the
 | 
			
		||||
  // system and never apply any commits as jobs keep getting invalidated. The
 | 
			
		||||
  // expiration time sets a limit on how long before a job can keep getting
 | 
			
		||||
  // invalidated before it must be comitted.
 | 
			
		||||
  expiration: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const enum JobStatus {
 | 
			
		||||
  IDLE = 0,
 | 
			
		||||
  PENDING_PATCH,
 | 
			
		||||
  PENDING_STAGE,
 | 
			
		||||
  PENDING_COMMIT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Job extends Function {
 | 
			
		||||
  status: JobStatus
 | 
			
		||||
  ops: Op[]
 | 
			
		||||
  post: Function[]
 | 
			
		||||
  children: Job[]
 | 
			
		||||
  cleanup: Function | null
 | 
			
		||||
  expiration: number
 | 
			
		||||
// Priorities for different types of jobs. This number is added to the
 | 
			
		||||
// current time when a new job is queued to calculate the expiration time
 | 
			
		||||
// for that job.
 | 
			
		||||
//
 | 
			
		||||
// Currently we have only one type which expires 500ms after it is initially
 | 
			
		||||
// queued. There could be higher/lower priorities in the future.
 | 
			
		||||
const enum JobPriorities {
 | 
			
		||||
  NORMAL = 500
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// There can be only one job being patched at one time. This allows us to
 | 
			
		||||
// automatically "capture" and buffer the node ops and post effects queued
 | 
			
		||||
// during a job.
 | 
			
		||||
let currentJob: Job | null = null
 | 
			
		||||
 | 
			
		||||
// Indicates we have a flush pending.
 | 
			
		||||
let hasPendingFlush = false
 | 
			
		||||
 | 
			
		||||
// A timestamp that indicates when a flush was started.
 | 
			
		||||
let flushStartTimestamp: number = 0
 | 
			
		||||
 | 
			
		||||
// The frame budget is the maximum amount of time passed while performing
 | 
			
		||||
// "stage" work before we need to yield back to the browser.
 | 
			
		||||
// Aiming for 60fps. Maybe we need to dynamically adjust this?
 | 
			
		||||
const frameBudget = __JSDOM__ ? Infinity : 1000 / 60
 | 
			
		||||
 | 
			
		||||
const getNow = () => performance.now()
 | 
			
		||||
 | 
			
		||||
// An entire update consists of 4 phases:
 | 
			
		||||
 | 
			
		||||
// 1. Stage phase. Render functions are called, diffs are performed, new
 | 
			
		||||
//    component instances are created. However, no side-effects should be
 | 
			
		||||
//    performed (i.e. no lifecycle hooks, no direct DOM operations).
 | 
			
		||||
const stageQueue: Job[] = []
 | 
			
		||||
 | 
			
		||||
// 2. Commit phase. This is only reached when the stageQueue has been depleted.
 | 
			
		||||
//    Node ops are applied - in the browser, this means DOM is actually mutated
 | 
			
		||||
//    during this phase. If a job is committed, it's post effects are then
 | 
			
		||||
//    queued for the next phase.
 | 
			
		||||
const commitQueue: Job[] = []
 | 
			
		||||
 | 
			
		||||
// 3. Post-commit effects phase. Effect callbacks are only queued after a
 | 
			
		||||
//    successful commit. These include callbacks that need to be invoked
 | 
			
		||||
//    after DOM mutation - i.e. refs, mounted & updated hooks. This queue is
 | 
			
		||||
//    flushed in reverse because child component effects are queued after but
 | 
			
		||||
//    should be invoked before the parent's.
 | 
			
		||||
const postEffectsQueue: Function[] = []
 | 
			
		||||
 | 
			
		||||
// 4. NextTick phase. This is the user's catch-all mechanism for deferring
 | 
			
		||||
//    work after a complete update cycle.
 | 
			
		||||
const nextTickQueue: Function[] = []
 | 
			
		||||
const pendingRejectors: ErrorHandler[] = []
 | 
			
		||||
 | 
			
		||||
// Error handling --------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
type ErrorHandler = (err: Error) => any
 | 
			
		||||
 | 
			
		||||
let currentJob: Job | null = null
 | 
			
		||||
 | 
			
		||||
let start: number = 0
 | 
			
		||||
const getNow = () => performance.now()
 | 
			
		||||
const frameBudget = __JSDOM__ ? Infinity : 1000 / 60
 | 
			
		||||
 | 
			
		||||
const patchQueue: Job[] = []
 | 
			
		||||
const commitQueue: Job[] = []
 | 
			
		||||
const postCommitQueue: Function[] = []
 | 
			
		||||
const nextTickQueue: Function[] = []
 | 
			
		||||
 | 
			
		||||
let globalHandler: ErrorHandler
 | 
			
		||||
const pendingRejectors: ErrorHandler[] = []
 | 
			
		||||
 | 
			
		||||
// Microtask for batching state mutations
 | 
			
		||||
export function handleSchedulerError(handler: ErrorHandler) {
 | 
			
		||||
  globalHandler = handler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleError(err: Error) {
 | 
			
		||||
  if (globalHandler) globalHandler(err)
 | 
			
		||||
  pendingRejectors.forEach(handler => {
 | 
			
		||||
    handler(err)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Microtask defer -------------------------------------------------------------
 | 
			
		||||
// For batching state mutations before we start an update. This does
 | 
			
		||||
// NOT yield to the browser.
 | 
			
		||||
 | 
			
		||||
const p = Promise.resolve()
 | 
			
		||||
 | 
			
		||||
function flushAfterMicroTask() {
 | 
			
		||||
  start = getNow()
 | 
			
		||||
  flushStartTimestamp = getNow()
 | 
			
		||||
  return p.then(flush).catch(handleError)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Macrotask for time slicing
 | 
			
		||||
// Macrotask defer -------------------------------------------------------------
 | 
			
		||||
// For time slicing. This uses the window postMessage event to "yield"
 | 
			
		||||
// to the browser so that other user events can trigger in between. This keeps
 | 
			
		||||
// the app responsive even when performing large amount of JavaScript work.
 | 
			
		||||
 | 
			
		||||
const key = `$vueTick`
 | 
			
		||||
 | 
			
		||||
window.addEventListener(
 | 
			
		||||
@ -54,7 +153,7 @@ window.addEventListener(
 | 
			
		||||
    if (event.source !== window || event.data !== key) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    start = getNow()
 | 
			
		||||
    flushStartTimestamp = getNow()
 | 
			
		||||
    try {
 | 
			
		||||
      flush()
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
@ -68,6 +167,65 @@ function flushAfterMacroTask() {
 | 
			
		||||
  window.postMessage(key, `*`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// API -------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
// This is the main API of the scheduler. The raw job can actually be any
 | 
			
		||||
// function, but since they are invalidated by identity, it is important that
 | 
			
		||||
// a component's update job is a consistent function across its lifecycle -
 | 
			
		||||
// in the renderer, it's actually instance._updateHandle which is in turn
 | 
			
		||||
// an Autorun function.
 | 
			
		||||
export function queueJob(rawJob: Function) {
 | 
			
		||||
  const job = rawJob as Job
 | 
			
		||||
  if (currentJob) {
 | 
			
		||||
    currentJob.children.push(job)
 | 
			
		||||
  }
 | 
			
		||||
  // Let's see if this invalidates any work that
 | 
			
		||||
  // has already been staged.
 | 
			
		||||
  if (job.status === JobStatus.PENDING_COMMIT) {
 | 
			
		||||
    // staged job invalidated
 | 
			
		||||
    invalidateJob(job)
 | 
			
		||||
    // re-insert it into the stage queue
 | 
			
		||||
    requeueInvalidatedJob(job)
 | 
			
		||||
  } else if (job.status !== JobStatus.PENDING_STAGE) {
 | 
			
		||||
    // a new job
 | 
			
		||||
    queueJobForStaging(job)
 | 
			
		||||
  }
 | 
			
		||||
  if (!hasPendingFlush) {
 | 
			
		||||
    hasPendingFlush = true
 | 
			
		||||
    flushAfterMicroTask()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function queueEffect(fn: Function) {
 | 
			
		||||
  if (currentJob) {
 | 
			
		||||
    currentJob.effects.push(fn)
 | 
			
		||||
  } else {
 | 
			
		||||
    postEffectsQueue.push(fn)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function flushEffects() {
 | 
			
		||||
  // post commit hooks (updated, mounted)
 | 
			
		||||
  // this queue is flushed in reverse becuase these hooks should be invoked
 | 
			
		||||
  // child first
 | 
			
		||||
  let i = postEffectsQueue.length
 | 
			
		||||
  while (i--) {
 | 
			
		||||
    postEffectsQueue[i]()
 | 
			
		||||
  }
 | 
			
		||||
  postEffectsQueue.length = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function queueNodeOp(op: Op) {
 | 
			
		||||
  if (currentJob) {
 | 
			
		||||
    currentJob.ops.push(op)
 | 
			
		||||
  } else {
 | 
			
		||||
    applyOp(op)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The original nextTick now needs to be reworked so that the callback only
 | 
			
		||||
// triggers after the next commit, when all node ops and post effects have been
 | 
			
		||||
// completed.
 | 
			
		||||
export function nextTick<T>(fn?: () => T): Promise<T> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    p.then(() => {
 | 
			
		||||
@ -86,108 +244,35 @@ export function nextTick<T>(fn?: () => T): Promise<T> {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleError(err: Error) {
 | 
			
		||||
  if (globalHandler) globalHandler(err)
 | 
			
		||||
  pendingRejectors.forEach(handler => {
 | 
			
		||||
    handler(err)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function handleSchedulerError(handler: ErrorHandler) {
 | 
			
		||||
  globalHandler = handler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let hasPendingFlush = false
 | 
			
		||||
 | 
			
		||||
export function queueJob(rawJob: Function) {
 | 
			
		||||
  const job = rawJob as Job
 | 
			
		||||
  if (currentJob) {
 | 
			
		||||
    currentJob.children.push(job)
 | 
			
		||||
  }
 | 
			
		||||
  // 1. let's see if this invalidates any work that
 | 
			
		||||
  // has already been done.
 | 
			
		||||
  if (job.status === JobStatus.PENDING_COMMIT) {
 | 
			
		||||
    // pending commit job invalidated
 | 
			
		||||
    invalidateJob(job)
 | 
			
		||||
    requeueInvalidatedJob(job)
 | 
			
		||||
  } else if (job.status !== JobStatus.PENDING_PATCH) {
 | 
			
		||||
    // a new job
 | 
			
		||||
    insertNewJob(job)
 | 
			
		||||
  }
 | 
			
		||||
  if (!hasPendingFlush) {
 | 
			
		||||
    hasPendingFlush = true
 | 
			
		||||
    flushAfterMicroTask()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function requeueInvalidatedJob(job: Job) {
 | 
			
		||||
  // With varying priorities we should insert job at correct position
 | 
			
		||||
  // based on expiration time.
 | 
			
		||||
  for (let i = 0; i < patchQueue.length; i++) {
 | 
			
		||||
    if (job.expiration < patchQueue[i].expiration) {
 | 
			
		||||
      patchQueue.splice(i, 0, job)
 | 
			
		||||
      job.status = JobStatus.PENDING_PATCH
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  patchQueue.push(job)
 | 
			
		||||
  job.status = JobStatus.PENDING_PATCH
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function queuePostCommitCb(fn: Function) {
 | 
			
		||||
  if (currentJob) {
 | 
			
		||||
    currentJob.post.push(fn)
 | 
			
		||||
  } else {
 | 
			
		||||
    postCommitQueue.push(fn)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function flushPostCommitCbs() {
 | 
			
		||||
  // post commit hooks (updated, mounted)
 | 
			
		||||
  // this queue is flushed in reverse becuase these hooks should be invoked
 | 
			
		||||
  // child first
 | 
			
		||||
  let i = postCommitQueue.length
 | 
			
		||||
  while (i--) {
 | 
			
		||||
    postCommitQueue[i]()
 | 
			
		||||
  }
 | 
			
		||||
  postCommitQueue.length = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function queueNodeOp(op: Op) {
 | 
			
		||||
  if (currentJob) {
 | 
			
		||||
    currentJob.ops.push(op)
 | 
			
		||||
  } else {
 | 
			
		||||
    applyOp(op)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Internals -------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
function flush(): void {
 | 
			
		||||
  let job
 | 
			
		||||
  while (true) {
 | 
			
		||||
    job = patchQueue.shift()
 | 
			
		||||
    job = stageQueue.shift()
 | 
			
		||||
    if (job) {
 | 
			
		||||
      patchJob(job)
 | 
			
		||||
      stageJob(job)
 | 
			
		||||
    } else {
 | 
			
		||||
      break
 | 
			
		||||
    }
 | 
			
		||||
    if (!__COMPAT__) {
 | 
			
		||||
      const now = getNow()
 | 
			
		||||
      if (now - start > frameBudget && job.expiration > now) {
 | 
			
		||||
      if (now - flushStartTimestamp > frameBudget && job.expiration > now) {
 | 
			
		||||
        break
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (patchQueue.length === 0) {
 | 
			
		||||
  if (stageQueue.length === 0) {
 | 
			
		||||
    // all done, time to commit!
 | 
			
		||||
    for (let i = 0; i < commitQueue.length; i++) {
 | 
			
		||||
      commitJob(commitQueue[i])
 | 
			
		||||
    }
 | 
			
		||||
    commitQueue.length = 0
 | 
			
		||||
    flushPostCommitCbs()
 | 
			
		||||
    flushEffects()
 | 
			
		||||
    // some post commit hook triggered more updates...
 | 
			
		||||
    if (patchQueue.length > 0) {
 | 
			
		||||
      if (!__COMPAT__ && getNow() - start > frameBudget) {
 | 
			
		||||
    if (stageQueue.length > 0) {
 | 
			
		||||
      if (!__COMPAT__ && getNow() - flushStartTimestamp > frameBudget) {
 | 
			
		||||
        return flushAfterMacroTask()
 | 
			
		||||
      } else {
 | 
			
		||||
        // not out of budget yet, flush sync
 | 
			
		||||
@ -203,29 +288,29 @@ function flush(): void {
 | 
			
		||||
    nextTickQueue.length = 0
 | 
			
		||||
  } else {
 | 
			
		||||
    // got more job to do
 | 
			
		||||
    // shouldn't reach here in compat mode, because the patchQueue is
 | 
			
		||||
    // guarunteed to be drained
 | 
			
		||||
    // shouldn't reach here in compat mode, because the stageQueue is
 | 
			
		||||
    // guarunteed to have been depleted
 | 
			
		||||
    flushAfterMacroTask()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function resetJob(job: Job) {
 | 
			
		||||
  job.ops.length = 0
 | 
			
		||||
  job.post.length = 0
 | 
			
		||||
  job.effects.length = 0
 | 
			
		||||
  job.children.length = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function insertNewJob(job: Job) {
 | 
			
		||||
function queueJobForStaging(job: Job) {
 | 
			
		||||
  job.ops = job.ops || []
 | 
			
		||||
  job.post = job.post || []
 | 
			
		||||
  job.effects = job.effects || []
 | 
			
		||||
  job.children = job.children || []
 | 
			
		||||
  resetJob(job)
 | 
			
		||||
  // inherit parent job's expiration deadline
 | 
			
		||||
  job.expiration = currentJob
 | 
			
		||||
    ? currentJob.expiration
 | 
			
		||||
    : getNow() + Priorities.NORMAL
 | 
			
		||||
  patchQueue.push(job)
 | 
			
		||||
  job.status = JobStatus.PENDING_PATCH
 | 
			
		||||
    : getNow() + JobPriorities.NORMAL
 | 
			
		||||
  stageQueue.push(job)
 | 
			
		||||
  job.status = JobStatus.PENDING_STAGE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function invalidateJob(job: Job) {
 | 
			
		||||
@ -235,8 +320,8 @@ function invalidateJob(job: Job) {
 | 
			
		||||
    const child = children[i]
 | 
			
		||||
    if (child.status === JobStatus.PENDING_COMMIT) {
 | 
			
		||||
      invalidateJob(child)
 | 
			
		||||
    } else if (child.status === JobStatus.PENDING_PATCH) {
 | 
			
		||||
      patchQueue.splice(patchQueue.indexOf(child), 1)
 | 
			
		||||
    } else if (child.status === JobStatus.PENDING_STAGE) {
 | 
			
		||||
      stageQueue.splice(stageQueue.indexOf(child), 1)
 | 
			
		||||
      child.status = JobStatus.IDLE
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -250,7 +335,21 @@ function invalidateJob(job: Job) {
 | 
			
		||||
  job.status = JobStatus.IDLE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function patchJob(job: Job) {
 | 
			
		||||
function requeueInvalidatedJob(job: Job) {
 | 
			
		||||
  // With varying priorities we should insert job at correct position
 | 
			
		||||
  // based on expiration time.
 | 
			
		||||
  for (let i = 0; i < stageQueue.length; i++) {
 | 
			
		||||
    if (job.expiration < stageQueue[i].expiration) {
 | 
			
		||||
      stageQueue.splice(i, 0, job)
 | 
			
		||||
      job.status = JobStatus.PENDING_STAGE
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  stageQueue.push(job)
 | 
			
		||||
  job.status = JobStatus.PENDING_STAGE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stageJob(job: Job) {
 | 
			
		||||
  // job with existing ops means it's already been patched in a low priority queue
 | 
			
		||||
  if (job.ops.length === 0) {
 | 
			
		||||
    currentJob = job
 | 
			
		||||
@ -262,13 +361,13 @@ function patchJob(job: Job) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function commitJob(job: Job) {
 | 
			
		||||
  const { ops, post } = job
 | 
			
		||||
  const { ops, effects } = job
 | 
			
		||||
  for (let i = 0; i < ops.length; i++) {
 | 
			
		||||
    applyOp(ops[i])
 | 
			
		||||
  }
 | 
			
		||||
  // queue post commit cbs
 | 
			
		||||
  if (post) {
 | 
			
		||||
    postCommitQueue.push(...post)
 | 
			
		||||
  if (effects) {
 | 
			
		||||
    postEffectsQueue.push(...effects)
 | 
			
		||||
  }
 | 
			
		||||
  resetJob(job)
 | 
			
		||||
  job.status = JobStatus.IDLE
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user