maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
140 lines (128 loc) • 4.61 kB
text/typescript
/**
* The functions an {@link UpdateQueue} calls for each update.
*/
export type UpdateQueueHandlers<T, R> = {
/**
* Sends an update. The next one is not sent before the returned promise settles.
*/
send: (update: T) => Promise<R>;
/**
* Called with the result of an update, once it is no longer being sent.
* `replaced` is whether {@link UpdateQueue#replace} was called while the update was being sent, in which case
* the result describes a state that has since been replaced.
*/
onResult: (update: T, result: R, replaced: boolean) => void;
/**
* Called when sending an update, or handling its result, failed.
*/
onError: (update: T, error: unknown) => void;
};
/**
* Sends updates one at a time, in the order they were queued.
* An update that is still waiting can be changed in place, through {@link UpdateQueue#top}, until it is sent.
*/
export class UpdateQueue<T, R> {
private readonly _handlers: UpdateQueueHandlers<T, R>;
private _waiting: T[] = [];
private _sending = false;
/**
* Whether {@link UpdateQueue#replace} was called since the update being sent was sent.
*/
private _replacedWhileSending = false;
/**
* What every {@link UpdateQueue#flush} call returns until the queue is idle again.
* It is created by the first flush after the queue was idle, shared by the flushes that follow,
* and resolved and cleared once no update is left, so it is only set while a flush is in progress.
*/
private _flushing: {promise: Promise<void>; resolve: () => void} | undefined;
constructor(handlers: UpdateQueueHandlers<T, R>) {
this._handlers = handlers;
}
/**
* Whether no update is being sent or waiting to be.
*/
isIdle(): boolean {
return !this._sending && this._waiting.length === 0;
}
/**
* The update queued last, if it is still waiting to be sent.
* The update being sent is not waiting, so it is never returned.
*/
top(): T | undefined {
return this._waiting.length ? this._waiting[this._waiting.length - 1] : undefined;
}
/**
* Whether any waiting update matches the predicate. The update being sent is not waiting, so it is never tested.
*/
some(predicate: (update: T) => boolean): boolean {
return this._waiting.some(predicate);
}
/**
* Queues an update without sending it.
*/
enqueue(update: T): void {
this._waiting.push(update);
}
/**
* Drops the waiting updates and queues this one instead.
*/
replace(update: T): void {
this._waiting = [update];
this._replacedWhileSending = this._sending;
}
/**
* Drops the waiting updates. The update being sent, if any, still runs to its end.
*/
clear(): void {
this._waiting = [];
}
/**
* Sends the waiting updates.
* @returns a promise that resolves once no update is being sent or waiting to be.
*/
flush(): Promise<void> {
if (this.isIdle()) return Promise.resolve();
if (!this._flushing) {
let resolve: () => void;
const promise = new Promise<void>((r) => { resolve = r; });
this._flushing = {promise, resolve};
}
const {promise} = this._flushing;
this._next();
return promise;
}
private _next(): void {
if (this._sending) return;
const update = this._waiting.shift();
if (update === undefined) {
this._flushing?.resolve();
this._flushing = undefined;
return;
}
this._sending = true;
this._replacedWhileSending = false;
this._send(update).finally(() => this._next());
}
/**
* Sends one update and hands its result, or the error that sending or handling it raised, to the handlers.
*
* The update stops counting as being sent once, before either handler runs, so that an update a handler
* starts is still counted as being sent when that handler throws.
*/
private async _send(update: T): Promise<void> {
let result: R;
try {
result = await this._handlers.send(update);
} catch (error) {
this._sending = false;
this._handlers.onError(update, error);
return;
}
this._sending = false;
try {
this._handlers.onResult(update, result, this._replacedWhileSending);
} catch (error) {
this._handlers.onError(update, error);
}
}
}