Skip to content

Instantly share code, notes, and snippets.

@evelant
Last active December 18, 2023 19:02
Show Gist options
  • Save evelant/c7d2b79af62a3864771d63c4d53a1d9a to your computer and use it in GitHub Desktop.
Save evelant/c7d2b79af62a3864771d63c4d53a1d9a to your computer and use it in GitHub Desktop.
Frame aware scheduler for Effect
class FrameAwareReactScheduler implements Scheduler.Scheduler {
tasks: Array<Scheduler.Task> = []
scheduledRaf: number | undefined = undefined
constructor(readonly maxMsBeforeFrame: number) {}
isRunning = false
scheduleFrame() {
if (this.scheduledRaf === undefined && !this.isRunning) {
this.scheduledRaf = requestAnimationFrame(() => {
this.isRunning = true
this.scheduledRaf = undefined
this.run()
})
}
}
debug = false
private run() {
const startedThisBatchAt = performance.now()
runIdent += 1
const toRun = this.tasks
this.tasks = []
let processedUpTo = 0
let brokeForFrame = false
let brokeAt = 0
unstable_batchedUpdates(() => {
if (this.debug) console.log(`${runIdent}: FrameAwareScheduler -- running ${toRun.length} tasks`)
for (let i = 0; i < toRun.length; i++) {
toRun[i]()
processedUpTo = i
// console.log(`${runIdent}: ran ${i + 1} of ${toRun.length} -- processedUpTo ${processedUpTo}`)
const now = performance.now()
if (now - startedThisBatchAt > this.maxMsBeforeFrame && i + 1 < toRun.length) {
if (this.debug)
console.log(
`${runIdent}: FrameAwareScheduler -- interrupting batch of ${toRun.length} for frame, ${
processedUpTo + 1
} tasks processed. ${this.tasks.length} new tasks in queue, ${
toRun.length - processedUpTo - 1
} remain in toRun. Exceeded frame time by ${
now - startedThisBatchAt - this.maxMsBeforeFrame
} `,
)
this.tasks = [...toRun.slice(processedUpTo + 1), ...this.tasks]
brokeForFrame = true
brokeAt = now
break
}
}
})
if (this.tasks.length === 0) {
if (this.debug) console.log(`FrameAwareScheduler -- all tasks complete`)
this.isRunning = false
} else if (brokeForFrame) {
if (this.debug)
console.log(
`${runIdent}: FrameAwareScheduler -- batch exceeded frame time (processed ${
processedUpTo + 1
} tasks in ${brokeAt - startedThisBatchAt}ms) exceeding frame time by ${
brokeAt - startedThisBatchAt - this.maxMsBeforeFrame
}ms -- scheduling next batch of ${this.tasks.length} tasks after frame`,
)
// InteractionManager.runAfterInteractions(() => {
this.isRunning = false
this.scheduleFrame()
// })
} else {
this.tasks = [...toRun.slice(processedUpTo + 1), ...this.tasks]
if (this.debug)
console.log(
`${runIdent}: FrameAwareScheduler -- promise tick for next batch of ${this.tasks.length} tasks`,
)
Promise.resolve(void 0).then(() => this.run())
// this.scheduleFrame()
}
}
scheduleTask(task: Scheduler.Task) {
this.tasks.push(task)
this.scheduleFrame()
}
shouldYield(fiber: Fiber.RuntimeFiber<unknown, unknown>): number | false {
return fiber.currentOpCount > fiber.getFiberRef(FiberRef.currentMaxOpsBeforeYield)
? fiber.getFiberRef(FiberRef.currentSchedulingPriority)
: false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment