1+ const SCHEDULE_MODE = {
2+ idle : 'idle' ,
3+ raf : 'raf' ,
4+ timeout : 'timeout' ,
5+ } as const
6+
7+ type ScheduleMode = ( typeof SCHEDULE_MODE ) [ keyof typeof SCHEDULE_MODE ]
8+
19export class JobQueue {
210 private isFlushing = false
311 private isFlushPending = false
412 private scheduleId = 0
513 private queue : Job [ ] = [ ]
6- private frameInterval = 33
14+ private frameInterval = 16
715 private initialTime = Date . now ( )
16+ private pendingJobs = new Map < string , Job > ( )
17+ private scheduleMode : ScheduleMode | null = null
818
919 queueJob ( job : Job ) {
1020 if ( job . priority & JOB_PRIORITY . PRIOR ) {
1121 job . cb ( )
1222 } else {
13- const index = this . findInsertionIndex ( job )
14- if ( index >= 0 ) {
23+ const existing = this . pendingJobs . get ( job . id )
24+ if ( existing ) {
25+ // 仅更新已有任务的回调与优先级
26+ existing . cb = job . cb
27+ if ( job . priority !== existing . priority ) {
28+ existing . priority = job . priority
29+ const idx = this . queue . indexOf ( existing )
30+ if ( idx >= 0 ) {
31+ this . queue . splice ( idx , 1 )
32+ const newIndex = this . findInsertionIndex ( existing )
33+ this . queue . splice ( newIndex , 0 , existing )
34+ }
35+ }
36+ } else {
37+ const index = this . findInsertionIndex ( job )
1538 this . queue . splice ( index , 0 , job )
39+ this . pendingJobs . set ( job . id , job )
1640 }
1741 }
1842 }
@@ -33,21 +57,29 @@ export class JobQueue {
3357
3458 clearJobs ( ) {
3559 this . queue . length = 0
60+ this . pendingJobs . clear ( )
3661 this . isFlushing = false
3762 this . isFlushPending = false
3863 this . cancelScheduleJob ( )
3964 }
4065
41- flushJobs ( ) {
66+ flushJobs ( deadline ?: IdleDeadline ) {
4267 this . isFlushPending = false
4368 this . isFlushing = true
4469
4570 const startTime = this . getCurrentTime ( )
71+ let budget = this . frameInterval
72+ if ( deadline && typeof deadline . timeRemaining === 'function' ) {
73+ const remain = deadline . timeRemaining ( )
74+ // 防止过长占用单帧
75+ budget = Math . max ( 0 , Math . min ( budget , remain ) )
76+ }
4677
47- let job
48- while ( ( job = this . queue . shift ( ) ) ) {
78+ while ( this . queue . length > 0 ) {
79+ const job = this . queue . shift ( ) !
4980 job . cb ( )
50- if ( this . getCurrentTime ( ) - startTime >= this . frameInterval ) {
81+ this . pendingJobs . delete ( job . id )
82+ if ( this . getCurrentTime ( ) - startTime >= budget ) {
5183 break
5284 }
5385 }
@@ -63,14 +95,14 @@ export class JobQueue {
6395 this . isFlushPending = false
6496 this . isFlushing = true
6597
66- let job
67- while ( ( job = this . queue . shift ( ) ) ) {
98+ while ( this . queue . length > 0 ) {
99+ const job = this . queue . shift ( ) !
68100 try {
69101 job . cb ( )
70102 } catch ( error ) {
71- // eslint-disable-next-line
72- console . log ( error )
103+ console . error ( error )
73104 }
105+ this . pendingJobs . delete ( job . id )
74106 }
75107
76108 this . isFlushing = false
@@ -94,33 +126,42 @@ export class JobQueue {
94126 }
95127
96128 private scheduleJob ( ) {
129+ if ( this . scheduleId ) {
130+ this . cancelScheduleJob ( )
131+ }
97132 if ( 'requestIdleCallback' in window ) {
98- if ( this . scheduleId ) {
99- this . cancelScheduleJob ( )
100- }
101- this . scheduleId = window . requestIdleCallback ( this . flushJobs . bind ( this ) , {
102- timeout : 100 ,
103- } )
133+ this . scheduleMode = SCHEDULE_MODE . idle
134+ this . scheduleId = window . requestIdleCallback (
135+ ( deadline : IdleDeadline ) => this . flushJobs ( deadline ) ,
136+ {
137+ timeout : 100 ,
138+ } ,
139+ )
140+ } else if ( 'requestAnimationFrame' in window ) {
141+ this . scheduleMode = SCHEDULE_MODE . raf
142+ this . scheduleId = ( window as Window ) . requestAnimationFrame ( ( ) =>
143+ this . flushJobs ( ) ,
144+ )
104145 } else {
105- if ( this . scheduleId ) {
106- this . cancelScheduleJob ( )
107- }
108- this . scheduleId = ( window as Window ) . setTimeout ( this . flushJobs . bind ( this ) )
146+ this . scheduleMode = SCHEDULE_MODE . timeout
147+ this . scheduleId = ( window as Window ) . setTimeout ( ( ) => this . flushJobs ( ) )
109148 }
110149 }
111150
112151 private cancelScheduleJob ( ) {
113- if ( 'cancelIdleCallback' in window ) {
114- if ( this . scheduleId ) {
115- window . cancelIdleCallback ( this . scheduleId )
116- }
117- this . scheduleId = 0
118- } else {
119- if ( this . scheduleId ) {
120- clearTimeout ( this . scheduleId )
121- }
122- this . scheduleId = 0
152+ if ( ! this . scheduleId ) return
153+ const cancelMethods : Partial < Record < ScheduleMode , ( id : number ) => void > > = {
154+ [ SCHEDULE_MODE . idle ] : window ? .cancelIdleCallback ,
155+ [ SCHEDULE_MODE . raf ] : window ?. cancelAnimationFrame ,
156+ [ SCHEDULE_MODE . timeout ] : window ?. clearTimeout ,
157+ }
158+ const mode = this . scheduleMode
159+ const cancelMethod = mode ? cancelMethods [ mode ] : undefined
160+ if ( typeof cancelMethod === 'function' ) {
161+ cancelMethod ( this . scheduleId as number )
123162 }
163+ this . scheduleId = 0
164+ this . scheduleMode = null
124165 }
125166
126167 private getCurrentTime ( ) {
@@ -145,20 +186,3 @@ export enum JOB_PRIORITY {
145186 RenderNode = /**/ 1 << 3 ,
146187 PRIOR = /* */ 1 << 20 ,
147188}
148-
149- // function findInsertionIndex(job: Job) {
150- // let start = 0
151- // for (let i = 0, len = queue.length; i < len; i += 1) {
152- // const j = queue[i]
153- // if (j.id === job.id) {
154- // console.log('xx', j.bit, job.bit)
155- // }
156- // if (j.id === job.id && (job.bit ^ (job.bit & j.bit)) === 0) {
157- // return -1
158- // }
159- // if (j.priority <= job.priority) {
160- // start += 1
161- // }
162- // }
163- // return start
164- // }
0 commit comments