UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

193 lines (159 loc) 4.17 kB
import { assert } from "../../core/assert.js"; import BinaryHeap from "../../core/collection/heap/BinaryHeap.js"; import { noop } from "../../core/function/noop.js"; /** * @readonly * @type {{high: number, auto: number, low: number}} */ const priority_value_map = { "high": 10, "low": -1, "auto": 0 }; /** * * @param {string|undefined} p * @return {number} */ function extract_priority_from_resource(p) { let v = 0; if (typeof p === "string") { v = priority_value_map[p]; if (v === undefined) { console.warn(`Unsupported request priority value '${p}', defaulting to 'auto'`); } v = priority_value_map['auto']; } return v; } class FetchRequest { /** * * @param {Request|string} resource * @param {{}} options */ constructor(resource, options) { let priority = 0; this.__resource = resource; this.__options = options; if (typeof this.__resource === "object" && typeof this.__resource.priority !== "undefined") { priority = extract_priority_from_resource(this.__resource.priority); } this.__priority = priority; this.fulfillment = { resolve: null, reject: null }; this.promise = new Promise((resolve, reject) => { this.fulfillment.resolve = resolve; this.fulfillment.reject = reject; }); } } /** * * @param {FetchRequest} request * @returns {number} */ function get_priority_score(request) { return -request.__priority; } /** * @readonly * @type {number} */ const DEFAULT_CONCURRENCY = Infinity; /** * Wraps {@link fetch} api and adds concurrency management to it by queueing requests, * as well as prioritization in that queue */ export class PriorityFetch { constructor({ adapter = noop, concurrency = DEFAULT_CONCURRENCY }) { /** * * @type {number} * @private */ this.__concurrency = concurrency; /** * * @type {BinaryHeap<FetchRequest>} * @private */ this.__queue = new BinaryHeap(get_priority_score); /** * * @type {Set<FetchRequest>} * @private */ this.__pending_set = new Set(); /** * * @type {null} * @private */ this.__adapter = adapter; } /** * * @param {function} v */ set adapter(v) { this.__adapter = v; } /** * * @param {number} v */ set concurrency(v) { assert.isNumber(v, 'v'); assert.notNaN(v, 'v'); assert.greaterThan(v, 0, 'concurrency must be greater than 0'); this.__concurrency = v; } /** * * @return {number} */ get concurrency() { return this.__concurrency; } /** * * @param {FetchRequest} req * @private */ __dispatch(req) { const promise = this.__adapter(req.__resource, req.__options); promise.finally(() => { this.__pending_set.delete(req); this.__prod(); }); promise .then( (v) => { req.fulfillment.resolve(v); }, (reason) => { req.fulfillment.reject(reason); } ); } __prod() { if (this.__pending_set.size < this.__concurrency && !this.__queue.isEmpty()) { const req = this.__queue.pop(); this.__dispatch(req); } } fetch(resource, options) { const req = new FetchRequest(resource, options); if (this.__pending_set.size < this.__concurrency) { this.__dispatch(req); } else { this.__queue.push(req); } return req.promise; } }