Skip to content

Commit 55660b0

Browse files
authored
refactor(scheduler): use bitwise flags for scheduler jobs + optimize queueJob (#10407)
related: vuejs/vue-vapor#138
1 parent 58d827c commit 55660b0

File tree

6 files changed

+100
-74
lines changed

6 files changed

+100
-74
lines changed

packages/reactivity/src/effect.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,6 @@ export class ReactiveEffect<T = any>
126126
* @internal
127127
*/
128128
nextEffect?: ReactiveEffect = undefined
129-
/**
130-
* @internal
131-
*/
132-
allowRecurse?: boolean
133129

134130
scheduler?: EffectScheduler = undefined
135131
onStop?: () => void
@@ -144,7 +140,10 @@ export class ReactiveEffect<T = any>
144140
* @internal
145141
*/
146142
notify() {
147-
if (this.flags & EffectFlags.RUNNING && !this.allowRecurse) {
143+
if (
144+
this.flags & EffectFlags.RUNNING &&
145+
!(this.flags & EffectFlags.ALLOW_RECURSE)
146+
) {
148147
return
149148
}
150149
if (this.flags & EffectFlags.NO_BATCH) {

packages/runtime-core/__tests__/scheduler.spec.ts

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
type SchedulerJob,
3+
SchedulerJobFlags,
24
flushPostFlushCbs,
35
flushPreFlushCbs,
46
invalidateJob,
@@ -119,12 +121,12 @@ describe('scheduler', () => {
119121
const job1 = () => {
120122
calls.push('job1')
121123
}
122-
const cb1 = () => {
124+
const cb1: SchedulerJob = () => {
123125
// queueJob in postFlushCb
124126
calls.push('cb1')
125127
queueJob(job1)
126128
}
127-
cb1.pre = true
129+
cb1.flags! |= SchedulerJobFlags.PRE
128130

129131
queueJob(cb1)
130132
await nextTick()
@@ -138,25 +140,25 @@ describe('scheduler', () => {
138140
}
139141
job1.id = 1
140142

141-
const cb1 = () => {
143+
const cb1: SchedulerJob = () => {
142144
calls.push('cb1')
143145
queueJob(job1)
144146
// cb2 should execute before the job
145147
queueJob(cb2)
146148
queueJob(cb3)
147149
}
148-
cb1.pre = true
150+
cb1.flags! |= SchedulerJobFlags.PRE
149151

150-
const cb2 = () => {
152+
const cb2: SchedulerJob = () => {
151153
calls.push('cb2')
152154
}
153-
cb2.pre = true
155+
cb2.flags! |= SchedulerJobFlags.PRE
154156
cb2.id = 1
155157

156-
const cb3 = () => {
158+
const cb3: SchedulerJob = () => {
157159
calls.push('cb3')
158160
}
159-
cb3.pre = true
161+
cb3.flags! |= SchedulerJobFlags.PRE
160162
cb3.id = 1
161163

162164
queueJob(cb1)
@@ -166,37 +168,37 @@ describe('scheduler', () => {
166168

167169
it('should insert jobs after pre jobs with the same id', async () => {
168170
const calls: string[] = []
169-
const job1 = () => {
171+
const job1: SchedulerJob = () => {
170172
calls.push('job1')
171173
}
172174
job1.id = 1
173-
job1.pre = true
174-
const job2 = () => {
175+
job1.flags! |= SchedulerJobFlags.PRE
176+
const job2: SchedulerJob = () => {
175177
calls.push('job2')
176178
queueJob(job5)
177179
queueJob(job6)
178180
}
179181
job2.id = 2
180-
job2.pre = true
181-
const job3 = () => {
182+
job2.flags! |= SchedulerJobFlags.PRE
183+
const job3: SchedulerJob = () => {
182184
calls.push('job3')
183185
}
184186
job3.id = 2
185-
job3.pre = true
186-
const job4 = () => {
187+
job3.flags! |= SchedulerJobFlags.PRE
188+
const job4: SchedulerJob = () => {
187189
calls.push('job4')
188190
}
189191
job4.id = 3
190-
job4.pre = true
191-
const job5 = () => {
192+
job4.flags! |= SchedulerJobFlags.PRE
193+
const job5: SchedulerJob = () => {
192194
calls.push('job5')
193195
}
194196
job5.id = 2
195-
const job6 = () => {
197+
const job6: SchedulerJob = () => {
196198
calls.push('job6')
197199
}
198200
job6.id = 2
199-
job6.pre = true
201+
job6.flags! |= SchedulerJobFlags.PRE
200202

201203
// We need several jobs to test this properly, otherwise
202204
// findInsertionIndex can yield the correct index by chance
@@ -221,16 +223,16 @@ describe('scheduler', () => {
221223
flushPreFlushCbs()
222224
calls.push('job1')
223225
}
224-
const cb1 = () => {
226+
const cb1: SchedulerJob = () => {
225227
calls.push('cb1')
226228
// a cb triggers its parent job, which should be skipped
227229
queueJob(job1)
228230
}
229-
cb1.pre = true
230-
const cb2 = () => {
231+
cb1.flags! |= SchedulerJobFlags.PRE
232+
const cb2: SchedulerJob = () => {
231233
calls.push('cb2')
232234
}
233-
cb2.pre = true
235+
cb2.flags! |= SchedulerJobFlags.PRE
234236

235237
queueJob(job1)
236238
await nextTick()
@@ -240,8 +242,8 @@ describe('scheduler', () => {
240242
// #3806
241243
it('queue preFlushCb inside postFlushCb', async () => {
242244
const spy = vi.fn()
243-
const cb = () => spy()
244-
cb.pre = true
245+
const cb: SchedulerJob = () => spy()
246+
cb.flags! |= SchedulerJobFlags.PRE
245247
queuePostFlushCb(() => {
246248
queueJob(cb)
247249
})
@@ -521,25 +523,25 @@ describe('scheduler', () => {
521523
test('should allow explicitly marked jobs to trigger itself', async () => {
522524
// normal job
523525
let count = 0
524-
const job = () => {
526+
const job: SchedulerJob = () => {
525527
if (count < 3) {
526528
count++
527529
queueJob(job)
528530
}
529531
}
530-
job.allowRecurse = true
532+
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
531533
queueJob(job)
532534
await nextTick()
533535
expect(count).toBe(3)
534536

535537
// post cb
536-
const cb = () => {
538+
const cb: SchedulerJob = () => {
537539
if (count < 5) {
538540
count++
539541
queuePostFlushCb(cb)
540542
}
541543
}
542-
cb.allowRecurse = true
544+
cb.flags! |= SchedulerJobFlags.ALLOW_RECURSE
543545
queuePostFlushCb(cb)
544546
await nextTick()
545547
expect(count).toBe(5)
@@ -572,7 +574,7 @@ describe('scheduler', () => {
572574
// simulate parent component that toggles child
573575
const job1 = () => {
574576
// @ts-expect-error
575-
job2.active = false
577+
job2.flags! |= SchedulerJobFlags.DISPOSED
576578
}
577579
// simulate child that's triggered by the same reactive change that
578580
// triggers its toggle
@@ -589,11 +591,11 @@ describe('scheduler', () => {
589591

590592
it('flushPreFlushCbs inside a pre job', async () => {
591593
const spy = vi.fn()
592-
const job = () => {
594+
const job: SchedulerJob = () => {
593595
spy()
594596
flushPreFlushCbs()
595597
}
596-
job.pre = true
598+
job.flags! |= SchedulerJobFlags.PRE
597599
queueJob(job)
598600
await nextTick()
599601
expect(spy).toHaveBeenCalledTimes(1)

packages/runtime-core/src/apiWatch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
isRef,
1212
isShallow,
1313
} from '@vue/reactivity'
14-
import { type SchedulerJob, queueJob } from './scheduler'
14+
import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler'
1515
import {
1616
EMPTY_OBJ,
1717
NOOP,
@@ -382,7 +382,7 @@ function doWatch(
382382

383383
// important: mark the job as a watcher callback so that scheduler knows
384384
// it is allowed to self-trigger (#1727)
385-
job.allowRecurse = !!cb
385+
if (cb) job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
386386

387387
const effect = new ReactiveEffect(getter)
388388

@@ -394,7 +394,7 @@ function doWatch(
394394
scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
395395
} else {
396396
// default: 'pre'
397-
job.pre = true
397+
job.flags! |= SchedulerJobFlags.PRE
398398
if (instance) job.id = instance.uid
399399
scheduler = () => queueJob(job)
400400
}

packages/runtime-core/src/components/BaseTransition.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ErrorCodes, callWithAsyncErrorHandling } from '../errorHandling'
1919
import { PatchFlags, ShapeFlags, isArray } from '@vue/shared'
2020
import { onBeforeUnmount, onMounted } from '../apiLifecycle'
2121
import type { RendererElement } from '../renderer'
22+
import { SchedulerJobFlags } from '../scheduler'
2223

2324
type Hook<T = () => void> = T | T[]
2425

@@ -231,7 +232,7 @@ const BaseTransitionImpl: ComponentOptions = {
231232
state.isLeaving = false
232233
// #6835
233234
// it also needs to be updated when active is undefined
234-
if (instance.job.active !== false) {
235+
if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
235236
instance.update()
236237
}
237238
}

packages/runtime-core/src/renderer.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,19 @@ import {
3939
} from '@vue/shared'
4040
import {
4141
type SchedulerJob,
42+
SchedulerJobFlags,
4243
flushPostFlushCbs,
4344
flushPreFlushCbs,
4445
invalidateJob,
4546
queueJob,
4647
queuePostFlushCb,
4748
} from './scheduler'
48-
import { ReactiveEffect, pauseTracking, resetTracking } from '@vue/reactivity'
49+
import {
50+
EffectFlags,
51+
ReactiveEffect,
52+
pauseTracking,
53+
resetTracking,
54+
} from '@vue/reactivity'
4955
import { updateProps } from './componentProps'
5056
import { updateSlots } from './componentSlots'
5157
import { popWarningContext, pushWarningContext, warn } from './warning'
@@ -2281,7 +2287,7 @@ function baseCreateRenderer(
22812287
// setup has resolved.
22822288
if (job) {
22832289
// so that scheduler will no longer invoke it
2284-
job.active = false
2290+
job.flags! |= SchedulerJobFlags.DISPOSED
22852291
unmount(subTree, instance, parentSuspense, doRemove)
22862292
}
22872293
// unmounted hook
@@ -2419,7 +2425,13 @@ function toggleRecurse(
24192425
{ effect, job }: ComponentInternalInstance,
24202426
allowed: boolean,
24212427
) {
2422-
effect.allowRecurse = job.allowRecurse = allowed
2428+
if (allowed) {
2429+
effect.flags |= EffectFlags.ALLOW_RECURSE
2430+
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
2431+
} else {
2432+
effect.flags &= ~EffectFlags.ALLOW_RECURSE
2433+
job.flags! &= ~SchedulerJobFlags.ALLOW_RECURSE
2434+
}
24232435
}
24242436

24252437
export function needTransition(

0 commit comments

Comments
 (0)