@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
193 lines (159 loc) • 4.17 kB
JavaScript
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;
}
}