From ce935be52a1153239db17ac777bded03188d74f3 Mon Sep 17 00:00:00 2001 From: Antonio Stoilkov Date: Thu, 1 Feb 2024 14:28:25 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix=20bugs=20with=20the=20new=20?= =?UTF-8?q?implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/index.html | 7 ++-- playground/playground.ts | 67 +++++++++++++++++++++++++++++++++------ src/Scheduler.ts | 11 ++++--- src/frameTracker.ts | 48 +++++++++++----------------- src/ricTracker.ts | 24 ++++++-------- src/utils/ReactiveTask.ts | 12 +++---- test.ts | 10 ++++++ 7 files changed, 113 insertions(+), 66 deletions(-) create mode 100644 test.ts diff --git a/playground/index.html b/playground/index.html index 31a119f..6c982ff 100644 --- a/playground/index.html +++ b/playground/index.html @@ -31,10 +31,11 @@
- + - - + + +
diff --git a/playground/playground.ts b/playground/playground.ts index 354d0f0..a633ccb 100644 --- a/playground/playground.ts +++ b/playground/playground.ts @@ -1,26 +1,32 @@ import { isTimeToYield, SchedulingStrategy, yieldOrContinue } from '../index' +document.querySelector('#run-interactive')!.addEventListener('click', () => { + run('interactive') +}) document.querySelector('#run-smooth')!.addEventListener('click', () => { run('smooth') }) -document.querySelector('#run-user-blocking')!.addEventListener('click', () => { - run('interactive') -}) -document.querySelector('#run-background')!.addEventListener('click', () => { +document.querySelector('#run-idle')!.addEventListener('click', () => { run('idle') }) -document.querySelector('#run-all')!.addEventListener('click', async () => { +document.querySelector('#run-all-sequential')!.addEventListener('click', async () => { + await run('interactive') + await run('smooth') + await run('idle') +}) +document.querySelector('#run-all-parallel')!.addEventListener('click', async () => { run('interactive') - run('smooth') - run('idle') + run('smooth', 2000) + run('idle', 3000) }) -async function run(strategy: SchedulingStrategy) { +async function run(strategy: SchedulingStrategy, time: number = 1000) { const start = Date.now() - while (Date.now() - start < 1000) { + while (Date.now() - start < time) { if (isTimeToYield(strategy)) { await yieldOrContinue(strategy) } + simulateWork() } performance.measure(strategy, { start: start, @@ -41,6 +47,7 @@ document.querySelector('#post-task-background')!.addEventListener('click', () => async function runPostTask(priority: 'user-blocking' | 'user-visible' | 'background') { for (let i = 0; i < 5; i++) { + // @ts-ignore scheduler.postTask( () => { const start = Date.now() @@ -52,3 +59,45 @@ async function runPostTask(priority: 'user-blocking' | 'user-visible' | 'backgro ) } } + +function simulateWork(): void { + // a 5x5 matrix + const matrixA = [ + [1, 2, 3, 4, 5], + [4, 5, 6, 7, 8], + [7, 8, 9, 10, 11], + [7, 8, 9, 10, 11], + [7, 8, 9, 10, 11], + ] + const matrixB = [ + [1, 2, 3, 4, 5], + [4, 5, 6, 7, 8], + [7, 8, 9, 10, 11], + [7, 8, 9, 10, 11], + [7, 8, 9, 10, 11], + ] + for (let i = 0; i < 5000; i++) { + matrixMultiplication(matrixA, matrixB) + } +} + +function matrixMultiplication(matrix1: number[][], matrix2: number[][]) { + const result = [] + const rows1 = matrix1.length + const cols1 = matrix1[0]!.length + const cols2 = matrix2[0]!.length + + for (var i = 0; i < rows1; i++) { + result[i] = [] + for (var j = 0; j < cols2; j++) { + // @ts-ignore + result[i][j] = 0 + for (var k = 0; k < cols1; k++) { + // @ts-ignore + result[i][j] += matrix1[i][k] * matrix2[k][j] + } + } + } + + return result +} diff --git a/src/Scheduler.ts b/src/Scheduler.ts index 9efa57d..97cd768 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -6,9 +6,9 @@ import { requestPromiseEscape } from './utils/promiseEscape' import ReactiveTask from './utils/ReactiveTask' const strategyPriorities = { - interactive: 0, - smooth: 1, - idle: 2, + interactive: 30, + smooth: 20, + idle: 10, } class Scheduler { @@ -76,8 +76,9 @@ class Scheduler { if (index !== -1) { this.#tasks.splice(index, 1) } - - this.#topTask.set(this.#tasks[0]) + if (index === 0) { + this.#topTask.set(this.#tasks[0]) + } } } diff --git a/src/frameTracker.ts b/src/frameTracker.ts index a38049d..a672544 100644 --- a/src/frameTracker.ts +++ b/src/frameTracker.ts @@ -1,60 +1,50 @@ -import withResolvers from './utils/withResolvers' +import withResolvers, { PromiseWithResolvers } from './utils/withResolvers' import { queueTask } from '../index' class FrameTracker { - #resolve: () => void - #promise: Promise #timeoutId?: number #requestAnimationId?: number + #deferred: PromiseWithResolvers constructor() { - const { promise, resolve } = withResolvers() - this.#promise = promise - this.#resolve = resolve + this.#deferred = withResolvers() } async waitAnimationFrame(): Promise { - return this.#promise + return this.#deferred.promise } async waitAfterFrame(): Promise { - await this.#promise + await this.#deferred.promise await new Promise((resolve) => queueTask(resolve)) } start(): void { - if (this.#requestAnimationId !== undefined) { - return - } - - this.#loop() clearTimeout(this.#timeoutId) - this.#timeoutId = undefined + + if (this.#requestAnimationId === undefined) { + this.#requestAnimationId = requestAnimationFrame(() => { + this.#requestAnimationId = undefined + + this.#deferred.resolve() + + this.#deferred = withResolvers() + + this.start() + }) + } } requestStop(): void { if (this.#timeoutId === undefined) { this.#timeoutId = setTimeout(() => { this.#timeoutId = undefined - if (this.#requestAnimationId !== undefined) { - cancelAnimationFrame(this.#requestAnimationId) - } + cancelAnimationFrame(this.#requestAnimationId) + this.#requestAnimationId = undefined }, 200) } } - - #loop(): void { - this.#requestAnimationId = requestAnimationFrame(() => { - this.#resolve() - - const { promise, resolve } = withResolvers() - this.#promise = promise - this.#resolve = resolve - - this.#loop() - }) - } } const frameTracker = new FrameTracker() diff --git a/src/ricTracker.ts b/src/ricTracker.ts index 9057995..f854fc6 100644 --- a/src/ricTracker.ts +++ b/src/ricTracker.ts @@ -1,15 +1,12 @@ -import withResolvers from './utils/withResolvers' +import withResolvers, { PromiseWithResolvers } from './utils/withResolvers' class RicTracker { - #promise: Promise - #resolve: (deadline: IdleDeadline) => void #idleCallbackId?: number #idleDeadline?: IdleDeadline + #deferred: PromiseWithResolvers constructor() { - const { promise, resolve } = withResolvers() - this.#promise = promise - this.#resolve = resolve + this.#deferred = withResolvers() } get available() { @@ -21,7 +18,7 @@ class RicTracker { } async waitIdleCallback(): Promise { - return this.#promise + return this.#deferred.promise } start(): void { @@ -33,18 +30,17 @@ class RicTracker { this.#idleDeadline = deadline this.#idleCallbackId = undefined - this.#resolve?.(deadline) + this.#deferred.resolve(deadline) - const { promise, resolve } = withResolvers() - this.#promise = promise - this.#resolve = resolve + this.#deferred = withResolvers() + + this.start() }) } stop() { - if (this.#idleCallbackId !== undefined) { - cancelIdleCallback(this.#idleCallbackId) - } + cancelIdleCallback(this.#idleCallbackId) + this.#idleCallbackId = undefined } } diff --git a/src/utils/ReactiveTask.ts b/src/utils/ReactiveTask.ts index 11eb199..7e61eef 100644 --- a/src/utils/ReactiveTask.ts +++ b/src/utils/ReactiveTask.ts @@ -8,14 +8,14 @@ export default class ReactiveTask { #effect: (task: ScheduledTask, signal: AbortSignal) => void = () => {} set(task: ScheduledTask | undefined): void { - if (this.#task !== task) { + if (task === undefined) { + this.#task = undefined + this.#controller.abort() + } else if (this.#task !== task) { this.#task = task this.#controller.abort() - if (this.#task !== undefined) { - this.#controller = new AbortController() - - this.#effect(this.#task, this.#controller.signal) - } + this.#controller = new AbortController() + this.#effect(this.#task, this.#controller.signal) } } diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..baefe0c --- /dev/null +++ b/test.ts @@ -0,0 +1,10 @@ +// 👋 Hey! +// I see you want to see the library tests... + +// Previously, the library had tests: +// https://github.com/astoilkov/main-thread-scheduling/blob/0b9797e7bdaf1a4cf375c1be837d66c216459048/test.ts +// However, they were slowing the development and experimentation process, so I removed them. +// I now use the `playground` folder to test the library. +// In the future, when the library is more mature, I will add tests again. + +export {}