UNPKG

vim-webgl-component

Version:

A demonstration app built on top of the vim-webgl-viewer

483 lines (481 loc) 17 kB
"use strict"; /** * @module vim-ts */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BFast = exports.BFastHeader = exports.typeSize = exports.parseName = exports.Range = void 0; const remoteValue_1 = require("./http/remoteValue"); const remoteBuffer_1 = require("./http/remoteBuffer"); const pako = __importStar(require("pako")); class Range { constructor(start, end) { this.start = start; this.end = end; } get length() { return this.end - this.start; } offset(offset) { return new Range(this.start + offset, this.end + offset); } } exports.Range = Range; /** * Returns -1 size for undefined attributes. */ function parseName(name) { if (name.startsWith('g3d')) { const result = name.includes(':int8:') ? [1, Int8Array] : name.includes(':uint8:') ? [1, Uint8Array] : name.includes(':int16:') ? [2, Int16Array] : name.includes(':uint16:') ? [2, Uint16Array] : name.includes(':int32:') ? [4, Int32Array] : name.includes(':uint32:') ? [4, Uint32Array] : name.includes(':int64:') ? [8, BigInt64Array] : name.includes(':uint64:') ? [8, BigUint64Array] : name.includes(':float32:') ? [4, Float32Array] : name.includes(':float64:') ? [8, Float64Array] : [-1, undefined]; return result; } else { const result = name.startsWith('byte:') ? [1, Int8Array] : name.startsWith('ubyte:') ? [1, Uint8Array] : name.startsWith('short:') ? [2, Int16Array] : name.startsWith('ushort:') ? [2, Uint16Array] : name.startsWith('int:') ? [4, Int32Array] : name.startsWith('uint:') ? [4, Uint32Array] : name.startsWith('long:') ? [8, BigInt64Array] : name.startsWith('ulong:') ? [8, BigUint64Array] : name.startsWith('float:') ? [4, Float32Array] : name.startsWith('double:') ? [8, Float64Array] : [-1, undefined]; return result; } } exports.parseName = parseName; function typeSize(type) { switch (type) { case 'byte': case 'ubyte': return 1; case 'short': case 'ushort': return 2; case 'int': case 'uint': case 'float': return 4; case 'long': case 'ulong': case 'double': return 8; default: return 4; } } exports.typeSize = typeSize; function typeConstructor(type) { switch (type) { case 'byte': return Int8Array; case 'ubyte': return Uint8Array; case 'short': return Int16Array; case 'ushort': return Uint16Array; case 'int': return Int32Array; case 'uint': return Uint32Array; case 'long': return BigInt64Array; case 'ulong': return BigUint64Array; case 'float': return Float32Array; case 'double': return Float64Array; default: return Int32Array; } } /** * Bfast header, mainly for validation. * See https://github.com/vimaec/bfast */ class BFastHeader { constructor(magic, dataStart, dataEnd, numArrays) { if (magic !== 0xbfa5) { throw new Error('Invalid Bfast. Invalid Magic number'); } if (dataStart <= 32 || dataStart > Number.MAX_SAFE_INTEGER) { throw new Error('Invalid Bfast. Data start is out of valid range'); } if (dataEnd < dataStart || dataEnd > Number.MAX_SAFE_INTEGER) { throw new Error('Invalid Bfast. Data end is out of valid range'); } if (numArrays < 0 || numArrays > dataEnd) { throw new Error('Invalid Bfast. Number of arrays is invalid'); } this.magic = magic; this.dataStart = dataStart; this.dataEnd = dataEnd; this.numArrays = numArrays; } static createFromArray(array) { // Check validity of data // TODO: check endianness if (array[1] !== 0) { throw new Error('Invalid Bfast. Expected 0 in byte position 0'); } if (array[3] !== 0) { throw new Error('Invalid Bfast. Expected 0 in byte position 8'); } if (array[5] !== 0) { throw new Error('Invalid Bfast. Expected 0 in position 16'); } if (array[7] !== 0) { throw new Error('Invalid Bfast. Expected 0 in position 24'); } return new this(array[0], array[2], array[4], array[6]); } static createFromBuffer(array) { return BFastHeader.createFromArray(new Uint32Array(array)); } } exports.BFastHeader = BFastHeader; /** * See https://github.com/vimaec/bfast for bfast format spec * This implementation can either lazily request content as needed from http * Or it can serve the data directly from an ArrayBuffer * Remote mode can transition to buffer mode if server doesnt support partial http request */ class BFast { constructor(source, offset = 0, name = '') { this.source = source.buffer ? source.buffer : new remoteBuffer_1.RemoteBuffer(source.url, source.headers); this.offset = offset; this.name = name ?? "root"; this._header = new remoteValue_1.RemoteValue(() => this.requestHeader(), name + '.header'); this._children = new Map(); this._ranges = new remoteValue_1.RemoteValue(() => this.requestRanges(), name + '.ranges'); } /** * @returns url of the underlying RemoteBuffer if available */ get url() { return this.source instanceof remoteBuffer_1.RemoteBuffer ? this.source.url : undefined; } /** * Aborts all downloads from the underlying RemoteBuffer */ abort() { if (this.source instanceof remoteBuffer_1.RemoteBuffer) { this.source.abort(); } this._header.abort(); this._ranges.abort(); this._children.forEach(c => c.abort()); } /** * @returns Bfast Header */ async getHeader() { return this._header.get(); } /** * @returns a map of all buffers by names */ async getRanges() { return this._ranges.get(); } /** * Returns the buffer associated with name as a new bfast. * This value is cached for future requests. * @param name buffer name */ async getBfast(name) { let request = this._children.get(name); if (!request) { request = new remoteValue_1.RemoteValue(() => this.requestBfast(name)); this._children.set(name, request); } return request.get(); } async getLocalBfast(name, inflate = false) { let buffer = await this.getBuffer(name); if (!buffer) return undefined; if (inflate) { buffer = pako.inflateRaw(buffer).buffer; } return new BFast({ buffer }, 0, name); } /** * Returns the raw buffer associated with a name * This value is not cached. * @param name buffer name */ async getBuffer(name) { const ranges = await this.getRanges(); const range = ranges.get(name); if (!range) return undefined; const buffer = await this.request(range, name); return buffer; } /** * Returns a number array from the buffer associated with name * @param name buffer name */ async getArray(name) { const buffer = await this.getBuffer(name); if (!buffer) return undefined; const type = name.split(':')[0]; const Ctor = typeConstructor(type); const array = new Ctor(buffer); return array; } async getInt32Array(name) { const buffer = await this.getBuffer(name); if (!buffer) return; return new Int32Array(buffer); } async getFloat32Array(name) { const buffer = await this.getBuffer(name); if (!buffer) return; return new Float32Array(buffer); } async getBigInt64Array(name) { const buffer = await this.getBuffer(name); if (!buffer) return; return new BigInt64Array(buffer); } async getUint16Array(name) { const buffer = await this.getBuffer(name); if (!buffer) return; return new Uint16Array(buffer); } /** * Returns a single value from given buffer name * @param name buffer name * @param index row index */ async getValue(name, index) { const array = await this.getValues(name, index, 1); return array?.[0]; } async getRange(name) { const ranges = await this.getRanges(); return ranges.get(name); } /** * Returns count subsequent values from given buffer name. * @param name buffer name * @param index row index * @param count count of values to return */ async getValues(name, index, count) { if (index < 0 || count < 1) return undefined; const range = await this.getRange(name); if (!range) return undefined; const [size, ctor] = parseName(name); if (size < 0) return undefined; const start = Math.min(range.start + index * size, range.end); const end = Math.min(start + size * count, range.end); const dataRange = new Range(start, end); if (dataRange.length <= 0) return undefined; const buffer = await this.request(dataRange, `${name}[${index.toString()}]`); if (!buffer) return undefined; const array = new ctor(buffer); return array; } /** * Returns the buffer with given name as a byte array * @param name buffer name */ async getBytes(name) { const buffer = await this.getBuffer(name); if (!buffer) return undefined; const array = new Uint8Array(buffer); return array; } /** * Returns a map of name-values with the same index from all buffers. * @param name buffer name */ async getRow(index) { const ranges = await this.getRanges(); if (!ranges) return undefined; const result = new Map(); const promises = []; for (const name of ranges.keys()) { const p = this.getValue(name, index).then((v) => result.set(name, v)); promises.push(p); } await Promise.all(promises); return result; } /** * Forces download of the full underlying buffer, from now on all calls will be local. */ async forceDownload() { if (this.source instanceof ArrayBuffer) { console.log('Ignoring forceDownload on local buffer.'); return; } const buffer = await this.remote(undefined, this.name); if (!buffer) throw new Error('Failed to download BFAST.'); this.source = buffer; } /** * Downloads the appropriate range and cast it as a new sub bfast. */ async requestBfast(name) { const ranges = await this.getRanges(); const range = ranges.get(name); if (!range) return undefined; const result = new BFast({ buffer: this.source }, this.offset + range.start, this.name + '.' + name); return result; } /** * Downloads and parses ranges as a map of name->range */ async requestRanges() { const header = await this.getHeader(); const buffer = await this.request(new Range(32, 32 + header.numArrays * 16), 'Ranges'); if (!buffer) throw new Error('Could not get BFAST Ranges.'); // Parse range const array = new Uint32Array(buffer); const ranges = []; for (let i = 0; i < array.length; i += 4) { if (array[i + 1] !== 0 || array[i + 3] !== 0) { throw new Error('Invalid Bfast. 64 bit ranges not supported'); } ranges.push(new Range(array[i], array[i + 2])); } const names = await this.requestNames(ranges[0]); if (ranges.length !== names.length + 1) { throw new Error('Mismatched ranges and names count'); } // Map ranges and names const map = new Map(); for (let i = 0; i < names.length; i++) { map.set(names[i], ranges[i + 1]); } return map; } /** * Downloads and parse names from remote. */ async requestNames(range) { const buffer = await this.request(range, 'Names'); const names = new TextDecoder('utf-8').decode(buffer); const result = names.slice(0, -1).split('\0'); return result; } /** * Downloads and parse header from remote. */ async requestHeader() { const buffer = await this.request(new Range(0, 32), 'Header'); if (!buffer) throw new Error('Could not get BFAST Header'); const result = BFastHeader.createFromBuffer(buffer); return result; } /** * Gets array buffer from from cache, or partial http, fallback to full http * @param range range to get, or get full resource if undefined * @param label label for logs */ async request(range, label) { const buffer = this.local(range, label) ?? (await this.remote(range, label)) ?? (await this.remote(undefined, label)); if (!buffer) { throw new Error(`Could not load vim at ${this.source}`); } if (buffer.byteLength > range.length) { this.source = buffer; return this.local(range, label); } return buffer; } /** * returns requested range from cache. */ local(range, label) { if (!(this.source instanceof ArrayBuffer)) return undefined; //console.log(`Returning local ${this.name}.${label}`) const r = range.offset(this.offset); return this.source.slice(r.start, r.end); } /** * returns requested range from remote. */ async remote(range, label) { if (!(this.source instanceof remoteBuffer_1.RemoteBuffer)) return undefined; const r = range?.offset(this.offset); const buffer = await this.source.http(r, `${this.name}.${label}`); if (range && (buffer?.byteLength ?? 0) < range.length) { console.log('Range request request failed.'); return undefined; } return buffer; } /** * Returns a new local bfast equivalent to this bfast. */ async getSelf() { const header = await this._header.get(); const range = new Range(0, header.dataEnd); const buffer = await this.request(range, this.name); const result = new BFast({ buffer }, 0, this.name); return result; } } exports.BFast = BFast;