@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
168 lines (121 loc) • 4.47 kB
JavaScript
import { noop } from "../../../core/function/noop.js";
import { Asset } from "../Asset.js";
import { CrossOriginConfig } from "../CORS/CrossOriginConfig.js";
import { CrossOriginKind } from "../CORS/CrossOriginKind.js";
import { AssetLoader } from "./AssetLoader.js";
/**
*
* @param {Response} input
* @param {function} progress
* @return {Response}
*/
function observeResponseProgress(input, progress) {
let response = input
// Workaround: Checking if response.body === undefined for Alipay browser
if (typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined) {
return response;
}
// Nginx needs X-File-Size check
// https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
const contentLength = response.headers.get('Content-Length') || response.headers.get('X-File-Size');
const total = contentLength ? parseInt(contentLength) : 0;
let loaded = 0;
// periodically read data into the new stream tracking while download progress
const stream_prototype = {
type: "bytes",
start(controller) {
/**
* @type {ReadableStreamDefaultReader<Uint8Array>}
*/
const reader = response.body.getReader();
pump();
function pump() {
reader.read().then(({ done, value }) => {
if (done) {
// no more data, we're done
controller.close();
return;
}
loaded += value.byteLength;
progress(loaded, total);
controller.enqueue(value);
pump();
});
}
}
};
/**
* @type {ReadableStream}
*/
let stream;
try {
stream = new ReadableStream(stream_prototype);
response = new Response(stream);
} catch (e) {
/*
Workaround for Safari bug: "TypeError: ReadableByteStreamController is not implemented"
By not wrapping the response we lose the ability to track progress, but that's not a critical issue in most cases
*/
console.warn(e);
}
return response;
}
export class ArrayBufferLoader extends AssetLoader {
/**
*
* @param {"auto"|"low"|"high"} fetch_priority
*/
constructor({
fetch_priority = "auto"
} = {}) {
super();
/**
*
* @type {"auto"|"low"|"high"}
* @private
*/
this.__fetch_priority = fetch_priority;
}
async load(
scope,
path,
success,
failure = console.error,
progress = noop
) {
const coc = this.assetManager !== null ? this.assetManager.crossOriginConfig : CrossOriginConfig.default;
const headers = new Headers();
const request = new Request(path, {
credentials: coc.kind === CrossOriginKind.UseCredentials ? 'include' : 'same-origin',
headers: headers
});
if (typeof request.priority !== "undefined") {
// priority API is present
request.priority = this.__fetch_priority;
}
let response = await fetch(request);
const response_status = response.status;
if (response_status !== 200 && response_status !== 0) {
throw Error(`fetch for "${response.url}" responded with ${response_status}: ${response.statusText}`);
}
// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.
if (response_status === 0) {
console.warn('HTTP Status 0 received.');
}
try {
response = observeResponseProgress(response, progress);
} catch (e) {
console.warn('Failed to wrap response');
}
const arrayBuffer = await response.arrayBuffer();
const asset = new Asset(
function () {
return arrayBuffer;
},
arrayBuffer.byteSize
);
success(asset);
return asset;
}
}