After staring at auto generated js code for a while , I made some progress.
To answer a previous question: there is caching system built in to emscripten. I’m still not entirely sure how it works, but once you load your resources once, the default behaviour is to cache them (in Chrome temp folder?? On local machine??). So that means, if you delete the “.data” file, the app will still work as long as cache has not been cleared. So, step 1 of having new resources be loaded is to skip this caching part.
The second step is to separate the resource loading js code and the player js code (as weitjong suggests). Now, the resource loading code is actually autogenerated and on the first line of the of Player.js file (i.e. the compiled app js code). I isolated this code and deleted it from the Player.js file. To do this, you find all the code that comes after (and includes) the first “var Module;” block. That is, there is a bunch of code on the first line: "[color=#FF8040]var Module; …lots…of…code…;[/color] var Module … remove the orange bits. Btw, a good reference is the Atomic Engine Web Build code. After reading through that (and the Deployment assets) I was able to understand the flow of a emscripten web app a bit better.
Then, I copied that auto generated code in to a new file, called “ScratchPadResources.js”. I removed all the caching related code and what is left seems to be the resource loading mechanism:
var Module;
if (typeof Module === "undefined") Module = {};
if (!Module.expectedDataFileDownloads) {
Module.expectedDataFileDownloads = 0;
Module.finishedDataFileDownloads = 0
}
Module.expectedDataFileDownloads++;
((function() {
var loadPackage = (function(metadata) {
var PACKAGE_PATH;
if (typeof window === "object") { PACKAGE_PATH = window["encodeURIComponent"](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf("/")) + "/") } else if (typeof location !== "undefined") { PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf("/")) + "/") } else {
throw "using preloaded data can only be done on a web page or in a web worker"
}
var PACKAGE_NAME = "binScratchPad.data";
var REMOTE_PACKAGE_BASE = "ScratchPad.data";
if (typeof Module["locateFilePackage"] === "function" && !Module["locateFile"]) {
Module["locateFile"] = Module["locateFilePackage"];
Module.printErr("warning: you defined Module.locateFilePackage, that has been renamed to Module.locateFile (using your locateFilePackage for now)")
}
var REMOTE_PACKAGE_NAME = typeof Module["locateFile"] === "function" ? Module["locateFile"](REMOTE_PACKAGE_BASE) : (Module["filePackagePrefixURL"] || "") + REMOTE_PACKAGE_BASE;
var REMOTE_PACKAGE_SIZE = metadata.remote_package_size;
var PACKAGE_UUID = metadata.package_uuid;
function fetchRemotePackage(packageName, packageSize, callback, errback) {
var xhr = new XMLHttpRequest;
xhr.open("GET", packageName, true);
xhr.responseType = "arraybuffer";
xhr.onprogress = (function(event) {
var url = packageName;
var size = packageSize;
if (event.total) size = event.total;
if (event.loaded) {
if (!xhr.addedTotal) {
xhr.addedTotal = true;
if (!Module.dataFileDownloads) Module.dataFileDownloads = {};
Module.dataFileDownloads[url] = { loaded: event.loaded, total: size }
} else { Module.dataFileDownloads[url].loaded = event.loaded }
var total = 0;
var loaded = 0;
var num = 0;
for (var download in Module.dataFileDownloads) {
var data = Module.dataFileDownloads[download];
total += data.total;
loaded += data.loaded;
num++
}
total = Math.ceil(total * Module.expectedDataFileDownloads / num);
if (Module["setStatus"]) Module["setStatus"]("Downloading data... (" + loaded + "/" + total + ")")
} else if (!Module.dataFileDownloads) {
if (Module["setStatus"]) Module["setStatus"]("Downloading data...")
}
});
xhr.onload = (function(event) {
var packageData = xhr.response;
callback(packageData)
});
xhr.send(null)
}
function handleError(error) { console.error("package error:", error) }
var fetched = null,
fetchedCallback = null;
fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE, function(data) {
if (fetchedCallback) {
fetchedCallback(data);
fetchedCallback = null;
} else {
fetched = data;
}
}, handleError);
function runWithFS() {
function assert(check, msg) {
if (!check) throw msg + (new Error).stack
}
function DataRequest(start, end, crunched, audio) {
this.start = start;
this.end = end;
this.crunched = crunched;
this.audio = audio
}
DataRequest.prototype = {
requests: {},
open: (function(mode, name) {
this.name = name;
this.requests[name] = this;
Module["addRunDependency"]("fp " + this.name)
}),
send: (function() {}),
onload: (function() {
var byteArray = this.byteArray.subarray(this.start, this.end);
this.finish(byteArray)
}),
finish: (function(byteArray) {
var that = this;
Module["FS_createDataFile"](this.name, null, byteArray, true, true, true);
Module["removeRunDependency"]("fp " + that.name);
this.requests[this.name] = null
})
};
var files = metadata.files;
for (i = 0; i < files.length; ++i) {
(new DataRequest(files[i].start, files[i].end, files[i].crunched, files[i].audio)).open("GET", files[i].filename)
}
function processPackageData(arrayBuffer) {
Module.finishedDataFileDownloads++;
assert(arrayBuffer, "Loading data file failed.");
assert(arrayBuffer instanceof ArrayBuffer, "bad input to processPackageData");
var byteArray = new Uint8Array(arrayBuffer);
var curr;
if (Module["SPLIT_MEMORY"]) Module.printErr("warning: you should run the file packager with --no-heap-copy when SPLIT_MEMORY is used, otherwise copying into the heap may fail due to the splitting");
var ptr = Module["getMemory"](byteArray.length);
Module["HEAPU8"].set(byteArray, ptr);
DataRequest.prototype.byteArray = Module["HEAPU8"].subarray(ptr, ptr + byteArray.length);
var files = metadata.files;
for (i = 0; i < files.length; ++i) { DataRequest.prototype.requests[files[i].filename].onload() }
Module["removeRunDependency"]("datafile_binScratchPad.data")
}
Module["addRunDependency"]("datafile_binScratchPad.data");
if (!Module.preloadResults) Module.preloadResults = {};
Module.preloadResults[PACKAGE_NAME] = { fromCache: false };
if (fetched) {
processPackageData(fetched);
fetched = null;
} else {
fetchedCallback = processPackageData;
}
}
if (Module["calledRun"]) { runWithFS() } else {
if (!Module["preRun"]) Module["preRun"] = [];
Module["preRun"].push(runWithFS)
}
});
loadPackage({ "files": [{ "audio": 0, "start": 0, "crunched": 0, "end": 157508, "filename": "/CoreData.pak" }, { "audio": 0, "start": 157508, "crunched": 0, "end": 11440897, "filename": "/Data.pak" }], "remote_package_size": 11440897, "package_uuid": "7fd80462-f151-458c-8e32-73ec819b2b73" })
}))();
Ok, so I now have a “ScratchPadResources.js” file and a “ScratchPad.js” player file. Then, you simply adjust the shell (i.e. the webpage that loads the js code) to first run the Resources script, and then the Player script. Here is the relevant lines way at the bottom of the autogenerated webpage:
<script async type="text/javascript" src="ScratchPadResource.js"></script>
<script async type="text/javascript" src="ScratchPad.js"></script>
</body>
</html>
And that’s it! Start up a webserver, and I can now swap out the .data file with a new one, reload the page, and the new resources are used. So that’s good. What remains is to somehow call the emscripten packaging tool on the .pak files to create that new .data file…