UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

189 lines (168 loc) 4.83 kB
const SCHEDULE_MODE = { idle: 'idle', raf: 'raf', timeout: 'timeout', } as const type ScheduleMode = (typeof SCHEDULE_MODE)[keyof typeof SCHEDULE_MODE] export class JobQueue { private isFlushing = false private isFlushPending = false private scheduleId = 0 private queue: Job[] = [] private frameInterval = 16 private initialTime = Date.now() private pendingJobs = new Map<string, Job>() private scheduleMode: ScheduleMode | null = null queueJob(job: Job) { if (job.priority & JOB_PRIORITY.PRIOR) { job.cb() } else { const existing = this.pendingJobs.get(job.id) if (existing) { // 仅更新已有任务的回调与优先级 existing.cb = job.cb if (job.priority !== existing.priority) { existing.priority = job.priority const idx = this.queue.indexOf(existing) if (idx >= 0) { this.queue.splice(idx, 1) const newIndex = this.findInsertionIndex(existing) this.queue.splice(newIndex, 0, existing) } } } else { const index = this.findInsertionIndex(job) this.queue.splice(index, 0, job) this.pendingJobs.set(job.id, job) } } } queueFlush() { if (!this.isFlushing && !this.isFlushPending) { this.isFlushPending = true this.scheduleJob() } } queueFlushSync() { if (!this.isFlushing && !this.isFlushPending) { this.isFlushPending = true this.flushJobsSync() } } clearJobs() { this.queue.length = 0 this.pendingJobs.clear() this.isFlushing = false this.isFlushPending = false this.cancelScheduleJob() } flushJobs(deadline?: IdleDeadline) { this.isFlushPending = false this.isFlushing = true const startTime = this.getCurrentTime() let budget = this.frameInterval if (deadline && typeof deadline.timeRemaining === 'function') { const remain = deadline.timeRemaining() // 防止过长占用单帧 budget = Math.max(0, Math.min(budget, remain)) } while (this.queue.length > 0) { const job = this.queue.shift()! job.cb() this.pendingJobs.delete(job.id) if (this.getCurrentTime() - startTime >= budget) { break } } this.isFlushing = false if (this.queue.length) { this.queueFlush() } } flushJobsSync() { this.isFlushPending = false this.isFlushing = true while (this.queue.length > 0) { const job = this.queue.shift()! try { job.cb() } catch (error) { console.error(error) } this.pendingJobs.delete(job.id) } this.isFlushing = false } private findInsertionIndex(job: Job) { let left = 0 let ins = this.queue.length let right = ins - 1 const priority = job.priority while (left <= right) { const mid = ((right - left) >> 1) + left if (priority <= this.queue[mid].priority) { left = mid + 1 } else { ins = mid right = mid - 1 } } return ins } private scheduleJob() { if (this.scheduleId) { this.cancelScheduleJob() } if ('requestAnimationFrame' in window) { this.scheduleMode = SCHEDULE_MODE.raf this.scheduleId = (window as Window).requestAnimationFrame(() => this.flushJobs(), ) } else if ('requestIdleCallback' in window) { this.scheduleMode = SCHEDULE_MODE.idle this.scheduleId = (window as Window).requestIdleCallback( (deadline: IdleDeadline) => this.flushJobs(deadline), { timeout: 100, }, ) } else { this.scheduleMode = SCHEDULE_MODE.timeout this.scheduleId = (window as Window).setTimeout(() => this.flushJobs()) } } private cancelScheduleJob() { if (!this.scheduleId) return const cancelMethods: Partial<Record<ScheduleMode, (id: number) => void>> = { [SCHEDULE_MODE.idle]: window?.cancelIdleCallback, [SCHEDULE_MODE.raf]: window?.cancelAnimationFrame, [SCHEDULE_MODE.timeout]: window?.clearTimeout, } const mode = this.scheduleMode const cancelMethod = mode ? cancelMethods[mode] : undefined if (typeof cancelMethod === 'function') { cancelMethod(this.scheduleId as number) } this.scheduleId = 0 this.scheduleMode = null } private getCurrentTime() { const hasPerformanceNow = typeof performance === 'object' && typeof performance.now === 'function' if (hasPerformanceNow) { return performance.now() } return Date.now() - this.initialTime } } export interface Job { id: string priority: JOB_PRIORITY cb: () => void } export enum JOB_PRIORITY { Update = /* */ 1 << 1, RenderEdge = /**/ 1 << 2, RenderNode = /**/ 1 << 3, PRIOR = /* */ 1 << 20, }