@lytjs/common-async-scheduler 6.5.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// @lytjs/common-async-scheduler\r\n// 异步调度器:统一异步时序操作,通过 host 注入的定时器执行,支持同步插队刷新\r\n\r\ndeclare const __DEV__: boolean;\r\n\r\nimport type { RendererHost } from '@lytjs/host-contract';\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/**\r\n * 调度任务优先级。\r\n */\r\nexport type SchedulerPriority = 'sync' | 'high' | 'normal' | 'low';\r\n\r\n/**\r\n * 调度任务。\r\n */\r\nexport interface SchedulerJob {\r\n /** 任务 ID */\r\n id: number;\r\n /** 任务函数 */\r\n fn: () => void;\r\n /** 优先级 */\r\n priority: SchedulerPriority;\r\n /** 是否允许合并(同一 tick 内仅执行一次) */\r\n allowMerge: boolean;\r\n}\r\n\r\n/**\r\n * 异步调度器配置项。\r\n */\r\nexport interface AsyncSchedulerOptions {\r\n /** 默认优先级(默认 'normal') */\r\n defaultPriority?: SchedulerPriority;\r\n /** 是否启用同步插队(默认 true) */\r\n enableFlushSync?: boolean;\r\n}\r\n\r\n// ============================================================\r\n// 常量\r\n// ============================================================\r\n\r\n/** 默认配置 */\r\nconst DEFAULT_OPTIONS: Required<AsyncSchedulerOptions> = {\r\n defaultPriority: 'normal',\r\n enableFlushSync: true,\r\n};\r\n\r\n/** FIX: P2-40 优先级调度策略:支持自定义优先级权重 */\r\n/** 优先级权重映射(数值越小优先级越高) */\r\nconst PRIORITY_WEIGHT: Record<SchedulerPriority, number> = {\r\n sync: 0,\r\n high: 1,\r\n normal: 2,\r\n low: 3,\r\n};\r\n\r\n\r\n\r\n// ============================================================\r\n// AsyncScheduler\r\n// ============================================================\r\n\r\n/**\r\n * 异步调度器。\r\n *\r\n * 统一异步时序操作,通过 RendererHost 注入的定时器执行,\r\n * 支持优先级排序、任务合并和同步插队刷新。\r\n *\r\n * @template HN - 宿主节点类型\r\n * @template HE - 宿主元素类型\r\n */\r\nexport class AsyncScheduler<HN = unknown, HE extends HN = HN> {\r\n /** RendererHost 实例 */\r\n private host: RendererHost<HN, HE>;\r\n\r\n /** 配置项 */\r\n private options: Required<AsyncSchedulerOptions>;\r\n\r\n /** 待执行的任务队列 */\r\n private queue: SchedulerJob[] = [];\r\n\r\n /** 是否已调度刷新 */\r\n private scheduled = false;\r\n\r\n /** 调度的定时器 ID */\r\n private timerId: number | null = null;\r\n\r\n /** 是否正在执行刷新(防止重入) */\r\n private flushing = false;\r\n\r\n /** 已执行过的任务 ID 集合(用于 allowMerge 去重) */\r\n private executedJobIds = new Set<number>();\r\n\r\n /** FIX: P2-43 自增的任务 ID,改为实例属性避免全局变量 */\r\n private nextJobId = 0;\r\n\r\n /**\r\n * 创建异步调度器实例。\r\n * @param host - RendererHost 实例\r\n * @param options - 可选的配置项\r\n */\r\n constructor(host: RendererHost<HN, HE>, options?: AsyncSchedulerOptions) {\r\n this.host = host;\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n }\r\n\r\n // ==========================================================\r\n // 公开方法\r\n // ==========================================================\r\n\r\n /**\r\n * 调度一个任务。\r\n *\r\n * @param fn - 任务函数\r\n * @param priority - 任务优先级(默认使用配置的 defaultPriority)\r\n * @param allowMerge - 是否允许合并(默认 true)\r\n * @returns 任务 ID\r\n */\r\n schedule(\r\n fn: () => void,\r\n priority?: SchedulerPriority,\r\n allowMerge?: boolean,\r\n ): number {\r\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\r\n const id = ++this.nextJobId;\r\n const job: SchedulerJob = {\r\n id,\r\n fn,\r\n priority: priority ?? this.options.defaultPriority,\r\n allowMerge: allowMerge ?? true,\r\n };\r\n\r\n // 如果允许合并,在 queue 中查找同 id 任务进行去重\r\n // 不依赖 executedJobIds(它在 flush 开始时被 clear,导致 flush 间隙竞态)\r\n if (job.allowMerge) {\r\n const existingIdx = this.queue.findIndex((q) => q.allowMerge && q.id === id);\r\n if (existingIdx !== -1) {\r\n // 队列中已有同 id 任务,替换为最新的\r\n this.queue[existingIdx] = job;\r\n this.sortQueue();\r\n this.scheduleFlush();\r\n return id;\r\n }\r\n }\r\n\r\n this.queue.push(job);\r\n this.sortQueue();\r\n this.scheduleFlush();\r\n\r\n return id;\r\n }\r\n\r\n /**\r\n * 调度一个同步任务(立即在当前 tick 执行)。\r\n *\r\n * @param fn - 任务函数\r\n */\r\n scheduleSync(fn: () => void): void {\r\n // FIX: P1-50 scheduleSync 检查 enableFlushSync 配置,\r\n // 当 enableFlushSync 为 false 时回退到异步调度\r\n if (!this.options.enableFlushSync) {\r\n this.schedule(fn, 'sync', false);\r\n return;\r\n }\r\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\r\n const id = ++this.nextJobId;\r\n const job: SchedulerJob = {\r\n id,\r\n fn,\r\n priority: 'sync',\r\n allowMerge: false,\r\n };\r\n\r\n this.queue.unshift(job);\r\n this.flushSync();\r\n }\r\n\r\n /**\r\n * 同步插队刷新:立即执行队列中所有待处理任务。\r\n *\r\n * 用于需要立即更新 DOM 的场景(如用户交互后读取布局信息)。\r\n */\r\n flushSync(): void {\r\n if (!this.options.enableFlushSync || this.flushing) return;\r\n\r\n // 取消已调度的异步刷新\r\n if (this.timerId !== null) {\r\n this.host.clearTimeout(this.timerId);\r\n this.timerId = null;\r\n }\r\n this.scheduled = false;\r\n\r\n this.flush();\r\n }\r\n\r\n /**\r\n * 在下一帧执行回调(通过 host.nextFrame)。\r\n *\r\n * @param fn - 回调函数\r\n */\r\n nextFrame(fn: () => void): void {\r\n this.host.nextFrame(fn);\r\n }\r\n\r\n /**\r\n * 延迟执行回调。\r\n *\r\n * @param fn - 回调函数\r\n * @param ms - 延迟时间(ms)\r\n * @returns 定时器 ID\r\n */\r\n setTimeout(fn: () => void, ms: number): number {\r\n return this.host.setTimeout(fn, ms);\r\n }\r\n\r\n /**\r\n * 取消延迟执行。\r\n *\r\n * @param id - 定时器 ID\r\n */\r\n clearTimeout(id: number): void {\r\n this.host.clearTimeout(id);\r\n }\r\n\r\n /**\r\n * 获取当前队列中的任务数量。\r\n */\r\n get size(): number {\r\n return this.queue.length;\r\n }\r\n\r\n /**\r\n * 清空队列(不执行任务)。\r\n */\r\n clear(): void {\r\n if (this.timerId !== null) {\r\n this.host.clearTimeout(this.timerId);\r\n this.timerId = null;\r\n }\r\n this.queue.length = 0;\r\n this.scheduled = false;\r\n this.executedJobIds.clear();\r\n }\r\n\r\n /**\r\n * 销毁调度器,清理所有状态。\r\n */\r\n dispose(): void {\r\n this.clear();\r\n }\r\n\r\n // ==========================================================\r\n // 内部方法\r\n // ==========================================================\r\n\r\n /**\r\n * 调度异步刷新。\r\n */\r\n private scheduleFlush(): void {\r\n if (this.scheduled) return;\r\n this.scheduled = true;\r\n this.timerId = this.host.setTimeout(() => {\r\n this.timerId = null;\r\n this.scheduled = false;\r\n this.flush();\r\n }, 0);\r\n }\r\n\r\n /**\r\n * 执行队列中所有任务。\r\n */\r\n private flush(): void {\r\n if (this.flushing || this.queue.length === 0) return;\r\n\r\n this.flushing = true;\r\n\r\n // 取出当前所有任务\r\n const jobs = this.queue;\r\n this.queue = [];\r\n // FIX: P1-47 消除 executedJobIds 竞态窗口:\r\n // 不在 flush 开始时清空 executedJobIds,而是在每个任务执行后立即添加。\r\n // 同时在 schedule() 中不依赖 executedJobIds 进行去重(已在 queue 中去重),\r\n // executedJobIds 仅用于防止 flush 期间重复入队的任务被执行两次。\r\n // 因此此处无需 clear,而是在任务执行后立即 add。\r\n\r\n for (const job of jobs) {\r\n // FIX: P1-47 在执行前检查是否已在当前 flush 周期中执行过\r\n if (job.allowMerge && this.executedJobIds.has(job.id)) {\r\n continue;\r\n }\r\n try {\r\n job.fn();\r\n } catch (err) {\r\n // 任务执行失败不影响后续任务\r\n if (__DEV__) console.warn('[lytjs/async-scheduler] Error executing job:', err);\r\n }\r\n // 记录已执行的任务 ID(用于 allowMerge 去重)\r\n if (job.allowMerge) {\r\n this.executedJobIds.add(job.id);\r\n }\r\n }\r\n\r\n this.flushing = false;\r\n\r\n // FIX: P2-44 flush 完成后清空 executedJobIds,防止无限增长\r\n this.executedJobIds.clear();\r\n\r\n // 如果 flush 过程中又有新任务入队,继续调度\r\n if (this.queue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n /**\r\n * 按优先级排序队列。\r\n *\r\n * 优先级高的排在前面,同优先级保持插入顺序(稳定排序)。\r\n * FIX: P2-42 添加第二排序键(任务 ID)确保排序稳定性\r\n */\r\n private sortQueue(): void {\r\n this.queue.sort((a, b) => {\r\n const weightA = PRIORITY_WEIGHT[a.priority]!;\r\n const weightB = PRIORITY_WEIGHT[b.priority]!;\r\n // 首先按优先级排序\r\n if (weightA !== weightB) {\r\n return weightA - weightB;\r\n }\r\n // FIX: P2-42 同优先级时按任务 ID 排序,确保稳定性\r\n return a.id - b.id;\r\n });\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// @lytjs/common-async-scheduler\n// 异步调度器:统一异步时序操作,通过 host 注入的定时器执行,支持同步插队刷新\n\ndeclare const __DEV__: boolean;\n\nimport type { RendererHost } from '@lytjs/host-contract';\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 调度任务优先级。\n */\nexport type SchedulerPriority = 'sync' | 'high' | 'normal' | 'low';\n\n/**\n * 调度任务。\n */\nexport interface SchedulerJob {\n /** 任务 ID */\n id: number;\n /** 任务函数 */\n fn: () => void;\n /** 优先级 */\n priority: SchedulerPriority;\n /** 是否允许合并(同一 tick 内仅执行一次) */\n allowMerge: boolean;\n}\n\n/**\n * 异步调度器配置项。\n */\nexport interface AsyncSchedulerOptions {\n /** 默认优先级(默认 'normal') */\n defaultPriority?: SchedulerPriority;\n /** 是否启用同步插队(默认 true) */\n enableFlushSync?: boolean;\n}\n\n// ============================================================\n// 常量\n// ============================================================\n\n/** 默认配置 */\nconst DEFAULT_OPTIONS: Required<AsyncSchedulerOptions> = {\n defaultPriority: 'normal',\n enableFlushSync: true,\n};\n\n/** FIX: P2-40 优先级调度策略:支持自定义优先级权重 */\n/** 优先级权重映射(数值越小优先级越高) */\nconst PRIORITY_WEIGHT: Record<SchedulerPriority, number> = {\n sync: 0,\n high: 1,\n normal: 2,\n low: 3,\n};\n\n// ============================================================\n// AsyncScheduler\n// ============================================================\n\n/**\n * 异步调度器。\n *\n * 统一异步时序操作,通过 RendererHost 注入的定时器执行,\n * 支持优先级排序、任务合并和同步插队刷新。\n *\n * @template HN - 宿主节点类型\n * @template HE - 宿主元素类型\n */\nexport class AsyncScheduler<HN = unknown, HE extends HN = HN> {\n /** RendererHost 实例 */\n private host: RendererHost<HN, HE>;\n\n /** 配置项 */\n private options: Required<AsyncSchedulerOptions>;\n\n /** 待执行的任务队列 */\n private queue: SchedulerJob[] = [];\n\n /** 是否已调度刷新 */\n private scheduled = false;\n\n /** 调度的定时器 ID */\n private timerId: number | null = null;\n\n /** 是否正在执行刷新(防止重入) */\n private flushing = false;\n\n /** 已执行过的任务 ID 集合(用于 allowMerge 去重) */\n private executedJobIds = new Set<number>();\n\n /** FIX: P2-43 自增的任务 ID,改为实例属性避免全局变量 */\n private nextJobId = 0;\n\n /**\n * 创建异步调度器实例。\n * @param host - RendererHost 实例\n * @param options - 可选的配置项\n */\n constructor(host: RendererHost<HN, HE>, options?: AsyncSchedulerOptions) {\n this.host = host;\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n // ==========================================================\n // 公开方法\n // ==========================================================\n\n /**\n * 调度一个任务。\n *\n * @param fn - 任务函数\n * @param priority - 任务优先级(默认使用配置的 defaultPriority)\n * @param allowMerge - 是否允许合并(默认 true)\n * @returns 任务 ID\n */\n schedule(fn: () => void, priority?: SchedulerPriority, allowMerge?: boolean): number {\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\n const id = ++this.nextJobId;\n const job: SchedulerJob = {\n id,\n fn,\n priority: priority ?? this.options.defaultPriority,\n allowMerge: allowMerge ?? true,\n };\n\n // 如果允许合并,在 queue 中查找同 id 任务进行去重\n // 不依赖 executedJobIds(它在 flush 开始时被 clear,导致 flush 间隙竞态)\n if (job.allowMerge) {\n const existingIdx = this.queue.findIndex((q) => q.allowMerge && q.id === id);\n if (existingIdx !== -1) {\n // 队列中已有同 id 任务,替换为最新的\n this.queue[existingIdx] = job;\n this.sortQueue();\n this.scheduleFlush();\n return id;\n }\n }\n\n this.queue.push(job);\n this.sortQueue();\n this.scheduleFlush();\n\n return id;\n }\n\n /**\n * 调度一个同步任务(立即在当前 tick 执行)。\n *\n * @param fn - 任务函数\n */\n scheduleSync(fn: () => void): void {\n // FIX: P1-50 scheduleSync 检查 enableFlushSync 配置,\n // 当 enableFlushSync 为 false 时回退到异步调度\n if (!this.options.enableFlushSync) {\n this.schedule(fn, 'sync', false);\n return;\n }\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\n const id = ++this.nextJobId;\n const job: SchedulerJob = {\n id,\n fn,\n priority: 'sync',\n allowMerge: false,\n };\n\n this.queue.unshift(job);\n this.flushSync();\n }\n\n /**\n * 同步插队刷新:立即执行队列中所有待处理任务。\n *\n * 用于需要立即更新 DOM 的场景(如用户交互后读取布局信息)。\n */\n flushSync(): void {\n if (!this.options.enableFlushSync || this.flushing) return;\n\n // 取消已调度的异步刷新\n if (this.timerId !== null) {\n this.host.clearTimeout(this.timerId);\n this.timerId = null;\n }\n this.scheduled = false;\n\n this.flush();\n }\n\n /**\n * 在下一帧执行回调(通过 host.nextFrame)。\n *\n * @param fn - 回调函数\n */\n nextFrame(fn: () => void): void {\n this.host.nextFrame(fn);\n }\n\n /**\n * 延迟执行回调。\n *\n * @param fn - 回调函数\n * @param ms - 延迟时间(ms)\n * @returns 定时器 ID\n */\n setTimeout(fn: () => void, ms: number): number {\n return this.host.setTimeout(fn, ms);\n }\n\n /**\n * 取消延迟执行。\n *\n * @param id - 定时器 ID\n */\n clearTimeout(id: number): void {\n this.host.clearTimeout(id);\n }\n\n /**\n * 获取当前队列中的任务数量。\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * 清空队列(不执行任务)。\n */\n clear(): void {\n if (this.timerId !== null) {\n this.host.clearTimeout(this.timerId);\n this.timerId = null;\n }\n this.queue.length = 0;\n this.scheduled = false;\n this.executedJobIds.clear();\n }\n\n /**\n * 销毁调度器,清理所有状态。\n */\n dispose(): void {\n this.clear();\n }\n\n // ==========================================================\n // 内部方法\n // ==========================================================\n\n /**\n * 调度异步刷新。\n */\n private scheduleFlush(): void {\n if (this.scheduled) return;\n this.scheduled = true;\n this.timerId = this.host.setTimeout(() => {\n this.timerId = null;\n this.scheduled = false;\n this.flush();\n }, 0);\n }\n\n /**\n * 执行队列中所有任务。\n */\n private flush(): void {\n if (this.flushing || this.queue.length === 0) return;\n\n this.flushing = true;\n\n // 取出当前所有任务\n const jobs = this.queue;\n this.queue = [];\n // FIX: P1-47 消除 executedJobIds 竞态窗口:\n // 不在 flush 开始时清空 executedJobIds,而是在每个任务执行后立即添加。\n // 同时在 schedule() 中不依赖 executedJobIds 进行去重(已在 queue 中去重),\n // executedJobIds 仅用于防止 flush 期间重复入队的任务被执行两次。\n // 因此此处无需 clear,而是在任务执行后立即 add。\n\n for (const job of jobs) {\n // FIX: P1-47 在执行前检查是否已在当前 flush 周期中执行过\n if (job.allowMerge && this.executedJobIds.has(job.id)) {\n continue;\n }\n try {\n job.fn();\n } catch (err) {\n // 任务执行失败不影响后续任务\n if (__DEV__) console.warn('[lytjs/async-scheduler] Error executing job:', err);\n }\n // 记录已执行的任务 ID(用于 allowMerge 去重)\n if (job.allowMerge) {\n this.executedJobIds.add(job.id);\n }\n }\n\n this.flushing = false;\n\n // FIX: P2-44 flush 完成后清空 executedJobIds,防止无限增长\n this.executedJobIds.clear();\n\n // 如果 flush 过程中又有新任务入队,继续调度\n if (this.queue.length > 0) {\n this.scheduleFlush();\n }\n }\n\n /**\n * 按优先级排序队列。\n *\n * 优先级高的排在前面,同优先级保持插入顺序(稳定排序)。\n * FIX: P2-42 添加第二排序键(任务 ID)确保排序稳定性\n */\n private sortQueue(): void {\n this.queue.sort((a, b) => {\n const weightA = PRIORITY_WEIGHT[a.priority]!;\n const weightB = PRIORITY_WEIGHT[b.priority]!;\n // 首先按优先级排序\n if (weightA !== weightB) {\n return weightA - weightB;\n }\n // FIX: P2-42 同优先级时按任务 ID 排序,确保稳定性\n return a.id - b.id;\n });\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @lytjs/common-async-scheduler\r\n// 异步调度器:统一异步时序操作,通过 host 注入的定时器执行,支持同步插队刷新\r\n\r\ndeclare const __DEV__: boolean;\r\n\r\nimport type { RendererHost } from '@lytjs/host-contract';\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/**\r\n * 调度任务优先级。\r\n */\r\nexport type SchedulerPriority = 'sync' | 'high' | 'normal' | 'low';\r\n\r\n/**\r\n * 调度任务。\r\n */\r\nexport interface SchedulerJob {\r\n /** 任务 ID */\r\n id: number;\r\n /** 任务函数 */\r\n fn: () => void;\r\n /** 优先级 */\r\n priority: SchedulerPriority;\r\n /** 是否允许合并(同一 tick 内仅执行一次) */\r\n allowMerge: boolean;\r\n}\r\n\r\n/**\r\n * 异步调度器配置项。\r\n */\r\nexport interface AsyncSchedulerOptions {\r\n /** 默认优先级(默认 'normal') */\r\n defaultPriority?: SchedulerPriority;\r\n /** 是否启用同步插队(默认 true) */\r\n enableFlushSync?: boolean;\r\n}\r\n\r\n// ============================================================\r\n// 常量\r\n// ============================================================\r\n\r\n/** 默认配置 */\r\nconst DEFAULT_OPTIONS: Required<AsyncSchedulerOptions> = {\r\n defaultPriority: 'normal',\r\n enableFlushSync: true,\r\n};\r\n\r\n/** FIX: P2-40 优先级调度策略:支持自定义优先级权重 */\r\n/** 优先级权重映射(数值越小优先级越高) */\r\nconst PRIORITY_WEIGHT: Record<SchedulerPriority, number> = {\r\n sync: 0,\r\n high: 1,\r\n normal: 2,\r\n low: 3,\r\n};\r\n\r\n\r\n\r\n// ============================================================\r\n// AsyncScheduler\r\n// ============================================================\r\n\r\n/**\r\n * 异步调度器。\r\n *\r\n * 统一异步时序操作,通过 RendererHost 注入的定时器执行,\r\n * 支持优先级排序、任务合并和同步插队刷新。\r\n *\r\n * @template HN - 宿主节点类型\r\n * @template HE - 宿主元素类型\r\n */\r\nexport class AsyncScheduler<HN = unknown, HE extends HN = HN> {\r\n /** RendererHost 实例 */\r\n private host: RendererHost<HN, HE>;\r\n\r\n /** 配置项 */\r\n private options: Required<AsyncSchedulerOptions>;\r\n\r\n /** 待执行的任务队列 */\r\n private queue: SchedulerJob[] = [];\r\n\r\n /** 是否已调度刷新 */\r\n private scheduled = false;\r\n\r\n /** 调度的定时器 ID */\r\n private timerId: number | null = null;\r\n\r\n /** 是否正在执行刷新(防止重入) */\r\n private flushing = false;\r\n\r\n /** 已执行过的任务 ID 集合(用于 allowMerge 去重) */\r\n private executedJobIds = new Set<number>();\r\n\r\n /** FIX: P2-43 自增的任务 ID,改为实例属性避免全局变量 */\r\n private nextJobId = 0;\r\n\r\n /**\r\n * 创建异步调度器实例。\r\n * @param host - RendererHost 实例\r\n * @param options - 可选的配置项\r\n */\r\n constructor(host: RendererHost<HN, HE>, options?: AsyncSchedulerOptions) {\r\n this.host = host;\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n }\r\n\r\n // ==========================================================\r\n // 公开方法\r\n // ==========================================================\r\n\r\n /**\r\n * 调度一个任务。\r\n *\r\n * @param fn - 任务函数\r\n * @param priority - 任务优先级(默认使用配置的 defaultPriority)\r\n * @param allowMerge - 是否允许合并(默认 true)\r\n * @returns 任务 ID\r\n */\r\n schedule(\r\n fn: () => void,\r\n priority?: SchedulerPriority,\r\n allowMerge?: boolean,\r\n ): number {\r\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\r\n const id = ++this.nextJobId;\r\n const job: SchedulerJob = {\r\n id,\r\n fn,\r\n priority: priority ?? this.options.defaultPriority,\r\n allowMerge: allowMerge ?? true,\r\n };\r\n\r\n // 如果允许合并,在 queue 中查找同 id 任务进行去重\r\n // 不依赖 executedJobIds(它在 flush 开始时被 clear,导致 flush 间隙竞态)\r\n if (job.allowMerge) {\r\n const existingIdx = this.queue.findIndex((q) => q.allowMerge && q.id === id);\r\n if (existingIdx !== -1) {\r\n // 队列中已有同 id 任务,替换为最新的\r\n this.queue[existingIdx] = job;\r\n this.sortQueue();\r\n this.scheduleFlush();\r\n return id;\r\n }\r\n }\r\n\r\n this.queue.push(job);\r\n this.sortQueue();\r\n this.scheduleFlush();\r\n\r\n return id;\r\n }\r\n\r\n /**\r\n * 调度一个同步任务(立即在当前 tick 执行)。\r\n *\r\n * @param fn - 任务函数\r\n */\r\n scheduleSync(fn: () => void): void {\r\n // FIX: P1-50 scheduleSync 检查 enableFlushSync 配置,\r\n // 当 enableFlushSync 为 false 时回退到异步调度\r\n if (!this.options.enableFlushSync) {\r\n this.schedule(fn, 'sync', false);\r\n return;\r\n }\r\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\r\n const id = ++this.nextJobId;\r\n const job: SchedulerJob = {\r\n id,\r\n fn,\r\n priority: 'sync',\r\n allowMerge: false,\r\n };\r\n\r\n this.queue.unshift(job);\r\n this.flushSync();\r\n }\r\n\r\n /**\r\n * 同步插队刷新:立即执行队列中所有待处理任务。\r\n *\r\n * 用于需要立即更新 DOM 的场景(如用户交互后读取布局信息)。\r\n */\r\n flushSync(): void {\r\n if (!this.options.enableFlushSync || this.flushing) return;\r\n\r\n // 取消已调度的异步刷新\r\n if (this.timerId !== null) {\r\n this.host.clearTimeout(this.timerId);\r\n this.timerId = null;\r\n }\r\n this.scheduled = false;\r\n\r\n this.flush();\r\n }\r\n\r\n /**\r\n * 在下一帧执行回调(通过 host.nextFrame)。\r\n *\r\n * @param fn - 回调函数\r\n */\r\n nextFrame(fn: () => void): void {\r\n this.host.nextFrame(fn);\r\n }\r\n\r\n /**\r\n * 延迟执行回调。\r\n *\r\n * @param fn - 回调函数\r\n * @param ms - 延迟时间(ms)\r\n * @returns 定时器 ID\r\n */\r\n setTimeout(fn: () => void, ms: number): number {\r\n return this.host.setTimeout(fn, ms);\r\n }\r\n\r\n /**\r\n * 取消延迟执行。\r\n *\r\n * @param id - 定时器 ID\r\n */\r\n clearTimeout(id: number): void {\r\n this.host.clearTimeout(id);\r\n }\r\n\r\n /**\r\n * 获取当前队列中的任务数量。\r\n */\r\n get size(): number {\r\n return this.queue.length;\r\n }\r\n\r\n /**\r\n * 清空队列(不执行任务)。\r\n */\r\n clear(): void {\r\n if (this.timerId !== null) {\r\n this.host.clearTimeout(this.timerId);\r\n this.timerId = null;\r\n }\r\n this.queue.length = 0;\r\n this.scheduled = false;\r\n this.executedJobIds.clear();\r\n }\r\n\r\n /**\r\n * 销毁调度器,清理所有状态。\r\n */\r\n dispose(): void {\r\n this.clear();\r\n }\r\n\r\n // ==========================================================\r\n // 内部方法\r\n // ==========================================================\r\n\r\n /**\r\n * 调度异步刷新。\r\n */\r\n private scheduleFlush(): void {\r\n if (this.scheduled) return;\r\n this.scheduled = true;\r\n this.timerId = this.host.setTimeout(() => {\r\n this.timerId = null;\r\n this.scheduled = false;\r\n this.flush();\r\n }, 0);\r\n }\r\n\r\n /**\r\n * 执行队列中所有任务。\r\n */\r\n private flush(): void {\r\n if (this.flushing || this.queue.length === 0) return;\r\n\r\n this.flushing = true;\r\n\r\n // 取出当前所有任务\r\n const jobs = this.queue;\r\n this.queue = [];\r\n // FIX: P1-47 消除 executedJobIds 竞态窗口:\r\n // 不在 flush 开始时清空 executedJobIds,而是在每个任务执行后立即添加。\r\n // 同时在 schedule() 中不依赖 executedJobIds 进行去重(已在 queue 中去重),\r\n // executedJobIds 仅用于防止 flush 期间重复入队的任务被执行两次。\r\n // 因此此处无需 clear,而是在任务执行后立即 add。\r\n\r\n for (const job of jobs) {\r\n // FIX: P1-47 在执行前检查是否已在当前 flush 周期中执行过\r\n if (job.allowMerge && this.executedJobIds.has(job.id)) {\r\n continue;\r\n }\r\n try {\r\n job.fn();\r\n } catch (err) {\r\n // 任务执行失败不影响后续任务\r\n if (__DEV__) console.warn('[lytjs/async-scheduler] Error executing job:', err);\r\n }\r\n // 记录已执行的任务 ID(用于 allowMerge 去重)\r\n if (job.allowMerge) {\r\n this.executedJobIds.add(job.id);\r\n }\r\n }\r\n\r\n this.flushing = false;\r\n\r\n // FIX: P2-44 flush 完成后清空 executedJobIds,防止无限增长\r\n this.executedJobIds.clear();\r\n\r\n // 如果 flush 过程中又有新任务入队,继续调度\r\n if (this.queue.length > 0) {\r\n this.scheduleFlush();\r\n }\r\n }\r\n\r\n /**\r\n * 按优先级排序队列。\r\n *\r\n * 优先级高的排在前面,同优先级保持插入顺序(稳定排序)。\r\n * FIX: P2-42 添加第二排序键(任务 ID)确保排序稳定性\r\n */\r\n private sortQueue(): void {\r\n this.queue.sort((a, b) => {\r\n const weightA = PRIORITY_WEIGHT[a.priority]!;\r\n const weightB = PRIORITY_WEIGHT[b.priority]!;\r\n // 首先按优先级排序\r\n if (weightA !== weightB) {\r\n return weightA - weightB;\r\n }\r\n // FIX: P2-42 同优先级时按任务 ID 排序,确保稳定性\r\n return a.id - b.id;\r\n });\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @lytjs/common-async-scheduler\n// 异步调度器:统一异步时序操作,通过 host 注入的定时器执行,支持同步插队刷新\n\ndeclare const __DEV__: boolean;\n\nimport type { RendererHost } from '@lytjs/host-contract';\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 调度任务优先级。\n */\nexport type SchedulerPriority = 'sync' | 'high' | 'normal' | 'low';\n\n/**\n * 调度任务。\n */\nexport interface SchedulerJob {\n /** 任务 ID */\n id: number;\n /** 任务函数 */\n fn: () => void;\n /** 优先级 */\n priority: SchedulerPriority;\n /** 是否允许合并(同一 tick 内仅执行一次) */\n allowMerge: boolean;\n}\n\n/**\n * 异步调度器配置项。\n */\nexport interface AsyncSchedulerOptions {\n /** 默认优先级(默认 'normal') */\n defaultPriority?: SchedulerPriority;\n /** 是否启用同步插队(默认 true) */\n enableFlushSync?: boolean;\n}\n\n// ============================================================\n// 常量\n// ============================================================\n\n/** 默认配置 */\nconst DEFAULT_OPTIONS: Required<AsyncSchedulerOptions> = {\n defaultPriority: 'normal',\n enableFlushSync: true,\n};\n\n/** FIX: P2-40 优先级调度策略:支持自定义优先级权重 */\n/** 优先级权重映射(数值越小优先级越高) */\nconst PRIORITY_WEIGHT: Record<SchedulerPriority, number> = {\n sync: 0,\n high: 1,\n normal: 2,\n low: 3,\n};\n\n// ============================================================\n// AsyncScheduler\n// ============================================================\n\n/**\n * 异步调度器。\n *\n * 统一异步时序操作,通过 RendererHost 注入的定时器执行,\n * 支持优先级排序、任务合并和同步插队刷新。\n *\n * @template HN - 宿主节点类型\n * @template HE - 宿主元素类型\n */\nexport class AsyncScheduler<HN = unknown, HE extends HN = HN> {\n /** RendererHost 实例 */\n private host: RendererHost<HN, HE>;\n\n /** 配置项 */\n private options: Required<AsyncSchedulerOptions>;\n\n /** 待执行的任务队列 */\n private queue: SchedulerJob[] = [];\n\n /** 是否已调度刷新 */\n private scheduled = false;\n\n /** 调度的定时器 ID */\n private timerId: number | null = null;\n\n /** 是否正在执行刷新(防止重入) */\n private flushing = false;\n\n /** 已执行过的任务 ID 集合(用于 allowMerge 去重) */\n private executedJobIds = new Set<number>();\n\n /** FIX: P2-43 自增的任务 ID,改为实例属性避免全局变量 */\n private nextJobId = 0;\n\n /**\n * 创建异步调度器实例。\n * @param host - RendererHost 实例\n * @param options - 可选的配置项\n */\n constructor(host: RendererHost<HN, HE>, options?: AsyncSchedulerOptions) {\n this.host = host;\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n // ==========================================================\n // 公开方法\n // ==========================================================\n\n /**\n * 调度一个任务。\n *\n * @param fn - 任务函数\n * @param priority - 任务优先级(默认使用配置的 defaultPriority)\n * @param allowMerge - 是否允许合并(默认 true)\n * @returns 任务 ID\n */\n schedule(fn: () => void, priority?: SchedulerPriority, allowMerge?: boolean): number {\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\n const id = ++this.nextJobId;\n const job: SchedulerJob = {\n id,\n fn,\n priority: priority ?? this.options.defaultPriority,\n allowMerge: allowMerge ?? true,\n };\n\n // 如果允许合并,在 queue 中查找同 id 任务进行去重\n // 不依赖 executedJobIds(它在 flush 开始时被 clear,导致 flush 间隙竞态)\n if (job.allowMerge) {\n const existingIdx = this.queue.findIndex((q) => q.allowMerge && q.id === id);\n if (existingIdx !== -1) {\n // 队列中已有同 id 任务,替换为最新的\n this.queue[existingIdx] = job;\n this.sortQueue();\n this.scheduleFlush();\n return id;\n }\n }\n\n this.queue.push(job);\n this.sortQueue();\n this.scheduleFlush();\n\n return id;\n }\n\n /**\n * 调度一个同步任务(立即在当前 tick 执行)。\n *\n * @param fn - 任务函数\n */\n scheduleSync(fn: () => void): void {\n // FIX: P1-50 scheduleSync 检查 enableFlushSync 配置,\n // 当 enableFlushSync 为 false 时回退到异步调度\n if (!this.options.enableFlushSync) {\n this.schedule(fn, 'sync', false);\n return;\n }\n // FIX: P2-43 使用实例属性 nextJobId 替代全局变量\n const id = ++this.nextJobId;\n const job: SchedulerJob = {\n id,\n fn,\n priority: 'sync',\n allowMerge: false,\n };\n\n this.queue.unshift(job);\n this.flushSync();\n }\n\n /**\n * 同步插队刷新:立即执行队列中所有待处理任务。\n *\n * 用于需要立即更新 DOM 的场景(如用户交互后读取布局信息)。\n */\n flushSync(): void {\n if (!this.options.enableFlushSync || this.flushing) return;\n\n // 取消已调度的异步刷新\n if (this.timerId !== null) {\n this.host.clearTimeout(this.timerId);\n this.timerId = null;\n }\n this.scheduled = false;\n\n this.flush();\n }\n\n /**\n * 在下一帧执行回调(通过 host.nextFrame)。\n *\n * @param fn - 回调函数\n */\n nextFrame(fn: () => void): void {\n this.host.nextFrame(fn);\n }\n\n /**\n * 延迟执行回调。\n *\n * @param fn - 回调函数\n * @param ms - 延迟时间(ms)\n * @returns 定时器 ID\n */\n setTimeout(fn: () => void, ms: number): number {\n return this.host.setTimeout(fn, ms);\n }\n\n /**\n * 取消延迟执行。\n *\n * @param id - 定时器 ID\n */\n clearTimeout(id: number): void {\n this.host.clearTimeout(id);\n }\n\n /**\n * 获取当前队列中的任务数量。\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * 清空队列(不执行任务)。\n */\n clear(): void {\n if (this.timerId !== null) {\n this.host.clearTimeout(this.timerId);\n this.timerId = null;\n }\n this.queue.length = 0;\n this.scheduled = false;\n this.executedJobIds.clear();\n }\n\n /**\n * 销毁调度器,清理所有状态。\n */\n dispose(): void {\n this.clear();\n }\n\n // ==========================================================\n // 内部方法\n // ==========================================================\n\n /**\n * 调度异步刷新。\n */\n private scheduleFlush(): void {\n if (this.scheduled) return;\n this.scheduled = true;\n this.timerId = this.host.setTimeout(() => {\n this.timerId = null;\n this.scheduled = false;\n this.flush();\n }, 0);\n }\n\n /**\n * 执行队列中所有任务。\n */\n private flush(): void {\n if (this.flushing || this.queue.length === 0) return;\n\n this.flushing = true;\n\n // 取出当前所有任务\n const jobs = this.queue;\n this.queue = [];\n // FIX: P1-47 消除 executedJobIds 竞态窗口:\n // 不在 flush 开始时清空 executedJobIds,而是在每个任务执行后立即添加。\n // 同时在 schedule() 中不依赖 executedJobIds 进行去重(已在 queue 中去重),\n // executedJobIds 仅用于防止 flush 期间重复入队的任务被执行两次。\n // 因此此处无需 clear,而是在任务执行后立即 add。\n\n for (const job of jobs) {\n // FIX: P1-47 在执行前检查是否已在当前 flush 周期中执行过\n if (job.allowMerge && this.executedJobIds.has(job.id)) {\n continue;\n }\n try {\n job.fn();\n } catch (err) {\n // 任务执行失败不影响后续任务\n if (__DEV__) console.warn('[lytjs/async-scheduler] Error executing job:', err);\n }\n // 记录已执行的任务 ID(用于 allowMerge 去重)\n if (job.allowMerge) {\n this.executedJobIds.add(job.id);\n }\n }\n\n this.flushing = false;\n\n // FIX: P2-44 flush 完成后清空 executedJobIds,防止无限增长\n this.executedJobIds.clear();\n\n // 如果 flush 过程中又有新任务入队,继续调度\n if (this.queue.length > 0) {\n this.scheduleFlush();\n }\n }\n\n /**\n * 按优先级排序队列。\n *\n * 优先级高的排在前面,同优先级保持插入顺序(稳定排序)。\n * FIX: P2-42 添加第二排序键(任务 ID)确保排序稳定性\n */\n private sortQueue(): void {\n this.queue.sort((a, b) => {\n const weightA = PRIORITY_WEIGHT[a.priority]!;\n const weightB = PRIORITY_WEIGHT[b.priority]!;\n // 首先按优先级排序\n if (weightA !== weightB) {\n return weightA - weightB;\n }\n // FIX: P2-42 同优先级时按任务 ID 排序,确保稳定性\n return a.id - b.id;\n });\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lytjs/common-async-scheduler",
3
- "version": "6.5.0",
3
+ "version": "6.7.0",
4
4
  "description": "Async scheduler for unified timing operations with priority support in LytJS",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -27,7 +27,7 @@
