Last active
December 18, 2023 19:02
-
-
Save evelant/c7d2b79af62a3864771d63c4d53a1d9a to your computer and use it in GitHub Desktop.
Frame aware scheduler for Effect
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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