Skip to content

Commit 335a450

Browse files
committed
refactor(runtime-core): improve scheduler code reusability
1 parent 52804c7 commit 335a450

File tree

1 file changed

+118
-137
lines changed

1 file changed

+118
-137
lines changed

packages/runtime-core/src/scheduler.ts

Lines changed: 118 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
1+
import { ErrorCodes, handleError } from './errorHandling'
22
import { isArray } from '@vue/shared'
33
import { type GenericComponentInstance, getComponentName } from './component'
44

@@ -39,23 +39,21 @@ export interface SchedulerJob extends Function {
3939

4040
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
4141

42-
const queueMainJobs: (SchedulerJob | undefined)[] = []
43-
const queuePreJobs: (SchedulerJob | undefined)[] = []
44-
const queuePostJobs: (SchedulerJob | undefined)[] = []
42+
const jobs: SchedulerJob[] = []
43+
const preJobs: SchedulerJob[] = []
4544

46-
let mainFlushIndex = -1
47-
let preFlushIndex = -1
48-
let postFlushIndex = 0
49-
let mainJobsLength = 0
50-
let preJobsLength = 0
51-
let postJobsLength = 0
52-
let flushingPreJob = false
53-
let activePostFlushCbs: SchedulerJob[] | null = null
45+
let postJobs: SchedulerJob[] = []
46+
let activePostJobs: SchedulerJob[] | null = null
5447
let currentFlushPromise: Promise<void> | null = null
48+
let jobsLength = 0
49+
let preJobsLength = 0
50+
let flushIndex = 0
51+
let preFlushIndex = 0
52+
let postFlushIndex = 0
5553

5654
const resolvedPromise = /*@__PURE__*/ Promise.resolve() as Promise<any>
57-
5855
const RECURSION_LIMIT = 100
56+
5957
type CountMap = Map<SchedulerJob, number>
6058

6159
export function nextTick<T = void, R = void>(
@@ -75,48 +73,70 @@ export function nextTick<T = void, R = void>(
7573
// A pre watcher will have the same id as its component's update job. The
7674
// watcher should be inserted immediately before the update job. This allows
7775
// watchers to be skipped if the component is unmounted by the parent update.
78-
function findInsertionIndex(id: number, isPre: boolean) {
79-
let start = (isPre ? preFlushIndex : mainFlushIndex) + 1
80-
let end = isPre ? preJobsLength : mainJobsLength
81-
const queue = isPre ? queuePreJobs : queueMainJobs
82-
76+
function findInsertionIndex(
77+
id: number,
78+
queue: SchedulerJob[],
79+
start: number,
80+
end: number,
81+
) {
8382
while (start < end) {
8483
const middle = (start + end) >>> 1
85-
const middleJob = queue[middle]!
84+
const middleJob = queue[middle]
8685
if (middleJob.id! <= id) {
8786
start = middle + 1
8887
} else {
8988
end = middle
9089
}
9190
}
92-
9391
return start
9492
}
9593

9694
/**
9795
* @internal for runtime-vapor only
9896
*/
9997
export function queueJob(job: SchedulerJob, isPre = false): void {
98+
if (isPre) {
99+
if (_queueJob(job, preJobs, preJobsLength, preFlushIndex, -1)) {
100+
preJobsLength++
101+
queueFlush()
102+
}
103+
} else {
104+
if (_queueJob(job, jobs, jobsLength, flushIndex, Infinity)) {
105+
jobsLength++
106+
queueFlush()
107+
}
108+
}
109+
}
110+
111+
function _queueJob(
112+
job: SchedulerJob,
113+
queue: SchedulerJob[],
114+
length: number,
115+
flushIndex: number,
116+
defaultId: number,
117+
) {
100118
const flags = job.flags!
101119
if (!(flags & SchedulerJobFlags.QUEUED)) {
120+
job.flags! = flags | SchedulerJobFlags.QUEUED
102121
if (job.id === undefined) {
103-
job.id = isPre ? -1 : Infinity
122+
job.id = defaultId
104123
}
105-
const queueLength = isPre ? preJobsLength : mainJobsLength
106-
const queue = isPre ? queuePreJobs : queueMainJobs
107124
if (
108-
!queueLength ||
125+
flushIndex === length ||
109126
// fast path when the job id is larger than the tail
110-
job.id >= queue[queueLength - 1]!.id!
127+
job.id >= queue[length - 1].id!
111128
) {
112-
queue[queueLength] = job
129+
queue[length] = job
113130
} else {
114-
queue.splice(findInsertionIndex(job.id, isPre), 0, job)
131+
queue.splice(
132+
findInsertionIndex(job.id, queue, flushIndex, length),
133+
0,
134+
job,
135+
)
115136
}
116-
isPre ? preJobsLength++ : mainJobsLength++
117-
job.flags! = flags | SchedulerJobFlags.QUEUED
118-
queueFlush()
137+
return true
119138
}
139+
return false
120140
}
121141

122142
function queueFlush() {
@@ -128,26 +148,19 @@ function queueFlush() {
128148
}
129149
}
130150

131-
export function queuePostFlushCb(cb: SchedulerJobs): void {
132-
if (!isArray(cb)) {
133-
if (cb.id === undefined) {
134-
cb.id = Infinity
135-
}
136-
if (activePostFlushCbs && cb.id === -1) {
137-
activePostFlushCbs.splice(postFlushIndex + 1, 0, cb)
138-
} else if (!(cb.flags! & SchedulerJobFlags.QUEUED)) {
139-
queuePostJobs[postJobsLength++] = cb
140-
cb.flags! |= SchedulerJobFlags.QUEUED
151+
export function queuePostFlushCb(jobs: SchedulerJobs): void {
152+
if (!isArray(jobs)) {
153+
if (activePostJobs && jobs.id === -1) {
154+
activePostJobs.splice(postFlushIndex, 0, jobs)
155+
} else {
156+
_queueJob(jobs, postJobs, postJobs.length, 0, Infinity)
141157
}
142158
} else {
143159
// if cb is an array, it is a component lifecycle hook which can only be
144160
// triggered by a job, which is already deduped in the main queue, so
145161
// we can skip duplicate check here to improve perf
146-
for (const job of cb) {
147-
if (job.id === undefined) {
148-
job.id = Infinity
149-
}
150-
queuePostJobs[postJobsLength++] = job
162+
for (const job of jobs) {
163+
_queueJob(job, postJobs, postJobs.length, 0, Infinity)
151164
}
152165
}
153166
queueFlush()
@@ -160,62 +173,45 @@ export function flushPreFlushCbs(
160173
if (__DEV__) {
161174
seen = seen || new Map()
162175
}
163-
for (
164-
let i = flushingPreJob ? preFlushIndex + 1 : preFlushIndex;
165-
i < preJobsLength;
166-
i++
167-
) {
168-
const cb = queuePreJobs[i]
169-
if (cb) {
170-
if (instance && cb.id !== instance.uid) {
171-
continue
172-
}
173-
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
174-
continue
175-
}
176-
queuePreJobs.splice(i, 1)
177-
preJobsLength--
178-
i--
179-
if (cb.flags! & SchedulerJobFlags.ALLOW_RECURSE) {
180-
cb.flags! &= ~SchedulerJobFlags.QUEUED
181-
}
182-
cb()
183-
if (!(cb.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
184-
cb.flags! &= ~SchedulerJobFlags.QUEUED
185-
}
176+
for (let i = preFlushIndex; i < preJobsLength; i++) {
177+
const cb = preJobs[i]
178+
if (instance && cb.id !== instance.uid) {
179+
continue
180+
}
181+
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
182+
continue
183+
}
184+
preJobs.splice(i, 1)
185+
i--
186+
preJobsLength--
187+
if (cb.flags! & SchedulerJobFlags.ALLOW_RECURSE) {
188+
cb.flags! &= ~SchedulerJobFlags.QUEUED
189+
}
190+
cb()
191+
if (!(cb.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
192+
cb.flags! &= ~SchedulerJobFlags.QUEUED
186193
}
187194
}
188195
}
189196

190197
export function flushPostFlushCbs(seen?: CountMap): void {
191-
if (postJobsLength) {
192-
const deduped = new Set<SchedulerJob>()
193-
for (let i = 0; i < postJobsLength; i++) {
194-
const job = queuePostJobs[i]!
195-
queuePostJobs[i] = undefined
196-
deduped.add(job)
197-
}
198-
postJobsLength = 0
199-
200-
const sorted = [...deduped].sort((a, b) => a.id! - b.id!)
201-
198+
if (postJobs.length) {
202199
// #1947 already has active queue, nested flushPostFlushCbs call
203-
if (activePostFlushCbs) {
204-
activePostFlushCbs.push(...sorted)
200+
if (activePostJobs) {
201+
activePostJobs.push(...postJobs)
202+
postJobs.length = 0
205203
return
206204
}
207205

208-
activePostFlushCbs = sorted
206+
activePostJobs = postJobs
207+
postJobs = []
208+
209209
if (__DEV__) {
210210
seen = seen || new Map()
211211
}
212212

213-
for (
214-
postFlushIndex = 0;
215-
postFlushIndex < activePostFlushCbs.length;
216-
postFlushIndex++
217-
) {
218-
const cb = activePostFlushCbs[postFlushIndex]
213+
while (postFlushIndex < activePostJobs.length) {
214+
const cb = activePostJobs[postFlushIndex++]
219215
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
220216
continue
221217
}
@@ -230,7 +226,8 @@ export function flushPostFlushCbs(seen?: CountMap): void {
230226
}
231227
}
232228
}
233-
activePostFlushCbs = null
229+
230+
activePostJobs = null
234231
postFlushIndex = 0
235232
}
236233
}
@@ -250,33 +247,30 @@ export function flushOnAppMount(): void {
250247

251248
function flushJobs(seen?: CountMap) {
252249
if (__DEV__) {
253-
seen = seen || new Map()
250+
seen ||= new Map()
254251
}
255252

256253
try {
257-
preFlushIndex = 0
258-
mainFlushIndex = 0
259-
260-
while (preFlushIndex < preJobsLength || mainFlushIndex < mainJobsLength) {
254+
while (preFlushIndex < preJobsLength || flushIndex < jobsLength) {
261255
let job: SchedulerJob
262256
if (preFlushIndex < preJobsLength) {
263-
if (mainFlushIndex < mainJobsLength) {
264-
const preJob = queuePreJobs[preFlushIndex]!
265-
const mainJob = queueMainJobs[mainFlushIndex]!
257+
if (flushIndex < jobsLength) {
258+
const mainJob = jobs[flushIndex]
259+
const preJob = preJobs[preFlushIndex]
266260
if (preJob.id! <= mainJob.id!) {
267261
job = preJob
268-
flushingPreJob = true
262+
preJobs[preFlushIndex++] = undefined as any
269263
} else {
270264
job = mainJob
271-
flushingPreJob = false
265+
jobs[flushIndex++] = undefined as any
272266
}
273267
} else {
274-
job = queuePreJobs[preFlushIndex]!
275-
flushingPreJob = true
268+
job = preJobs[preFlushIndex]
269+
preJobs[preFlushIndex++] = undefined as any
276270
}
277271
} else {
278-
job = queueMainJobs[mainFlushIndex]!
279-
flushingPreJob = false
272+
job = jobs[flushIndex]
273+
jobs[flushIndex++] = undefined as any
280274
}
281275

282276
if (!(job.flags! & SchedulerJobFlags.DISPOSED)) {
@@ -286,60 +280,47 @@ function flushJobs(seen?: CountMap) {
286280
// they would get eventually shaken by a minifier like terser, some minifiers
287281
// would fail to do that (e.g. https://github.com/evanw/esbuild/issues/1610)
288282
if (__DEV__ && checkRecursiveUpdates(seen!, job)) {
289-
if (flushingPreJob) {
290-
queuePreJobs[preFlushIndex++] = undefined
291-
} else {
292-
queueMainJobs[mainFlushIndex++] = undefined
293-
}
294283
continue
295284
}
296285
if (job.flags! & SchedulerJobFlags.ALLOW_RECURSE) {
297286
job.flags! &= ~SchedulerJobFlags.QUEUED
298287
}
299-
callWithErrorHandling(
300-
job,
301-
job.i,
302-
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
303-
)
304-
if (!(job.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
305-
job.flags! &= ~SchedulerJobFlags.QUEUED
288+
try {
289+
job()
290+
} catch (err) {
291+
handleError(
292+
err,
293+
job.i,
294+
job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER,
295+
)
296+
} finally {
297+
if (!(job.flags! & SchedulerJobFlags.ALLOW_RECURSE)) {
298+
job.flags! &= ~SchedulerJobFlags.QUEUED
299+
}
306300
}
307301
}
308-
309-
if (flushingPreJob) {
310-
queuePreJobs[preFlushIndex++] = undefined
311-
} else {
312-
queueMainJobs[mainFlushIndex++] = undefined
313-
}
314302
}
315303
} finally {
316304
// If there was an error we still need to clear the QUEUED flags
317305
while (preFlushIndex < preJobsLength) {
318-
const job = queuePreJobs[preFlushIndex]
319-
queuePreJobs[preFlushIndex++] = undefined
320-
if (job) {
321-
job.flags! &= ~SchedulerJobFlags.QUEUED
322-
}
306+
preJobs[preFlushIndex].flags! &= ~SchedulerJobFlags.QUEUED
307+
preJobs[preFlushIndex++] = undefined as any
323308
}
324-
while (mainFlushIndex < mainJobsLength) {
325-
const job = queueMainJobs[mainFlushIndex]
326-
queueMainJobs[mainFlushIndex++] = undefined
327-
if (job) {
328-
job.flags! &= ~SchedulerJobFlags.QUEUED
329-
}
309+
while (flushIndex < jobsLength) {
310+
jobs[flushIndex].flags! &= ~SchedulerJobFlags.QUEUED
311+
jobs[flushIndex++] = undefined as any
330312
}
331313

332-
preFlushIndex = -1
333-
mainFlushIndex = -1
314+
flushIndex = 0
315+
preFlushIndex = 0
316+
jobsLength = 0
334317
preJobsLength = 0
335-
mainJobsLength = 0
336-
flushingPreJob = false
337318

338319
flushPostFlushCbs(seen)
339320

340321
currentFlushPromise = null
341322
// If new jobs have been added to either queue, keep flushing
342-
if (preJobsLength || mainJobsLength || postJobsLength) {
323+
if (preJobsLength || jobsLength || postJobs.length) {
343324
flushJobs(seen)
344325
}
345326
}

0 commit comments

Comments
 (0)