Skip to content

Commit

Permalink
🐛 fix bugs with the new implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
astoilkov committed Feb 1, 2024
1 parent d96987c commit ce935be
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 66 deletions.
7 changes: 4 additions & 3 deletions playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
</head>
<body>
<div>
<button id="run-user-blocking">user-blocking</button>
<button id="run-interactive">interactive</button>
<button id="run-smooth">smooth</button>
<button id="run-background">background</button>
<button id="run-all">run all</button>
<button id="run-idle">idle</button>
<button id="run-all-sequential">run all (sequential)</button>
<button id="run-all-parallel">run all (parallel)</button>
</div>
<div>
<button id="post-task-blocking">postTask('user-blocking')</button>
Expand Down
67 changes: 58 additions & 9 deletions playground/playground.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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()
Expand All @@ -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
}
11 changes: 6 additions & 5 deletions src/Scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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])
}
}
}

Expand Down
48 changes: 19 additions & 29 deletions src/frameTracker.ts
Original file line number Diff line number Diff line change
@@ -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<void>
#timeoutId?: number
#requestAnimationId?: number
#deferred: PromiseWithResolvers

constructor() {
const { promise, resolve } = withResolvers()
this.#promise = promise
this.#resolve = resolve
this.#deferred = withResolvers()
}

async waitAnimationFrame(): Promise<void> {
return this.#promise
return this.#deferred.promise
}

async waitAfterFrame(): Promise<void> {
await this.#promise
await this.#deferred.promise
await new Promise<void>((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()
Expand Down
24 changes: 10 additions & 14 deletions src/ricTracker.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import withResolvers from './utils/withResolvers'
import withResolvers, { PromiseWithResolvers } from './utils/withResolvers'

class RicTracker {
#promise: Promise<IdleDeadline>
#resolve: (deadline: IdleDeadline) => void
#idleCallbackId?: number
#idleDeadline?: IdleDeadline
#deferred: PromiseWithResolvers<IdleDeadline>

constructor() {
const { promise, resolve } = withResolvers<IdleDeadline>()
this.#promise = promise
this.#resolve = resolve
this.#deferred = withResolvers<IdleDeadline>()
}

get available() {
Expand All @@ -21,7 +18,7 @@ class RicTracker {
}

async waitIdleCallback(): Promise<IdleDeadline> {
return this.#promise
return this.#deferred.promise
}

start(): void {
Expand All @@ -33,18 +30,17 @@ class RicTracker {
this.#idleDeadline = deadline
this.#idleCallbackId = undefined

this.#resolve?.(deadline)
this.#deferred.resolve(deadline)

const { promise, resolve } = withResolvers<IdleDeadline>()
this.#promise = promise
this.#resolve = resolve
this.#deferred = withResolvers<IdleDeadline>()

this.start()
})
}

stop() {
if (this.#idleCallbackId !== undefined) {
cancelIdleCallback(this.#idleCallbackId)
}
cancelIdleCallback(this.#idleCallbackId)
this.#idleCallbackId = undefined
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/utils/ReactiveTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
10 changes: 10 additions & 0 deletions test.ts
Original file line number Diff line number Diff line change
@@ -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 {}

0 comments on commit ce935be

Please sign in to comment.