Skip to content

Commit a1449b5

Browse files
committed
feat (schedulers): improve low prio scheduling
1 parent 72ef019 commit a1449b5

File tree

9 files changed

+214
-181
lines changed

9 files changed

+214
-181
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"karma-chrome-launcher": "^2.2.0",
5151
"karma-coverage": "^1.1.1",
5252
"karma-mocha": "^1.3.0",
53+
"karma-mocha-reporter": "^2.2.5",
5354
"karma-rollup-preprocessor": "^5.0.1",
5455
"karma-source-map-support": "^1.2.0",
5556
"markdown-toc": "^1.1.0",

scripts/test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const bundlePath = bundleType ? `dist/${bundleType}.js` : 'src/index.js'
1111

1212
const config = {
1313
frameworks: ['mocha', 'chai', 'source-map-support'],
14-
reporters: ['progress', 'coverage'],
14+
reporters: ['progress', 'mocha', 'coverage'],
1515
files: ['tests/**/*.test.js'],
1616
preprocessors: {
1717
'tests/**/*.test.js': ['rollup']

src/processing.js

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { nextTick, nextAnimationFrame, nextIdlePeriod } from './timers'
1+
import { nextTick, nextAnimationFrame, nextIdlePeriod } from './schedulers'
22
import { queues, priorities } from './priorities'
33

44
const TARGET_FPS = 60
55
const TARGET_INTERVAL = 1000 / TARGET_FPS
6-
let lastRun
76

87
export function queueTaskProcessing (priority) {
98
if (priority === priorities.CRITICAL) {
109
nextTick(runQueuedCriticalTasks)
1110
} else if (priority === priorities.HIGH) {
1211
nextAnimationFrame(runQueuedHighTasks)
13-
} else if (priority === priorities.LOW) {
12+
} else {
1413
nextIdlePeriod(runQueuedLowTasks)
1514
}
1615
}
@@ -27,57 +26,41 @@ function processCriticalQueue (queue) {
2726
}
2827

2928
function runQueuedHighTasks () {
30-
// the env is not idle
31-
// only allow it to run for time remaining part of the current period
32-
lastRun = lastRun || performance.now()
33-
34-
const timeRemaining = processIdleQueues(priorities.HIGH)
35-
36-
// if there is free time remaining there are no more tasks to run
37-
if (timeRemaining) {
38-
lastRun = undefined
39-
} else {
29+
const startTime = performance.now()
30+
const isEmpty = processIdleQueues(priorities.HIGH, startTime)
31+
// there are more tasks to run in the next cycle
32+
if (!isEmpty) {
4033
nextAnimationFrame(runQueuedHighTasks)
41-
lastRun = performance.now()
4234
}
4335
}
4436

4537
function runQueuedLowTasks () {
46-
// the env is idle, allow the handler to run for a full period
47-
lastRun = performance.now()
48-
49-
// first check if there are pending high prio tasks
50-
let timeRemaining = processIdleQueues(priorities.HIGH)
51-
52-
if (timeRemaining) {
53-
timeRemaining = processIdleQueues(priorities.LOW)
54-
}
55-
56-
// if there is free time remaining there are no more tasks to run
57-
if (timeRemaining) {
58-
lastRun = undefined
59-
} else {
38+
const startTime = performance.now()
39+
const isEmpty = processIdleQueues(priorities.LOW, startTime)
40+
// there are more tasks to run in the next cycle
41+
if (!isEmpty) {
6042
nextIdlePeriod(runQueuedLowTasks)
61-
lastRun = performance.now()
6243
}
6344
}
6445

65-
function processIdleQueues (priority) {
46+
function processIdleQueues (priority, startTime) {
6647
const idleQueues = queues[priority]
67-
let timeRemaining = true
48+
let isEmpty = true
6849

69-
for (let i = 0; timeRemaining && i < idleQueues.length; i++) {
50+
// if a queue is not empty after processing, it means we have no more time
51+
// the loop whould stop in this case
52+
for (let i = 0; isEmpty && i < idleQueues.length; i++) {
7053
const queue = idleQueues.shift()
71-
timeRemaining = timeRemaining && processIdleQueue(queue)
54+
isEmpty = isEmpty && processIdleQueue(queue, startTime)
7255
idleQueues.push(queue)
7356
}
74-
return timeRemaining
57+
return isEmpty
7558
}
7659

77-
function processIdleQueue (queue) {
60+
function processIdleQueue (queue, startTime) {
7861
const iterator = queue[Symbol.iterator]()
7962
let task = iterator.next()
80-
while (performance.now() - lastRun < TARGET_INTERVAL) {
63+
while (performance.now() - startTime < TARGET_INTERVAL) {
8164
if (task.done) {
8265
return true
8366
}

src/schedulers.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
let tickTask
2+
let rafTask
3+
let ricTask
4+
5+
const currentTick = Promise.resolve()
6+
7+
function getRaf () {
8+
return typeof requestAnimationFrame === 'function'
9+
? requestAnimationFrame
10+
: setTimeout
11+
}
12+
13+
function getRic () {
14+
return typeof requestIdleCallback === 'function'
15+
? requestIdleCallback
16+
: getRaf()
17+
}
18+
19+
export function nextTick (task) {
20+
if (!tickTask) {
21+
tickTask = task
22+
currentTick.then(runTickTask)
23+
}
24+
}
25+
26+
function runTickTask () {
27+
const task = tickTask
28+
tickTask = undefined
29+
task()
30+
}
31+
32+
export function nextAnimationFrame (task) {
33+
if (!rafTask) {
34+
rafTask = task
35+
const raf = getRaf()
36+
raf(runRafTask)
37+
}
38+
}
39+
40+
function runRafTask () {
41+
const task = rafTask
42+
rafTask = undefined
43+
task()
44+
}
45+
46+
export function nextIdlePeriod (task) {
47+
if (!ricTask) {
48+
ricTask = task
49+
const ric = getRic()
50+
ric(runRicTask)
51+
}
52+
}
53+
54+
function runRicTask () {
55+
if (!rafTask) {
56+
const task = ricTask
57+
ricTask = undefined
58+
task()
59+
} else {
60+
const ric = getRic()
61+
ric(runRicTask)
62+
}
63+
}

src/timers.js

Lines changed: 0 additions & 46 deletions
This file was deleted.

tests/env._test.js

Lines changed: 0 additions & 26 deletions
This file was deleted.

tests/env.test.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// remove requestAnimationFrame and requestIdleCallback before the queue schedulers load
2+
import { expect } from 'chai'
3+
import { Queue, priorities } from '@nx-js/queue-util'
4+
import { removeRIC, restoreRIC, removeRAF, restoreRAF, beforeNextFrame, heavyCalculation } from './utils'
5+
6+
describe('environments', () => {
7+
describe('NodeJS', () => {
8+
before(() => {
9+
removeRIC()
10+
removeRAF()
11+
})
12+
13+
after(() => {
14+
restoreRIC()
15+
restoreRAF()
16+
})
17+
18+
it('should run all critical tasks before high prio tasks before low prio tasks', async () => {
19+
let criticalRuns = 0
20+
let highRuns = 0
21+
let lowRuns = 0
22+
23+
const criticalQueue = new Queue(priorities.CRITICAL)
24+
const highQueue = new Queue(priorities.HIGH)
25+
const lowQueue = new Queue(priorities.LOW)
26+
27+
for (let i = 0; i < 10; i++) {
28+
criticalQueue.add(() => {
29+
criticalRuns++
30+
heavyCalculation()
31+
})
32+
33+
highQueue.add(() => {
34+
highRuns++
35+
heavyCalculation()
36+
})
37+
38+
lowQueue.add(() => {
39+
lowRuns++
40+
heavyCalculation()
41+
})
42+
}
43+
44+
await criticalQueue.processing()
45+
expect(criticalRuns).to.equal(10)
46+
expect(highRuns).to.equal(0)
47+
expect(lowRuns).to.equal(0)
48+
await highQueue.processing()
49+
expect(criticalRuns).to.equal(10)
50+
expect(highRuns).to.equal(10)
51+
expect(lowRuns).to.equal(0)
52+
await lowQueue.processing()
53+
expect(criticalRuns).to.equal(10)
54+
expect(highRuns).to.equal(10)
55+
expect(lowRuns).to.equal(10)
56+
})
57+
})
58+
59+
describe('older browsers', () => {
60+
before(() => {
61+
removeRIC()
62+
})
63+
64+
after(() => {
65+
restoreRIC()
66+
})
67+
68+
it('should run all critical tasks before high prio tasks before low prio tasks', async () => {
69+
let criticalRuns = 0
70+
let highRuns = 0
71+
let lowRuns = 0
72+
73+
const criticalQueue = new Queue(priorities.CRITICAL)
74+
const highQueue = new Queue(priorities.HIGH)
75+
const lowQueue = new Queue(priorities.LOW)
76+
77+
for (let i = 0; i < 10; i++) {
78+
criticalQueue.add(() => {
79+
criticalRuns++
80+
heavyCalculation()
81+
})
82+
83+
highQueue.add(() => {
84+
highRuns++
85+
heavyCalculation()
86+
})
87+
88+
lowQueue.add(() => {
89+
lowRuns++
90+
heavyCalculation()
91+
})
92+
}
93+
94+
await criticalQueue.processing()
95+
expect(criticalRuns).to.equal(10)
96+
expect(highRuns).to.equal(0)
97+
expect(lowRuns).to.equal(0)
98+
await highQueue.processing()
99+
expect(criticalRuns).to.equal(10)
100+
expect(highRuns).to.equal(10)
101+
expect(lowRuns).to.equal(0)
102+
await lowQueue.processing()
103+
expect(criticalRuns).to.equal(10)
104+
expect(highRuns).to.equal(10)
105+
expect(lowRuns).to.equal(10)
106+
})
107+
})
108+
})

0 commit comments

Comments
 (0)