diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-10-19 16:44:37 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-10-19 16:44:37 -0700 |
commit | 04a6e44d823035ac7cdcdec4fa814c8e73556044 (patch) | |
tree | 1bad8fe2dd070a2c847f5211034ed1a9eb0e6e00 /src | |
parent | 98bda8bed35715a3d2dde32c398d34d93e5c227f (diff) | |
parent | 4e8a2eddd373f6f64fea1bb2e4aad1a4887621cd (diff) |
Merge pull request #648 from ysangkok/chunked-bin-xhr-lazy-loading
Chunked binary webworker xhr lazy loading
Diffstat (limited to 'src')
-rw-r--r-- | src/library.js | 93 | ||||
-rw-r--r-- | src/settings.js | 4 | ||||
-rw-r--r-- | src/shell.js | 26 |
3 files changed, 114 insertions, 9 deletions
diff --git a/src/library.js b/src/library.js index a89f1251..69642151 100644 --- a/src/library.js +++ b/src/library.js @@ -289,7 +289,85 @@ LibraryManager.library = { // XHR, which is not possible in browsers except in a web worker! Use preloading, // either --preload-file in emcc or FS.createPreloadedFile createLazyFile: function(parent, name, url, canRead, canWrite) { - var properties = {isDevice: false, url: url}; + + if (typeof XMLHttpRequest !== 'undefined') { + if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; + + // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. + var LazyUint8Array = function(chunkSize, length) { + this.length = length; + this.chunkSize = chunkSize; + this.chunks = []; // Loaded chunks. Index is the chunk number + this.isLazyUint8Array = true; + } + LazyUint8Array.prototype.get = function(idx) { + if (idx > this.length-1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % chunkSize; + var chunkNum = Math.floor(idx / chunkSize); + return this.getter(chunkNum)[chunkOffset]; + } + LazyUint8Array.prototype.setDataGetter = function(getter) { + this.getter = getter; + } + + // Find length + var xhr = new XMLHttpRequest(); + xhr.open('HEAD', url, false); + xhr.send(null); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); + var datalength = Number(xhr.getResponseHeader("Content-length")); + var header; + var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; +#if SMALL_CHUNKS + var chunkSize = 1024; // Chunk size in bytes +#else + var chunkSize = 1024*1024; // Chunk size in bytes +#endif + if (!hasByteServing) chunkSize = datalength; + + // Function to get a range from the remote URL. + var doXHR = (function(from, to) { + if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); + if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); + + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); + + // Some hints to the browser that we want binary data. + if (typeof Uint8Array != 'undefined') xhr.responseType = 'arraybuffer'; + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + xhr.send(null); + if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); + if (xhr.response !== undefined) { + return new Uint8Array(xhr.response || []); + } else { + return intArrayFromString(xhr.responseText || '', true); + } + }); + + var lazyArray = new LazyUint8Array(chunkSize, datalength); + lazyArray.setDataGetter(function(chunkNum) { + var start = chunkNum * lazyArray.chunkSize; + var end = (chunkNum+1) * lazyArray.chunkSize - 1; // including this byte + end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block + if (typeof(lazyArray.chunks[chunkNum]) === "undefined") { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof(lazyArray.chunks[chunkNum]) === "undefined") throw new Error("doXHR failed!"); + return lazyArray.chunks[chunkNum]; + }); + var properties = { isDevice: false, contents: lazyArray }; + } else { + var properties = { isDevice: false, url: url }; + } + return FS.createFile(parent, name, properties, canRead, canWrite); }, // Preloads a file asynchronously. You can call this before run, for example in @@ -360,8 +438,7 @@ LibraryManager.library = { if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; var success = true; if (typeof XMLHttpRequest !== 'undefined') { - // Browser. - throw 'Cannot do synchronous binary XHRs in modern browsers. Use --embed-file or --preload-file in emcc'; + throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); } else if (Module['read']) { // Command-line. try { @@ -1631,8 +1708,14 @@ LibraryManager.library = { } var contents = stream.object.contents; var size = Math.min(contents.length - offset, nbyte); - for (var i = 0; i < size; i++) { - {{{ makeSetValue('buf', 'i', 'contents[offset + i]', 'i8') }}} + if (contents.isLazyUint8Array) { + for (var i = 0; i < size; i++) { + {{{ makeSetValue('buf', 'i', 'contents.get(offset + i)', 'i8') }}} + } + } else { + for (var i = 0; i < size; i++) { + {{{ makeSetValue('buf', 'i', 'contents[offset + i]', 'i8') }}} + } } bytesRead += size; return bytesRead; diff --git a/src/settings.js b/src/settings.js index 5b1968b9..4017ad98 100644 --- a/src/settings.js +++ b/src/settings.js @@ -255,6 +255,10 @@ var WARN_ON_UNDEFINED_SYMBOLS = 0; // If set to 1, we will warn on any undefined // the existing buildsystem), and (2) functions might be // implemented later on, say in --pre-js +var SMALL_CHUNKS = 0; // Use small chunk size for binary synchronous XHR's in Web Workers. + // Used for testing. + // See test_chunked_synchronous_xhr in runner.py and library.js. + // Compiler debugging options var DEBUG_TAGS_SHOWING = []; // Some useful items: diff --git a/src/shell.js b/src/shell.js index 891a6328..3fb6cdbb 100644 --- a/src/shell.js +++ b/src/shell.js @@ -44,7 +44,9 @@ if (ENVIRONMENT_IS_NODE) { if (!Module['arguments']) { Module['arguments'] = process['argv'].slice(2); } -} else if (ENVIRONMENT_IS_SHELL) { +} + +if (ENVIRONMENT_IS_SHELL) { Module['print'] = print; if (typeof printErr != 'undefined') Module['printErr'] = printErr; // not present in v8 or older sm @@ -62,7 +64,9 @@ if (ENVIRONMENT_IS_NODE) { Module['arguments'] = arguments; } } -} else if (ENVIRONMENT_IS_WEB) { +} + +if (ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER) { if (!Module['print']) { Module['print'] = function(x) { console.log(x); @@ -74,7 +78,9 @@ if (ENVIRONMENT_IS_NODE) { console.log(x); }; } +} +if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { Module['read'] = function(url) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); @@ -87,12 +93,24 @@ if (ENVIRONMENT_IS_NODE) { Module['arguments'] = arguments; } } -} else if (ENVIRONMENT_IS_WORKER) { +} + +if (ENVIRONMENT_IS_WORKER) { // We can do very little here... + var TRY_USE_DUMP = false; + if (!Module['print']) { + Module['print'] = (TRY_USE_DUMP && (typeof(dump) !== "undefined") ? (function(x) { + dump(x); + }) : (function(x) { + self.postMessage(x); + })); + } Module['load'] = importScripts; +} -} else { +if (!ENVIRONMENT_IS_WORKER && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_SHELL) { + // Unreachable because SHELL is dependant on the others throw 'Unknown runtime environment. Where are we?'; } |