27
27
  "sideEffects": false,
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
- "@lytjs/host-contract": "^6.0.0"
30
+ "@lytjs/host-contract": "^6.7.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "tsup": "^8.4.0",
package/src/index.ts CHANGED
@@ -1,335 +1,329 @@
1
- // @lytjs/common-async-scheduler
2
- // 异步调度器:统一异步时序操作,通过 host 注入的定时器执行,支持同步插队刷新
3
-
4
- declare const __DEV__: boolean;
5
-
6
- import type { RendererHost } from '@lytjs/host-contract';
7
-
8
- // ============================================================
9
- // 类型定义
10
- // ============================================================
11
-
12
- /**
13
- * 调度任务优先级。
14
- */
15
- export type SchedulerPriority = 'sync' | 'high' | 'normal' | 'low';
16
-
17
- /**
18
- * 调度任务。
19
- */
20
- export interface SchedulerJob {
21
- /** 任务 ID */
22
- id: number;
23
- /** 任务函数 */
24
- fn: () => void;
25
- /** 优先级 */
26
- priority: SchedulerPriority;
27
- /** 是否允许合并(同一 tick 内仅执行一次) */
28
- allowMerge: boolean;
29
- }
30
-
31
- /**
32
- * 异步调度器配置项。
33
- */
34
- export interface AsyncSchedulerOptions {
35
- /** 默认优先级(默认 'normal') */
36
- defaultPriority?: SchedulerPriority;
37
- /** 是否启用同步插队(默认 true) */
38
- enableFlushSync?: boolean;
39
- }
40
-
41
- // ============================================================
42
- // 常量
43
- // ============================================================
44
-
45
- /** 默认配置 */
46
- const DEFAULT_OPTIONS: Required<AsyncSchedulerOptions> = {
47
- defaultPriority: 'normal',
48
- enableFlushSync: true,
49
- };
50
-
51
- /** FIX: P2-40 优先级调度策略:支持自定义优先级权重 */
52
- /** 优先级权重映射(数值越小优先级越高) */
53
- const PRIORITY_WEIGHT: Record<SchedulerPriority, number> = {
54
- sync: 0,
55
- high: 1,
56
- normal: 2,
57
- low: 3,
58
- };
59
-
60
-
61
-
62
- // ============================================================
63
- // AsyncScheduler
64
- // ============================================================
65
-
66
- /**
67
- * 异步调度器。
68
- *
69
- * 统一异步时序操作,通过 RendererHost 注入的定时器执行,
70
- * 支持优先级排序、任务合并和同步插队刷新。
71
- *
72
- * @template HN - 宿主节点类型
73
- * @template HE - 宿主元素类型
74
- */
75
- export class AsyncScheduler<HN = unknown, HE extends HN = HN> {
76
- /** RendererHost 实例 */
77
- private host: RendererHost<HN, HE>;
78
-
79
- /** 配置项 */
80
- private options: Required<AsyncSchedulerOptions>;
81
-
82
- /** 待执行的任务队列 */
83
- private queue: SchedulerJob[] = [];
84
-
85
- /** 是否已调度刷新 */
86
- private scheduled = false;
87
-
88
- /** 调度的定时器 ID */
89
- private timerId: number | null = null;
90
-
91
- /** 是否正在执行刷新(防止重入) */
92
- private flushing = false;
93
-
94
- /** 已执行过的任务 ID 集合(用于 allowMerge 去重) */
95
- private executedJobIds = new Set<number>();
96
-
97
- /** FIX: P2-43 自增的任务 ID,改为实例属性避免全局变量 */
98
- private nextJobId = 0;
99
-
100
- /**
101
- * 创建异步调度器实例。
102
- * @param host - RendererHost 实例
103
- * @param options - 可选的配置项
104
- */
105
- constructor(host: RendererHost<HN, HE>, options?: AsyncSchedulerOptions) {
106
- this.host = host;
107
- this.options = { ...DEFAULT_OPTIONS, ...options };
108
- }
109
-
110
- // ==========================================================
111
- // 公开方法
112
- // ==========================================================
113
-
114
- /**
115
- * 调度一个任务。
116
- *
117
- * @param fn - 任务函数
118
- * @param priority - 任务优先级(默认使用配置的 defaultPriority)
119
- * @param allowMerge - 是否允许合并(默认 true)
120
- * @returns 任务 ID
121
- */
122
- schedule(
123
- fn: () => void,
124
- priority?: SchedulerPriority,
125
- allowMerge?: boolean,
126
- ): number {
127
- // FIX: P2-43 使用实例属性 nextJobId 替代全局变量
128
- const id = ++this.nextJobId;
129
- const job: SchedulerJob = {
130
- id,
131
- fn,
132
- priority: priority ?? this.options.defaultPriority,
133
- allowMerge: allowMerge ?? true,
134
- };
135
-
136
- // 如果允许合并,在 queue 中查找同 id 任务进行去重
137
- // 不依赖 executedJobIds(它在 flush 开始时被 clear,导致 flush 间隙竞态)
138
- if (job.allowMerge) {
139
- const existingIdx = this.queue.findIndex((q) => q.allowMerge && q.id === id);
140
- if (existingIdx !== -1) {
141
- // 队列中已有同 id 任务,替换为最新的
142
- this.queue[existingIdx] = job;
143
- this.sortQueue();
144
- this.scheduleFlush();
145
- return id;
146
- }
147
- }
148
-
149
- this.queue.push(job);
150
- this.sortQueue();
151
- this.scheduleFlush();
152
-
153
- return id;
154
- }
155
-
156
- /**
157
- * 调度一个同步任务(立即在当前 tick 执行)。
158
- *
159
- * @param fn - 任务函数
160
- */
161
- scheduleSync(fn: () => void): void {
162
- // FIX: P1-50 scheduleSync 检查 enableFlushSync 配置,
163
- // enableFlushSync 为 false 时回退到异步调度
164
- if (!this.options.enableFlushSync) {
165
- this.schedule(fn, 'sync', false);
166
- return;
167
- }
168
- // FIX: P2-43 使用实例属性 nextJobId 替代全局变量
169
- const id = ++this.nextJobId;
170
- const job: SchedulerJob = {
171
- id,
172
- fn,
173
- priority: 'sync',
174
- allowMerge: false,
175
- };
176
-
177
- this.queue.unshift(job);
178
- this.flushSync();
179
- }
180
-
181
- /**
182
- * 同步插队刷新:立即执行队列中所有待处理任务。
183
- *
184
- * 用于需要立即更新 DOM 的场景(如用户交互后读取布局信息)。
185
- */
186
- flushSync(): void {
187
- if (!this.options.enableFlushSync || this.flushing) return;
188
-
189
- // 取消已调度的异步刷新
190
- if (this.timerId !== null) {
191
- this.host.clearTimeout(this.timerId);
192
- this.timerId = null;
193
- }
194
- this.scheduled = false;
195
-
196
- this.flush();
197
- }
198
-
199
- /**
200
- * 在下一帧执行回调(通过 host.nextFrame)。
201
- *
202
- * @param fn - 回调函数
203
- */
204
- nextFrame(fn: () => void): void {
205
- this.host.nextFrame(fn);
206
- }
207
-
208
- /**
209
- * 延迟执行回调。
210
- *
211
- * @param fn - 回调函数
212
- * @param ms - 延迟时间(ms)
213
- * @returns 定时器 ID
214
- */
215
- setTimeout(fn: () => void, ms: number): number {
216
- return this.host.setTimeout(fn, ms);
217
- }
218
-
219
- /**
220
- * 取消延迟执行。
221
- *
222
- * @param id - 定时器 ID
223
- */
224
- clearTimeout(id: number): void {
225
- this.host.clearTimeout(id);
226
- }
227
-
228
- /**
229
- * 获取当前队列中的任务数量。
230
- */
231
- get size(): number {
232
- return this.queue.length;
233
- }
234
-
235
- /**
236
- * 清空队列(不执行任务)。
237
- */
238
- clear(): void {
239
- if (this.timerId !== null) {
240
- this.host.clearTimeout(this.timerId);
241
- this.timerId = null;
242
- }
243
- this.queue.length = 0;
244
- this.scheduled = false;
245
- this.executedJobIds.clear();
246
- }
247
-
248
- /**
249
- * 销毁调度器,清理所有状态。
250
- */
251
- dispose(): void {
252
- this.clear();
253
- }
254
-
255
- // ==========================================================
256
- // 内部方法
257
- // ==========================================================
258
-
259
- /**
260
- * 调度异步刷新。
261
- */
262
- private scheduleFlush(): void {
263
- if (this.scheduled) return;
264
- this.scheduled = true;
265
- this.timerId = this.host.setTimeout(() => {
266
- this.timerId = null;
267
- this.scheduled = false;
268
- this.flush();
269
- }, 0);
270
- }
271
-
272
- /**
273
- * 执行队列中所有任务。
274
- */
275
- private flush(): void {
276
- if (this.flushing || this.queue.length === 0) return;
277
-
278
- this.flushing = true;
279
-
280
- // 取出当前所有任务
281
- const jobs = this.queue;
282
- this.queue = [];
283
- // FIX: P1-47 消除 executedJobIds 竞态窗口:
284
- // 不在 flush 开始时清空 executedJobIds,而是在每个任务执行后立即添加。
285
- // 同时在 schedule() 中不依赖 executedJobIds 进行去重(已在 queue 中去重),
286
- // executedJobIds 仅用于防止 flush 期间重复入队的任务被执行两次。
287
- // 因此此处无需 clear,而是在任务执行后立即 add。
288
-
289
- for (const job of jobs) {
290
- // FIX: P1-47 在执行前检查是否已在当前 flush 周期中执行过
291
- if (job.allowMerge && this.executedJobIds.has(job.id)) {
292
- continue;
293
- }
294
- try {
295
- job.fn();
296
- } catch (err) {
297
- // 任务执行失败不影响后续任务
298
- if (__DEV__) console.warn('[lytjs/async-scheduler] Error executing job:', err);
299
- }
300
- // 记录已执行的任务 ID(用于 allowMerge 去重)
301
- if (job.allowMerge) {
302
- this.executedJobIds.add(job.id);
303
- }
304
- }
305
-
306
- this.flushing = false;
307
-
308
- // FIX: P2-44 flush 完成后清空 executedJobIds,防止无限增长
309
- this.executedJobIds.clear();
310
-
311
- // 如果 flush 过程中又有新任务入队,继续调度
312
- if (this.queue.length > 0) {
313
- this.scheduleFlush();
314
- }
315
- }
316
-
317
- /**
318
- * 按优先级排序队列。
319
- *
320
- * 优先级高的排在前面,同优先级保持插入顺序(稳定排序)。
321
- * FIX: P2-42 添加第二排序键(任务 ID)确保排序稳定性
322
- */
323
- private sortQueue(): void {
324
- this.queue.sort((a, b) => {
325
- const weightA = PRIORITY_WEIGHT[a.priority]!;
326
- const weightB = PRIORITY_WEIGHT[b.priority]!;
327
- // 首先按优先级排序
328
- if (weightA !== weightB) {
329
- return weightA - weightB;
330
- }
331
- // FIX: P2-42 同优先级时按任务 ID 排序,确保稳定性
332
- return a.id - b.id;
333
- });
334
- }
335
- }
1
+ // @lytjs/common-async-scheduler
2
+ // 异步调度器:统一异步时序操作,通过 host 注入的定时器执行,支持同步插队刷新
3
+
4
+ declare const __DEV__: boolean;
5
+
6
+ import type { RendererHost } from '@lytjs/host-contract';
7
+
8
+ // ============================================================
9
+ // 类型定义
10
+ // ============================================================
11
+
12
+ /**
13
+ * 调度任务优先级。
14
+ */
15
+ export type SchedulerPriority = 'sync' | 'high' | 'normal' | 'low';
16
+
17
+ /**
18
+ * 调度任务。
19
+ */
20
+ export interface SchedulerJob {
21
+ /** 任务 ID */
22
+ id: number;
23
+ /** 任务函数 */
24
+ fn: () => void;
25
+ /** 优先级 */
26
+ priority: SchedulerPriority;
27
+ /** 是否允许合并(同一 tick 内仅执行一次) */
28
+ allowMerge: boolean;
29
+ }
30
+
31
+ /**
32
+ * 异步调度器配置项。
33
+ */
34
+ export interface AsyncSchedulerOptions {
35
+ /** 默认优先级(默认 'normal') */
36
+ defaultPriority?: SchedulerPriority;
37
+ /** 是否启用同步插队(默认 true) */
38
+ enableFlushSync?: boolean;
39
+ }
40
+
41
+ // ============================================================
42
+ // 常量
43
+ // ============================================================
44
+
45
+ /** 默认配置 */
46
+ const DEFAULT_OPTIONS: Required<AsyncSchedulerOptions> = {
47
+ defaultPriority: 'normal',
48
+ enableFlushSync: true,
49
+ };
50
+
51
+ /** FIX: P2-40 优先级调度策略:支持自定义优先级权重 */
52
+ /** 优先级权重映射(数值越小优先级越高) */
53
+ const PRIORITY_WEIGHT: Record<SchedulerPriority, number> = {
54
+ sync: 0,
55
+ high: 1,
56
+ normal: 2,
57
+ low: 3,
58
+ };
59
+
60
+ // ============================================================
61
+ // AsyncScheduler
62
+ // ============================================================
63
+
64
+ /**
65
+ * 异步调度器。
66
+ *
67
+ * 统一异步时序操作,通过 RendererHost 注入的定时器执行,
68
+ * 支持优先级排序、任务合并和同步插队刷新。
69
+ *
70
+ * @template HN - 宿主节点类型
71
+ * @template HE - 宿主元素类型
72
+ */
73
+ export class AsyncScheduler<HN = unknown, HE extends HN = HN> {
74
+ /** RendererHost 实例 */
75
+ private host: RendererHost<HN, HE>;
76
+
77
+ /** 配置项 */
78
+ private options: Required<AsyncSchedulerOptions>;
79
+
80
+ /** 待执行的任务队列 */
81
+ private queue: SchedulerJob[] = [];
82
+
83
+ /** 是否已调度刷新 */
84
+ private scheduled = false;
85
+
86
+ /** 调度的定时器 ID */
87
+ private timerId: number | null = null;
88
+
89
+ /** 是否正在执行刷新(防止重入) */
90
+ private flushing = false;
91
+
92
+ /** 已执行过的任务 ID 集合(用于 allowMerge 去重) */
93
+ private executedJobIds = new Set<number>();
94
+
95
+ /** FIX: P2-43 自增的任务 ID,改为实例属性避免全局变量 */
96
+ private nextJobId = 0;
97
+
98
+ /**
99
+ * 创建异步调度器实例。
100
+ * @param host - RendererHost 实例
101
+ * @param options - 可选的配置项
102
+ */
103
+ constructor(host: RendererHost<HN, HE>, options?: AsyncSchedulerOptions) {
104
+ this.host = host;
105
+ this.options = { ...DEFAULT_OPTIONS, ...options };
106
+ }
107
+
108
+ // ==========================================================
109
+ // 公开方法
110
+ // ==========================================================
111
+
112
+ /**
113
+ * 调度一个任务。
114
+ *
115
+ * @param fn - 任务函数
116
+ * @param priority - 任务优先级(默认使用配置的 defaultPriority)
117
+ * @param allowMerge - 是否允许合并(默认 true)
118
+ * @returns 任务 ID
119
+ */
120
+ schedule(fn: () => void, priority?: SchedulerPriority, allowMerge?: boolean): number {
121
+ // FIX: P2-43 使用实例属性 nextJobId 替代全局变量
122
+ const id = ++this.nextJobId;
123
+ const job: SchedulerJob = {
124
+ id,
125
+ fn,
126
+ priority: priority ?? this.options.defaultPriority,
127
+ allowMerge: allowMerge ?? true,
128
+ };
129
+
130
+ // 如果允许合并,在 queue 中查找同 id 任务进行去重
131
+ // 不依赖 executedJobIds(它在 flush 开始时被 clear,导致 flush 间隙竞态)
132
+ if (job.allowMerge) {
133
+ const existingIdx = this.queue.findIndex((q) => q.allowMerge && q.id === id);
134
+ if (existingIdx !== -1) {
135
+ // 队列中已有同 id 任务,替换为最新的
136
+ this.queue[existingIdx] = job;
137
+ this.sortQueue();
138
+ this.scheduleFlush();
139
+ return id;
140
+ }
141
+ }
142
+
143
+ this.queue.push(job);
144
+ this.sortQueue();
145
+ this.scheduleFlush();
146
+
147
+ return id;
148
+ }
149
+
150
+ /**
151
+ * 调度一个同步任务(立即在当前 tick 执行)。
152
+ *
153
+ * @param fn - 任务函数
154
+ */
155
+ scheduleSync(fn: () => void): void {
156
+ // FIX: P1-50 scheduleSync 检查 enableFlushSync 配置,
157
+ // enableFlushSync 为 false 时回退到异步调度
158
+ if (!this.options.enableFlushSync) {
159
+ this.schedule(fn, 'sync', false);
160
+ return;
161
+ }
162
+ // FIX: P2-43 使用实例属性 nextJobId 替代全局变量
163
+ const id = ++this.nextJobId;
164
+ const job: SchedulerJob = {
165
+ id,
166
+ fn,
167
+ priority: 'sync',
168
+ allowMerge: false,
169
+ };
170
+
171
+ this.queue.unshift(job);
172
+ this.flushSync();
173
+ }
174
+
175
+ /**
176
+ * 同步插队刷新:立即执行队列中所有待处理任务。
177
+ *
178
+ * 用于需要立即更新 DOM 的场景(如用户交互后读取布局信息)。
179
+ */
180
+ flushSync(): void {
181
+ if (!this.options.enableFlushSync || this.flushing) return;
182
+
183
+ // 取消已调度的异步刷新
184
+ if (this.timerId !== null) {
185
+ this.host.clearTimeout(this.timerId);
186
+ this.timerId = null;
187
+ }
188
+ this.scheduled = false;
189
+
190
+ this.flush();
191
+ }
192
+
193
+ /**
194
+ * 在下一帧执行回调(通过 host.nextFrame)。
195
+ *
196
+ * @param fn - 回调函数
197
+ */
198
+ nextFrame(fn: () => void): void {
199
+ this.host.nextFrame(fn);
200
+ }
201
+
202
+ /**
203
+ * 延迟执行回调。
204
+ *
205
+ * @param fn - 回调函数
206
+ * @param ms - 延迟时间(ms)
207
+ * @returns 定时器 ID
208
+ */
209
+ setTimeout(fn: () => void, ms: number): number {
210
+ return this.host.setTimeout(fn, ms);
211
+ }
212
+
213
+ /**
214
+ * 取消延迟执行。
215
+ *
216
+ * @param id - 定时器 ID
217
+ */
218
+ clearTimeout(id: number): void {
219
+ this.host.clearTimeout(id);
220
+ }
221
+
222
+ /**
223
+ * 获取当前队列中的任务数量。
224
+ */
225
+ get size(): number {
226
+ return this.queue.length;
227
+ }
228
+
229
+ /**
230
+ * 清空队列(不执行任务)。
231
+ */
232
+ clear(): void {
233
+ if (this.timerId !== null) {
234
+ this.host.clearTimeout(this.timerId);
235
+ this.timerId = null;
236
+ }
237
+ this.queue.length = 0;
238
+ this.scheduled = false;
239
+ this.executedJobIds.clear();
240
+ }
241
+
242
+ /**
243
+ * 销毁调度器,清理所有状态。
244
+ */
245
+ dispose(): void {
246
+ this.clear();
247
+ }
248
+
249
+ // ==========================================================
250
+ // 内部方法
251
+ // ==========================================================
252
+
253
+ /**
254
+ * 调度异步刷新。
255
+ */
256
+ private scheduleFlush(): void {
257
+ if (this.scheduled) return;
258
+ this.scheduled = true;
259
+ this.timerId = this.host.setTimeout(() => {
260
+ this.timerId = null;
261
+ this.scheduled = false;
262
+ this.flush();
263
+ }, 0);
264
+ }
265
+
266
+ /**
267
+ * 执行队列中所有任务。
268
+ */
269
+ private flush(): void {
270
+ if (this.flushing || this.queue.length === 0) return;
271
+
272
+ this.flushing = true;
273
+
274
+ // 取出当前所有任务
275
+ const jobs = this.queue;
276
+ this.queue = [];
277
+ // FIX: P1-47 消除 executedJobIds 竞态窗口:
278
+ // 不在 flush 开始时清空 executedJobIds,而是在每个任务执行后立即添加。
279
+ // 同时在 schedule() 中不依赖 executedJobIds 进行去重(已在 queue 中去重),
280
+ // executedJobIds 仅用于防止 flush 期间重复入队的任务被执行两次。
281
+ // 因此此处无需 clear,而是在任务执行后立即 add。
282
+
283
+ for (const job of jobs) {
284
+ // FIX: P1-47 在执行前检查是否已在当前 flush 周期中执行过
285
+ if (job.allowMerge && this.executedJobIds.has(job.id)) {
286
+ continue;
287
+ }
288
+ try {
289
+ job.fn();
290
+ } catch (err) {
291
+ // 任务执行失败不影响后续任务
292
+ if (__DEV__) console.warn('[lytjs/async-scheduler] Error executing job:', err);
293
+ }
294
+ // 记录已执行的任务 ID(用于 allowMerge 去重)
295
+ if (job.allowMerge) {
296
+ this.executedJobIds.add(job.id);
297
+ }
298
+ }
299
+
300
+ this.flushing = false;
301
+
302
+ // FIX: P2-44 flush 完成后清空 executedJobIds,防止无限增长
303
+ this.executedJobIds.clear();
304
+
305
+ // 如果 flush 过程中又有新任务入队,继续调度
306
+ if (this.queue.length > 0) {
307
+ this.scheduleFlush();
308
+ }
309
+ }
310
+
311
+ /**
312
+ * 按优先级排序队列。
313
+ *
314
+ * 优先级高的排在前面,同优先级保持插入顺序(稳定排序)。
315
+ * FIX: P2-42 添加第二排序键(任务 ID)确保排序稳定性
316
+ */
317
+ private sortQueue(): void {
318
+ this.queue.sort((a, b) => {
319
+ const weightA = PRIORITY_WEIGHT[a.priority]!;
320
+ const weightB = PRIORITY_WEIGHT[b.priority]!;
321
+ // 首先按优先级排序
322
+ if (weightA !== weightB) {
323
+ return weightA - weightB;
324
+ }
325
+ // FIX: P2-42 同优先级时按任务 ID 排序,确保稳定性
326
+ return a.id - b.id;
327
+ });
328
+ }
329
+ }