@surface/custom-element
Version:
Provides support of directives and data binding on custom elements.
107 lines (106 loc) • 3.59 kB
JavaScript
import { AggregateError, resolveError, runAsync } from "@surface/core";
class Queue {
constructor() {
this._length = 0;
this.node = null;
this.lastNode = null;
}
get length() {
return this._length;
}
enqueue(value) {
const node = { value };
if (this.node) {
this.lastNode.next = node;
}
else {
this.node = node;
}
this.lastNode = node;
this._length++;
}
dequeue() {
const value = this.node?.value;
this.node = this.node?.next ?? null;
this._length--;
if (this._length == 0) {
this.lastNode = null;
}
return value ?? null;
}
}
export default class Scheduler {
constructor(timeout) {
this.errors = [];
this.highPriorityQueue = new Queue();
this.lowPriorityQueue = new Queue();
this.normalPriorityQueue = new Queue();
this.currentExecution = Promise.resolve();
this.running = false;
this.timeout = timeout;
}
async nextFrame() {
return new Promise(resolve => window.requestAnimationFrame(resolve));
}
async processQueue(queue, hasPriorityChange) {
let expended = 0;
while (queue.length > 0) {
const [task, resolve, reject, cancellationToken] = queue.dequeue();
const start = window.performance.now();
if (cancellationToken?.canceled) {
resolve(undefined);
}
else {
try {
resolve(task());
}
catch (e) {
const error = resolveError(e);
this.errors.push(error);
reject(error);
}
}
const end = window.performance.now();
expended += end - start;
if (expended > this.timeout) {
expended = 0;
await this.nextFrame();
}
if (hasPriorityChange?.()) {
await this.execute();
}
}
}
async execute() {
await this.processQueue(this.highPriorityQueue);
await this.processQueue(this.normalPriorityQueue, () => this.highPriorityQueue.length > 0);
await this.processQueue(this.lowPriorityQueue, () => this.highPriorityQueue.length > 0 || this.normalPriorityQueue.length > 0);
}
start() {
this.errors.length = 0;
this.running = true;
this.currentExecution = runAsync(async () => (await this.execute(), this.stop()));
}
stop() {
this.running = false;
if (this.errors.length > 0) {
throw new AggregateError([...this.errors]);
}
}
async enqueue(task, priority, cancellationToken) {
if (!this.running) {
this.start();
}
switch (priority) {
case "high":
return new Promise((resolve, reject) => this.highPriorityQueue.enqueue([task, resolve, reject, cancellationToken]));
case "low":
return new Promise((resolve, reject) => this.lowPriorityQueue.enqueue([task, resolve, reject, cancellationToken]));
default:
return new Promise((resolve, reject) => this.normalPriorityQueue.enqueue([task, resolve, reject, cancellationToken]));
}
}
async execution() {
return this.currentExecution;
}
}