UNPKG

speedy-vision

Version:

GPU-accelerated Computer Vision for JavaScript

455 lines (405 loc) 13.2 kB
/* * speedy-vision.js * GPU-accelerated Computer Vision for JavaScript * Copyright 2020-2022 Alexandre Martins <alemartf(at)gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * speedy-promise.js * Speedy Promises: a fast implementation of Promises */ const PENDING = 0; const FULFILLED = 1; const REJECTED = 2; const SUSPEND_ASYNC = 1; const asap = (typeof queueMicrotask !== 'undefined' && queueMicrotask) || // browsers (typeof process !== 'undefined' && process.nextTick) || // node.js (f => Promise.resolve().then(() => f())); // most compatible /** * SpeedyPromise: Super Fast Promises. SpeedyPromises can * interoperate with ES6 Promises. This implementation is * based on the Promises/A+ specification. * @template T */ export class SpeedyPromise { /** * Constructor * @param {function(function(T=): void, function(Error): void): void} callback */ constructor(callback) { this._state = PENDING; this._value = undefined; this._onFulfillment = null; this._onRejection = null; this._children = 0; this[0] = this; this._parent = undefined; this._flags = 0; this._fulfill = this._fulfill.bind(this); this._reject = this._reject.bind(this); this._resolve = this._resolve.bind(this); this._broadcastIfAsync = this._broadcastIfAsync.bind(this); callback(this._fulfill, this._reject); } /** * Setup handlers * @template U, V=never * @param {null|undefined|(function(T): U|PromiseLike<U>|SpeedyPromise<U>)} onFulfillment called when the SpeedyPromise is fulfilled * @param {null|undefined|(function(Error): V|PromiseLike<V>|SpeedyPromise<V>)} [onRejection] called when the SpeedyPromise is rejected * @returns {SpeedyPromise<U>} */ then(onFulfillment, onRejection = null) { const child = new SpeedyPromise(this._nop); child._onFulfillment = typeof onFulfillment === 'function' && onFulfillment; child._onRejection = typeof onRejection === 'function' && onRejection; child._parent = this; this[this._children++] = child; // attach child this._flags &= ~SUSPEND_ASYNC; // restore the async behavior this._notify(); return child; } /** * Setup rejection handler * @template U, V=never * @param {null|undefined|(function(Error): V|PromiseLike<V>|SpeedyPromise<V>)} [onRejection] called when the SpeedyPromise is rejected * @returns {SpeedyPromise<V>} */ catch(onRejection) { return this.then(null, onRejection); } /** * Execute a callback when the promise is settled * (i.e., fulfilled or rejected) * @param {function(): void} onFinally * @returns {SpeedyPromise<T>} */ finally(onFinally) { const fn = val => { onFinally(); return val; }; return this.then(fn, fn); } /** * Start the computation immediately, synchronously. * Can't afford to spend any time at all waiting for micro-tasks, etc. * @returns {SpeedyPromise<T>} this */ turbocharge() { let my = this; // suspend the async behavior this._flags |= SUSPEND_ASYNC; while(my._parent !== undefined) { my = my._parent; my._flags |= SUSPEND_ASYNC; } // notify the children of the root my._notify(); // will be synchronous // return this SpeedyPromise return this; } /** * Convert to string * @returns {string} */ toString() { switch(this._state) { case PENDING: return `SpeedyPromise { <pending> }`; case FULFILLED: return `SpeedyPromise { <fulfilled> ${this._value} }`; case REJECTED: return `SpeedyPromise { <rejected> ${this._value} }`; default: return ''; } } /** * Symbol.toStringTag * @returns {string} */ get [Symbol.toStringTag]() { return 'SpeedyPromise'; } /** * Creates a resolved SpeedyPromise * @template U * @param {U} [value] * @returns {SpeedyPromise<U>} */ static resolve(value) { const promise = new SpeedyPromise(this._snop); if((typeof value === 'object' && value !== null && 'then' in value) || (typeof value === 'function' && 'then' in value)) { // resolve asynchronously promise._resolve(value); } else { // fulfill synchronously promise._value = value; promise._state = FULFILLED; } return promise; } /** * Creates a rejected SpeedyPromise * @template U * @param {Error} reason * @returns {SpeedyPromise<U>} */ static reject(reason) { const promise = new SpeedyPromise(this._snop); promise._value = reason; promise._state = REJECTED; return promise; } /** * Returns a SpeedyPromise that resolves to an array * containing the results of the input promises/values, * in their given order. The returned SpeedyPromise will * resolve if all input promises resolve, or reject if * any input promise rejects. * @template U * @param {Iterable<U>|Iterable<SpeedyPromise<U>>|Iterable<Promise<U>>} iterable e.g., a SpeedyPromise[], a thenable[] * @returns {SpeedyPromise<U[]>} * * FIXME iterables need not be all <U> */ static all(iterable) { return new SpeedyPromise((resolve, reject) => { const input = []; // get elements for(const element of iterable) input.push(element); // resolve synchronously if there are no elements const length = input.length; if(length == 0) { resolve([]); return; } // resolve asynchronously let counter = length; const output = new Array(length); const partialResolve = i => (val => { output[i] = val; if(0 == --counter) resolve(output); }); for(let i = 0; i < length; i++) { const element = input[i]; if(element.__proto__ === SpeedyPromise.prototype || element.__proto__ === Promise.prototype) element.then(partialResolve(i), reject); else SpeedyPromise.resolve(element).then(partialResolve(i), reject); } }); } /** * Returns a promise that gets fulfilled or rejected as soon * as the first promise in the iterable gets fulfilled or * rejected (with its value/reason). * @template U * @param {Iterable<U>|Iterable<SpeedyPromise<U>>|Iterable<Promise<U>>} iterable e.g., a SpeedyPromise[], a thenable[] * @returns {SpeedyPromise<U>} */ static race(iterable) { return new SpeedyPromise((resolve, reject) => { const input = []; // get elements for(const element of iterable) input.push(element); // if the iterable is empty, the promise // will be pending forever... // resolve asynchronously const length = input.length; for(let i = 0; i < length; i++) { const element = input[i]; if(element.__proto__ === SpeedyPromise.prototype || element.__proto__ === Promise.prototype) element.then(resolve, reject); else SpeedyPromise.resolve(element).then(resolve, reject); } }); } /** * Fulfill this promise with a value * @param {T} value */ _fulfill(value) { this._setState(FULFILLED, value); } /** * Reject this promise with a reason * @param {Error} reason */ _reject(reason) { this._setState(REJECTED, reason); } /** * Set the state and the value of this promise * @param {number} state * @param {T|Error} value */ _setState(state, value) { // the promise is already fulfilled or rejected if(this._state != PENDING) return; // set the new state this._state = state; this._value = value; this._notify(); } /** * Notify my children that this promise is no * longer pending. This is an async operation: * my childen will be notified "as soon * as possible" (it will be scheduled). * We may force this to be synchronous, though */ _notify() { // nothing to do if(this._state == PENDING) return; // have we turbocharged this promise? if(this._flags & SUSPEND_ASYNC) { this._broadcast(); // execute synchronously return; } // install a timer (default behavior) asap(this._broadcastIfAsync); } /** * Helper method */ _broadcastIfAsync() { // we may have installed a timer at some // point, but turbocharged the promise later if(!(this._flags & SUSPEND_ASYNC)) this._broadcast(); } /** * Tell my children that this promise * is either fulfilled or rejected. * This is a synchronous operation */ _broadcast() { const children = this._children; const state = this._state; if(state === FULFILLED) { for(let i = 0; i < children; i++) { const child = this[i]; const callback = child._onFulfillment; try { if(callback) { if(callback !== child._nop) { child._resolve(callback(this._value)); // promise resolution procedure child._onFulfillment = child._nop; // will not be called again } } else child._fulfill(this._value); } catch(e) { child._reject(e); } } } else if(state === REJECTED) { for(let i = 0; i < children; i++) { const child = this[i]; const callback = child._onRejection; try { if(callback) { if(callback !== child._nop) { child._resolve(callback(this._value)); // promise resolution procedure child._onRejection = child._nop; // will not be called again } } else child._reject(this._value); } catch(e) { child._reject(e); } } } } /** * Promise Resolution Procedure * based on the Promises/A+ spec * @param {T} x */ _resolve(x) { if((typeof x !== 'object' && typeof x !== 'function') || (x === null)) { // if(x !== Object(x)) this._fulfill(x); return; } if(x === this) throw new TypeError(); // Circular reference if(x.__proto__ === SpeedyPromise.prototype || x.__proto__ === Promise.prototype) { x.then(this._resolve, this._reject); return; } try { const then = x.then; if(typeof then === 'function') { let resolve = this._resolve, reject = this._reject; try { then.call(x, y => { resolve(y); resolve = reject = this._nop; }, r => { reject(r); resolve = reject = this._nop; } ); } catch(e) { if(resolve !== this._nop && reject !== this._nop) this._reject(e); } } else { this._fulfill(x); } } catch(e) { this._reject(e); } } /** * No-operation */ _nop() { } /** * Static no-operation */ static _snop() { } } //module.exports = { SpeedyPromise }; /* // Uncomment to test performance with regular Promises module.exports = { SpeedyPromise: Promise }; Promise.prototype.turbocharge = function() { return this }; */