main-thread-scheduling
Advanced tools
Comparing version 13.0.0 to 14.0.0
@@ -5,4 +5,1 @@ export { default as yieldOrContinue } from './src/yieldOrContinue'; | ||
export type { default as SchedulingStrategy } from './src/SchedulingStrategy'; | ||
export { default as queueTask } from './src/utils/queueTask'; | ||
export { default as withResolvers } from './src/utils/withResolvers'; | ||
export { default as afterFrame } from './src/utils/requestAfterFrame'; |
@@ -6,5 +6,1 @@ // primary | ||
export { default as isTimeToYield } from './src/isTimeToYield'; | ||
// utility | ||
export { default as queueTask } from './src/utils/queueTask'; | ||
export { default as withResolvers } from './src/utils/withResolvers'; | ||
export { default as afterFrame } from './src/utils/requestAfterFrame'; |
{ | ||
"name": "main-thread-scheduling", | ||
"version": "13.0.0", | ||
"version": "14.0.0", | ||
"description": "Fast and consistently responsive apps using a single function call", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -86,10 +86,2 @@ <br> | ||
#### `requestAfterFrame(callback)` | ||
_This is a utility function, most people don't need to use it._ The same way `requestAnimationFrame()` queues a `callback` to be executed just before a frame is rendered `requestAfterFrame()` is called just after a frame is rendered. | ||
#### `queueTask(callback)` | ||
_This is a utility function, most people don't need to use it._ The same way `queueMicrotask()` queues a `callback` to be executed in the end of the current microtask, `queueTask()` queues the task for the next task. You learn more at [here](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide#tasks_vs._microtasks). | ||
### More complex scenarios | ||
@@ -96,0 +88,0 @@ |
declare class FrameTracker { | ||
#private; | ||
constructor(); | ||
waitAnimationFrame(): Promise<void>; | ||
waitAfterFrame(): Promise<void>; | ||
@@ -6,0 +5,0 @@ start(): void; |
@@ -1,56 +0,40 @@ | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _FrameTracker_instances, _FrameTracker_timeoutId, _FrameTracker_requestAnimationId, _FrameTracker_deferred, _FrameTracker_loop; | ||
import withResolvers from './utils/withResolvers'; | ||
import { queueTask } from '../index'; | ||
import waitNextTask from './utils/waitNextTask'; | ||
class FrameTracker { | ||
#timeoutId; | ||
#requestAnimationId; | ||
#deferred; | ||
constructor() { | ||
_FrameTracker_instances.add(this); | ||
_FrameTracker_timeoutId.set(this, void 0); | ||
_FrameTracker_requestAnimationId.set(this, void 0); | ||
_FrameTracker_deferred.set(this, void 0); | ||
__classPrivateFieldSet(this, _FrameTracker_deferred, withResolvers(), "f"); | ||
this.#deferred = withResolvers(); | ||
} | ||
async waitAnimationFrame() { | ||
return __classPrivateFieldGet(this, _FrameTracker_deferred, "f").promise; | ||
} | ||
async waitAfterFrame() { | ||
await __classPrivateFieldGet(this, _FrameTracker_deferred, "f").promise; | ||
await new Promise((resolve) => queueTask(resolve)); | ||
await this.#deferred.promise; | ||
await waitNextTask(); | ||
} | ||
start() { | ||
clearTimeout(__classPrivateFieldGet(this, _FrameTracker_timeoutId, "f")); | ||
__classPrivateFieldSet(this, _FrameTracker_timeoutId, undefined, "f"); | ||
__classPrivateFieldGet(this, _FrameTracker_instances, "m", _FrameTracker_loop).call(this); | ||
clearTimeout(this.#timeoutId); | ||
this.#timeoutId = undefined; | ||
this.#loop(); | ||
} | ||
requestStop() { | ||
if (__classPrivateFieldGet(this, _FrameTracker_timeoutId, "f") === undefined) { | ||
__classPrivateFieldSet(this, _FrameTracker_timeoutId, setTimeout(() => { | ||
__classPrivateFieldSet(this, _FrameTracker_timeoutId, undefined, "f"); | ||
cancelAnimationFrame(__classPrivateFieldGet(this, _FrameTracker_requestAnimationId, "f")); | ||
__classPrivateFieldSet(this, _FrameTracker_requestAnimationId, undefined, "f"); | ||
}, 200), "f"); | ||
if (this.#timeoutId === undefined) { | ||
this.#timeoutId = setTimeout(() => { | ||
this.#timeoutId = undefined; | ||
cancelAnimationFrame(this.#requestAnimationId); | ||
this.#requestAnimationId = undefined; | ||
}, 200); | ||
} | ||
} | ||
#loop() { | ||
if (this.#requestAnimationId === undefined) { | ||
this.#requestAnimationId = requestAnimationFrame(() => { | ||
this.#requestAnimationId = undefined; | ||
this.#deferred.resolve(); | ||
this.#deferred = withResolvers(); | ||
this.#loop(); | ||
}); | ||
} | ||
} | ||
} | ||
_FrameTracker_timeoutId = new WeakMap(), _FrameTracker_requestAnimationId = new WeakMap(), _FrameTracker_deferred = new WeakMap(), _FrameTracker_instances = new WeakSet(), _FrameTracker_loop = function _FrameTracker_loop() { | ||
if (__classPrivateFieldGet(this, _FrameTracker_requestAnimationId, "f") === undefined) { | ||
__classPrivateFieldSet(this, _FrameTracker_requestAnimationId, requestAnimationFrame(() => { | ||
__classPrivateFieldSet(this, _FrameTracker_requestAnimationId, undefined, "f"); | ||
__classPrivateFieldGet(this, _FrameTracker_deferred, "f").resolve(); | ||
__classPrivateFieldSet(this, _FrameTracker_deferred, withResolvers(), "f"); | ||
__classPrivateFieldGet(this, _FrameTracker_instances, "m", _FrameTracker_loop).call(this); | ||
}), "f"); | ||
} | ||
}; | ||
const frameTracker = new FrameTracker(); | ||
export default frameTracker; |
import hasValidContext from './utils/hasValidContext'; | ||
import threadScheduler from './ThreadScheduler'; | ||
import toTask from './utils/toTask'; | ||
// #performance | ||
@@ -29,4 +30,4 @@ // calling `isTimeToYield()` thousand of times is slow | ||
cache.lastCallTime = now; | ||
cache.lastResult = threadScheduler.isTimeToYield(strategy); | ||
cache.lastResult = threadScheduler.isTimeToYield(toTask(strategy)); | ||
return cache.lastResult; | ||
} |
@@ -1,20 +0,8 @@ | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _RicTracker_idleCallbackId, _RicTracker_idleDeadline, _RicTracker_deferred; | ||
import withResolvers from './utils/withResolvers'; | ||
class RicTracker { | ||
#idleCallbackId; | ||
#idleDeadline; | ||
#deferred; | ||
constructor() { | ||
_RicTracker_idleCallbackId.set(this, void 0); | ||
_RicTracker_idleDeadline.set(this, void 0); | ||
_RicTracker_deferred.set(this, void 0); | ||
__classPrivateFieldSet(this, _RicTracker_deferred, withResolvers(), "f"); | ||
this.#deferred = withResolvers(); | ||
} | ||
@@ -25,26 +13,25 @@ get available() { | ||
get deadline() { | ||
return __classPrivateFieldGet(this, _RicTracker_idleDeadline, "f"); | ||
return this.#idleDeadline; | ||
} | ||
async waitIdleCallback() { | ||
return __classPrivateFieldGet(this, _RicTracker_deferred, "f").promise; | ||
return this.#deferred.promise; | ||
} | ||
start() { | ||
if (!this.available || __classPrivateFieldGet(this, _RicTracker_idleCallbackId, "f") !== undefined) { | ||
if (!this.available || this.#idleCallbackId !== undefined) { | ||
return; | ||
} | ||
__classPrivateFieldSet(this, _RicTracker_idleCallbackId, requestIdleCallback((deadline) => { | ||
__classPrivateFieldSet(this, _RicTracker_idleDeadline, deadline, "f"); | ||
__classPrivateFieldSet(this, _RicTracker_idleCallbackId, undefined, "f"); | ||
__classPrivateFieldGet(this, _RicTracker_deferred, "f").resolve(deadline); | ||
__classPrivateFieldSet(this, _RicTracker_deferred, withResolvers(), "f"); | ||
this.#idleCallbackId = requestIdleCallback((deadline) => { | ||
this.#idleDeadline = deadline; | ||
this.#idleCallbackId = undefined; | ||
this.#deferred.resolve(deadline); | ||
this.#deferred = withResolvers(); | ||
this.start(); | ||
}), "f"); | ||
}); | ||
} | ||
stop() { | ||
cancelIdleCallback(__classPrivateFieldGet(this, _RicTracker_idleCallbackId, "f")); | ||
__classPrivateFieldSet(this, _RicTracker_idleCallbackId, undefined, "f"); | ||
cancelIdleCallback(this.#idleCallbackId); | ||
this.#idleCallbackId = undefined; | ||
} | ||
} | ||
_RicTracker_idleCallbackId = new WeakMap(), _RicTracker_idleDeadline = new WeakMap(), _RicTracker_deferred = new WeakMap(); | ||
const ricTracker = new RicTracker(); | ||
export default ricTracker; |
@@ -1,6 +0,7 @@ | ||
import SchedulingStrategy from './SchedulingStrategy'; | ||
import { PromiseWithResolvers } from './utils/withResolvers'; | ||
type ScheduledTask = PromiseWithResolvers & { | ||
strategy: SchedulingStrategy; | ||
import type SchedulingTask from './SchedulingTask'; | ||
type ScheduledTask = SchedulingTask & { | ||
promise: Promise<void>; | ||
resolve: () => void; | ||
reject: (reason: DOMException) => void; | ||
}; | ||
export default ScheduledTask; |
@@ -1,10 +0,10 @@ | ||
import ScheduledTask from './ScheduledTask'; | ||
import SchedulingStrategy from './SchedulingStrategy'; | ||
import type ScheduledTask from './ScheduledTask'; | ||
import type SchedulingTask from './SchedulingTask'; | ||
declare class ThreadScheduler { | ||
#private; | ||
constructor(); | ||
createTask(strategy: SchedulingStrategy): ScheduledTask; | ||
isTimeToYield(strategy: SchedulingStrategy): boolean; | ||
schedule(task: SchedulingTask): ScheduledTask; | ||
isTimeToYield(task: SchedulingTask): boolean; | ||
} | ||
declare const threadScheduler: ThreadScheduler; | ||
export default threadScheduler; |
@@ -1,72 +0,73 @@ | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _ThreadScheduler_instances, _ThreadScheduler_tasks, _ThreadScheduler_topTask, _ThreadScheduler_workCycleTracker, _ThreadScheduler_completeTask, _ThreadScheduler_insertTask, _ThreadScheduler_removeTask; | ||
import WorkCycleTracker from './WorkCycleTracker'; | ||
import ReactiveTask from './utils/ReactiveTask'; | ||
import withResolvers from './utils/withResolvers'; | ||
import { requestPromiseEscape } from './utils/promiseEscape'; | ||
import ReactiveTask from './utils/ReactiveTask'; | ||
const strategyPriorities = { | ||
interactive: 30, | ||
smooth: 20, | ||
idle: 10, | ||
}; | ||
class ThreadScheduler { | ||
#tasks = []; | ||
#topTask = new ReactiveTask(); | ||
#workCycleTracker = new WorkCycleTracker(); | ||
constructor() { | ||
_ThreadScheduler_instances.add(this); | ||
_ThreadScheduler_tasks.set(this, []); | ||
_ThreadScheduler_topTask.set(this, new ReactiveTask()); | ||
_ThreadScheduler_workCycleTracker.set(this, new WorkCycleTracker()); | ||
__classPrivateFieldGet(this, _ThreadScheduler_topTask, "f").setEffect(async (task, signal) => { | ||
__classPrivateFieldGet(this, _ThreadScheduler_workCycleTracker, "f").startTracking(); | ||
await __classPrivateFieldGet(this, _ThreadScheduler_instances, "m", _ThreadScheduler_completeTask).call(this, task, signal); | ||
if (__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f").length === 0) { | ||
__classPrivateFieldGet(this, _ThreadScheduler_workCycleTracker, "f").requestStopTracking(); | ||
this.#topTask.setEffect(async (task, signal) => { | ||
this.#workCycleTracker.startTracking(); | ||
await this.#completeTask(task, signal); | ||
if (this.#tasks.length === 0) { | ||
this.#workCycleTracker.requestStopTracking(); | ||
} | ||
}); | ||
} | ||
createTask(strategy) { | ||
const task = { ...withResolvers(), strategy }; | ||
__classPrivateFieldGet(this, _ThreadScheduler_instances, "m", _ThreadScheduler_insertTask).call(this, task); | ||
return task; | ||
schedule(task) { | ||
const scheduled = { ...task, ...withResolvers() }; | ||
this.#insertTask(scheduled); | ||
task.signal?.addEventListener('abort', () => { | ||
this.#removeTask(scheduled); | ||
scheduled.reject(new DOMException('The operation was aborted.', 'AbortError')); | ||
}, { once: true }); | ||
return scheduled; | ||
} | ||
isTimeToYield(strategy) { | ||
return !__classPrivateFieldGet(this, _ThreadScheduler_workCycleTracker, "f").canWorkMore(strategy); | ||
isTimeToYield(task) { | ||
return !this.#workCycleTracker.canWorkMore(task); | ||
} | ||
} | ||
_ThreadScheduler_tasks = new WeakMap(), _ThreadScheduler_topTask = new WeakMap(), _ThreadScheduler_workCycleTracker = new WeakMap(), _ThreadScheduler_instances = new WeakSet(), _ThreadScheduler_completeTask = async function _ThreadScheduler_completeTask(task, signal) { | ||
while (!__classPrivateFieldGet(this, _ThreadScheduler_workCycleTracker, "f").canWorkMore(task.strategy)) { | ||
await __classPrivateFieldGet(this, _ThreadScheduler_workCycleTracker, "f").nextWorkCycle(task.strategy); | ||
if (signal.aborted) { | ||
return; | ||
async #completeTask(task, signal) { | ||
while (!this.#workCycleTracker.canWorkMore(task)) { | ||
await this.#workCycleTracker.nextWorkCycle(task); | ||
if (signal.aborted) { | ||
return; | ||
} | ||
} | ||
task.resolve(); | ||
// - wait for the user code to continue running to see if it will add more work to | ||
// be done. we prefer this, over continuing to the next task immediately | ||
// - if called at the end of an async method, it will wait for the async to get resolved in the | ||
// parent method that called it and will execute the provided callback in the next microtask. | ||
await new Promise((resolve) => { | ||
queueMicrotask(() => { | ||
queueMicrotask(() => { | ||
resolve(); | ||
}); | ||
}); | ||
}); | ||
this.#removeTask(task); | ||
} | ||
task.resolve(); | ||
// wait for the user code to continue running the code to see if he will add more work to | ||
// be done. we prefer this, other than continuing to the next task immediately | ||
await new Promise((resolve) => requestPromiseEscape(resolve)); | ||
__classPrivateFieldGet(this, _ThreadScheduler_instances, "m", _ThreadScheduler_removeTask).call(this, task); | ||
}, _ThreadScheduler_insertTask = function _ThreadScheduler_insertTask(task) { | ||
const priority = strategyPriorities[task.strategy]; | ||
for (let i = 0; i < __classPrivateFieldGet(this, _ThreadScheduler_tasks, "f").length; i++) { | ||
if (priority >= strategyPriorities[__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f")[i].strategy]) { | ||
__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f").splice(i, 0, task); | ||
__classPrivateFieldGet(this, _ThreadScheduler_topTask, "f").set(__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f")[0]); | ||
return; | ||
#insertTask(task) { | ||
const priority = task.priority; | ||
for (let i = 0; i < this.#tasks.length; i++) { | ||
if (priority >= this.#tasks[i].priority) { | ||
this.#tasks.splice(i, 0, task); | ||
this.#topTask.set(this.#tasks[0]); | ||
return; | ||
} | ||
} | ||
this.#tasks.push(task); | ||
this.#topTask.set(this.#tasks[0]); | ||
} | ||
__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f").push(task); | ||
__classPrivateFieldGet(this, _ThreadScheduler_topTask, "f").set(__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f")[0]); | ||
}, _ThreadScheduler_removeTask = function _ThreadScheduler_removeTask(task) { | ||
const index = __classPrivateFieldGet(this, _ThreadScheduler_tasks, "f").indexOf(task); | ||
if (index !== -1) { | ||
__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f").splice(index, 1); | ||
#removeTask(task) { | ||
const index = this.#tasks.indexOf(task); | ||
if (index !== -1) { | ||
this.#tasks.splice(index, 1); | ||
} | ||
if (index === 0) { | ||
this.#topTask.set(this.#tasks[0]); | ||
} | ||
} | ||
if (index === 0) { | ||
__classPrivateFieldGet(this, _ThreadScheduler_topTask, "f").set(__classPrivateFieldGet(this, _ThreadScheduler_tasks, "f")[0]); | ||
} | ||
}; | ||
} | ||
const threadScheduler = new ThreadScheduler(); | ||
export default threadScheduler; |
@@ -1,2 +0,2 @@ | ||
import ScheduledTask from '../ScheduledTask'; | ||
import type ScheduledTask from '../ScheduledTask'; | ||
export default class ReactiveTask { | ||
@@ -3,0 +3,0 @@ #private; |
@@ -1,37 +0,22 @@ | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var _ReactiveTask_task, _ReactiveTask_controller, _ReactiveTask_effect; | ||
// - reactivity for ScheduledTask | ||
// - otherwise, we would have to use something heavier like solid-js | ||
export default class ReactiveTask { | ||
constructor() { | ||
_ReactiveTask_task.set(this, void 0); | ||
_ReactiveTask_controller.set(this, new AbortController()); | ||
_ReactiveTask_effect.set(this, () => { }); | ||
} | ||
#task; | ||
#controller = new AbortController(); | ||
#effect = () => { }; | ||
set(task) { | ||
if (task === undefined) { | ||
__classPrivateFieldSet(this, _ReactiveTask_task, undefined, "f"); | ||
__classPrivateFieldGet(this, _ReactiveTask_controller, "f").abort(); | ||
this.#task = undefined; | ||
this.#controller.abort(); | ||
} | ||
else if (__classPrivateFieldGet(this, _ReactiveTask_task, "f") !== task) { | ||
__classPrivateFieldSet(this, _ReactiveTask_task, task, "f"); | ||
__classPrivateFieldGet(this, _ReactiveTask_controller, "f").abort(); | ||
__classPrivateFieldSet(this, _ReactiveTask_controller, new AbortController(), "f"); | ||
__classPrivateFieldGet(this, _ReactiveTask_effect, "f").call(this, __classPrivateFieldGet(this, _ReactiveTask_task, "f"), __classPrivateFieldGet(this, _ReactiveTask_controller, "f").signal); | ||
else if (this.#task !== task) { | ||
this.#task = task; | ||
this.#controller.abort(); | ||
this.#controller = new AbortController(); | ||
this.#effect(this.#task, this.#controller.signal); | ||
} | ||
} | ||
setEffect(effect) { | ||
__classPrivateFieldSet(this, _ReactiveTask_effect, effect, "f"); | ||
this.#effect = effect; | ||
} | ||
} | ||
_ReactiveTask_task = new WeakMap(), _ReactiveTask_controller = new WeakMap(), _ReactiveTask_effect = new WeakMap(); |
@@ -1,2 +0,2 @@ | ||
import SchedulingStrategy from './SchedulingStrategy'; | ||
import type SchedulingTask from './SchedulingTask'; | ||
export default class WorkCycleTracker { | ||
@@ -6,4 +6,4 @@ #private; | ||
requestStopTracking(): void; | ||
canWorkMore(strategy: SchedulingStrategy): boolean; | ||
nextWorkCycle(strategy: SchedulingStrategy): Promise<void>; | ||
canWorkMore(task: SchedulingTask): boolean; | ||
nextWorkCycle(task: SchedulingTask): Promise<void>; | ||
} |
@@ -1,20 +0,6 @@ | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
}; | ||
var _WorkCycleTracker_instances, _WorkCycleTracker_workCycleStart, _WorkCycleTracker_calculateDeadline; | ||
import ricTracker from './ricTracker'; | ||
import frameTracker from './frameTracker'; | ||
import waitHiddenTask from './utils/waitHiddenTask'; | ||
export default class WorkCycleTracker { | ||
constructor() { | ||
_WorkCycleTracker_instances.add(this); | ||
_WorkCycleTracker_workCycleStart.set(this, -1); | ||
} | ||
#workCycleStart = -1; | ||
startTracking() { | ||
@@ -28,15 +14,11 @@ ricTracker.start(); | ||
} | ||
canWorkMore(strategy) { | ||
var _a, _b; | ||
const isInputPending = ((_b = (_a = navigator.scheduling) === null || _a === void 0 ? void 0 : _a.isInputPending) === null || _b === void 0 ? void 0 : _b.call(_a)) === true; | ||
return !isInputPending && __classPrivateFieldGet(this, _WorkCycleTracker_instances, "m", _WorkCycleTracker_calculateDeadline).call(this, strategy) - Date.now() > 0; | ||
canWorkMore(task) { | ||
const isInputPending = navigator.scheduling?.isInputPending?.() === true; | ||
return !isInputPending && this.#calculateDeadline(task) - Date.now() > 0; | ||
} | ||
async nextWorkCycle(strategy) { | ||
if (strategy === 'interactive') { | ||
await frameTracker.waitAfterFrame(); | ||
async nextWorkCycle(task) { | ||
if (task.type === 'frame-based') { | ||
await Promise.race([frameTracker.waitAfterFrame(), waitHiddenTask()]); | ||
} | ||
else if (strategy === 'smooth') { | ||
await frameTracker.waitAfterFrame(); | ||
} | ||
else if (strategy === 'idle') { | ||
else if (task.type === 'idle-based') { | ||
if (ricTracker.available) { | ||
@@ -46,22 +28,24 @@ await ricTracker.waitIdleCallback(); | ||
else { | ||
// todo: use waitHiddenTask() with a timeout | ||
await frameTracker.waitAfterFrame(); | ||
} | ||
} | ||
__classPrivateFieldSet(this, _WorkCycleTracker_workCycleStart, Date.now(), "f"); | ||
this.#workCycleStart = Date.now(); | ||
} | ||
#calculateDeadline(task) { | ||
if (task.type === 'frame-based') { | ||
// const timePerFrame = 1000 / fps.guessRefreshRate() | ||
// const multiplier = timePerFrame / fps.guessRefreshRate() | ||
// const maxWorkTime = fps.fps() * multiplier | ||
// return this.#workCycleStart + maxWorkTime | ||
return this.#workCycleStart + task.workTime; | ||
} | ||
else if (task.type === 'idle-based') { | ||
const idleDeadline = ricTracker.deadline === undefined | ||
? Number.MAX_SAFE_INTEGER | ||
: Date.now() + ricTracker.deadline.timeRemaining(); | ||
return Math.min(this.#workCycleStart + task.workTime, idleDeadline); | ||
} | ||
return -1; | ||
} | ||
} | ||
_WorkCycleTracker_workCycleStart = new WeakMap(), _WorkCycleTracker_instances = new WeakSet(), _WorkCycleTracker_calculateDeadline = function _WorkCycleTracker_calculateDeadline(strategy) { | ||
if (strategy === 'interactive') { | ||
return __classPrivateFieldGet(this, _WorkCycleTracker_workCycleStart, "f") + 83; | ||
} | ||
else if (strategy === 'smooth') { | ||
return __classPrivateFieldGet(this, _WorkCycleTracker_workCycleStart, "f") + 13; | ||
} | ||
else if (strategy === 'idle') { | ||
const idleDeadline = ricTracker.deadline === undefined | ||
? Number.MAX_SAFE_INTEGER | ||
: Date.now() + ricTracker.deadline.timeRemaining(); | ||
return Math.min(__classPrivateFieldGet(this, _WorkCycleTracker_workCycleStart, "f") + 5, idleDeadline); | ||
} | ||
return -1; | ||
}; |
@@ -12,2 +12,2 @@ import SchedulingStrategy from './SchedulingStrategy'; | ||
*/ | ||
export default function yieldControl(strategy?: SchedulingStrategy): Promise<void>; | ||
export default function yieldControl(strategy?: SchedulingStrategy, signal?: AbortSignal): Promise<void>; |
import hasValidContext from './utils/hasValidContext'; | ||
import threadScheduler from './ThreadScheduler'; | ||
import toTask from './utils/toTask'; | ||
/** | ||
@@ -13,8 +14,7 @@ * Waits for the browser to become idle again in order to resume work. Calling `yieldControl()` | ||
*/ | ||
export default async function yieldControl(strategy = 'smooth') { | ||
export default async function yieldControl(strategy = 'smooth', signal) { | ||
if (!hasValidContext()) { | ||
return; | ||
} | ||
const task = threadScheduler.createTask(strategy); | ||
return task.promise; | ||
return threadScheduler.schedule(toTask(strategy, signal)).promise; | ||
} |
@@ -14,2 +14,2 @@ import SchedulingStrategy from './SchedulingStrategy'; | ||
*/ | ||
export default function yieldOrContinue(priority?: SchedulingStrategy): Promise<void>; | ||
export default function yieldOrContinue(priority?: SchedulingStrategy, signal?: AbortSignal): Promise<void>; |
@@ -17,7 +17,7 @@ import yieldControl from './yieldControl'; | ||
// eslint-disable-next-line @typescript-eslint/promise-function-async | ||
export default function yieldOrContinue(priority = 'smooth') { | ||
if (isTimeToYield(priority)) { | ||
return yieldControl(priority); | ||
export default function yieldOrContinue(priority = 'smooth', signal) { | ||
if (signal?.aborted !== true && isTimeToYield(priority)) { | ||
return yieldControl(priority, signal); | ||
} | ||
return Promise.resolve(); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
40
0
30477
531
157