Add image lazy loading

This commit is contained in:
Varun Patil 2020-10-20 03:13:10 +05:30
parent 27d55a776e
commit 8ff0e868ca
10 changed files with 123 additions and 29 deletions

View File

@ -435,7 +435,7 @@ SET(ASYNCIFY "-s ASYNCIFY=1 -s 'ASYNCIFY_IMPORTS=[\"load_file_async_js\"]'")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EMS_FLAGS} ${ERR_FLAGS} ${ASYNCIFY}") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EMS_FLAGS} ${ERR_FLAGS} ${ASYNCIFY}")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EMS_FLAGS} ${ERR_FLAGS} ${ASYNCIFY}") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EMS_FLAGS} ${ERR_FLAGS} ${ASYNCIFY}")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMS_FLAGS} ${ASYNCIFY} -lopenal -s DISABLE_EXCEPTION_CATCHING=1 -s ASSERTIONS=0 -s SAFE_HEAP=0 -s MINIFY_HTML=0 --shell-file extra/shell.html -s EMULATE_FUNCTION_POINTER_CASTS=0 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMS_FLAGS} ${ASYNCIFY} -lopenal -s DISABLE_EXCEPTION_CATCHING=1 -s ASSERTIONS=0 -s SAFE_HEAP=0 -s MINIFY_HTML=0 --shell-file extra/shell.html -s EMULATE_FUNCTION_POINTER_CASTS=0 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS='[\"_main\",\"_reloadBitmap\"]' -s EXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\"]'")
set_target_properties( set_target_properties(
${PROJECT_NAME} ${PROJECT_NAME}

View File

@ -19,7 +19,13 @@ function _base64ToBytes(base64) {
return bytes; return bytes;
} }
window.loadFileAsync = function(fullPath, callback) { // Canvas used for image generation
var generationCanvas = document.createElement('canvas')
window.loadFileAsync = function(fullPath, bitmap, callback) {
// noop
callback = callback || (() => {});
// Make cache object // Make cache object
if (!window.fileAsyncCache) window.fileAsyncCache = {}; if (!window.fileAsyncCache) window.fileAsyncCache = {};
@ -27,7 +33,7 @@ window.loadFileAsync = function(fullPath, callback) {
if (window.fileAsyncCache.hasOwnProperty(fullPath)) return callback(); if (window.fileAsyncCache.hasOwnProperty(fullPath)) return callback();
// Show spinner // Show spinner
if (window.setBusy) window.setBusy(); if (!bitmap && window.setBusy) window.setBusy();
// Get mapping key // Get mapping key
const mappingKey = fullPath.toLowerCase().replace(new RegExp("\\.[^/.]+$"), ""); const mappingKey = fullPath.toLowerCase().replace(new RegExp("\\.[^/.]+$"), "");
@ -46,18 +52,47 @@ window.loadFileAsync = function(fullPath, callback) {
const path = "/game/" + mappingValue.substring(0, mappingValue.lastIndexOf("/")); const path = "/game/" + mappingValue.substring(0, mappingValue.lastIndexOf("/"));
const filename = mappingValue.substring(mappingValue.lastIndexOf("/") + 1).split("?")[0]; const filename = mappingValue.substring(mappingValue.lastIndexOf("/") + 1).split("?")[0];
// Delete original file if existent // Main loading function
try { const load = (cb1) => {
FS.unlink(path + "/" + filename); getLazyAsset(iurl, filename, (data) => {
} catch (err) {} // Delete original file if existent
try { FS.unlink(path + "/" + filename); } catch (err) {}
// Get the new file FS.createPreloadedFile(path, filename, new Uint8Array(data), true, true, function() {
getLazyAsset(iurl, filename, () => { window.fileAsyncCache[fullPath] = 1;
FS.createPreloadedFile(path, filename, iurl, true, true, function() { if (!bitmap && window.setNotBusy) window.setNotBusy();
window.fileAsyncCache[fullPath] = 1; if (window.fileLoadedAsync) window.fileLoadedAsync(fullPath);
if (window.setNotBusy) window.setNotBusy(); callback();
if (window.fileLoadedAsync) window.fileLoadedAsync(fullPath); if (cb1) cb1();
callback(); }, console.error);
});
}
// Show progress if doing it synchronously only
if (bitmap && bitmapSizeMapping[mappingKey]) {
// Remove existing file
try { FS.unlink(path + "/" + filename); } catch (err) {}
// Get image
const sm = bitmapSizeMapping[mappingKey];
generationCanvas.width = sm[0];
generationCanvas.height = sm[1];
// Create dummy from data uri
FS.createPreloadedFile(path, filename, generationCanvas.toDataURL(), true, true, function() {
// Return control to C++
callback(); callback = () => {};
// Lazy load and refresh
load(() => {
const reloadBitmap = Module.cwrap('reloadBitmap', 'number', ['number'])
reloadBitmap(bitmap);
});
}, console.error); }, console.error);
}); } else {
if (bitmap) {
console.warn('No sizemap for image', mappingKey);
}
load();
}
} }

View File

@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
echo "var mapping = {" > mapping.js echo "var mapping = {" > mapping.js
echo "var bitmapSizeMapping = {" > bitmap-map.js
for file in {*,*/*,*/**/*} for file in {*,*/*,*/**/*}
do do
@ -17,7 +18,16 @@ fi
echo "\"$fl\": \"${file}?h=${md5}\"," >> mapping.js echo "\"$fl\": \"${file}?h=${md5}\"," >> mapping.js
if [ -f $file ]
then
sz=`identify -format "%w,%h" "${file}" 2>/dev/null`
if [ $? -eq 0 ]; then
echo "\"$fl\": [${sz}]," >> bitmap-map.js
fi
fi
done done
echo "};" >> mapping.js echo "};" >> mapping.js
echo "};" >> bitmap-map.js

View File

@ -8,6 +8,7 @@
<script src="js/localforage.min.js"></script> <script src="js/localforage.min.js"></script>
<script src="js/drive.js"></script> <script src="js/drive.js"></script>
<script src="gameasync/mapping.js"></script> <script src="gameasync/mapping.js"></script>
<script src="gameasync/bitmap-map.js"></script>
<title>MKXP</title> <title>MKXP</title>
<style> <style>
@ -292,6 +293,7 @@
var hideTimer = 0; var hideTimer = 0;
function getLazyAsset(url, filename, callback) { function getLazyAsset(url, filename, callback) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
const pdiv = document.getElementById("progress"); const pdiv = document.getElementById("progress");
let showTimer = 0; let showTimer = 0;
let abortTimer = 0; let abortTimer = 0;
@ -308,10 +310,9 @@
pdiv.style.opacity = '0'; pdiv.style.opacity = '0';
hideTimer = 0; hideTimer = 0;
}, 500); }, 500);
callback();
clearTimeout(showTimer); clearTimeout(showTimer);
clearTimeout(abortTimer); clearTimeout(abortTimer);
callback(xhr.response);
} }
} }
xhr.onprogress = function (event) { xhr.onprogress = function (event) {

View File

@ -258,8 +258,22 @@ struct BitmapOpenHandler : FileSystem::OpenHandler
Bitmap::Bitmap(const char *filename) Bitmap::Bitmap(const char *filename)
{ {
strcpy(this->filename, filename);
this->loadFromFilename();
}
void Bitmap::loadFromFilename()
{
char * filename = this->filename;
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
load_file_async_js(filename); bool reloading = false;
if (this->p) {
this->releaseResources();
reloading = true;
} else {
load_file_async_js(filename, (int)this);
}
#endif #endif
BitmapOpenHandler handler; BitmapOpenHandler handler;
@ -267,7 +281,7 @@ Bitmap::Bitmap(const char *filename)
SDL_Surface *imgSurf = handler.surf; SDL_Surface *imgSurf = handler.surf;
if (!imgSurf) { if (!imgSurf) {
printf("ERROR OCCURED LOADING IMAGE %s : %s\n", filename, SDL_GetError()); printf("Error occured loading image %s : %s\n", filename, SDL_GetError());
throw Exception(Exception::SDLError, "Error loading image '%s': %s", throw Exception(Exception::SDLError, "Error loading image '%s': %s",
filename, SDL_GetError()); filename, SDL_GetError());
} }
@ -306,6 +320,11 @@ Bitmap::Bitmap(const char *filename)
} }
p->addTaintedArea(rect()); p->addTaintedArea(rect());
#ifdef __EMSCRIPTEN__
if (reloading && this->reloadCallback)
this->reloadCallback(this->reloadCallbackData);
#endif
} }
Bitmap::Bitmap(int width, int height) Bitmap::Bitmap(int width, int height)
@ -1334,3 +1353,15 @@ void Bitmap::releaseResources()
delete p; delete p;
} }
#ifdef __EMSCRIPTEN__
extern "C" {
int reloadBitmap(int intptr) {
Bitmap * bitmap = (Bitmap*) intptr;
if (bitmap->isDisposed()) return 0;
bitmap->loadFromFilename();
return 1;
}
}
#endif

View File

@ -119,11 +119,16 @@ public:
sigc::signal<void> modified; sigc::signal<void> modified;
char filename[512];
void loadFromFilename();
void (*reloadCallback)(void *) = NULL;
void * reloadCallbackData = NULL;
private: private:
void releaseResources(); void releaseResources();
const char *klassName() const { return "bitmap"; } const char *klassName() const { return "bitmap"; }
BitmapPrivate *p; BitmapPrivate *p = 0;
}; };
#endif // BITMAP_H #endif // BITMAP_H

View File

@ -2,9 +2,9 @@
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
EM_JS(void, load_file_async_js, (const char* fullPathC), { EM_JS(void, load_file_async_js, (const char* fullPathC, int bitmap), {
Asyncify.handleSleep(function(wakeUp) { Asyncify.handleSleep(function(wakeUp) {
window.loadFileAsync(UTF8ToString(fullPathC), wakeUp); window.loadFileAsync(UTF8ToString(fullPathC), bitmap, wakeUp);
}); });
}); });

View File

@ -5,7 +5,7 @@
#include <emscripten.h> #include <emscripten.h>
extern "C" { extern "C" {
void load_file_async_js(const char* fullPathC); void load_file_async_js(const char* fullPathC, int bitmap=0);
void save_file_async_js(const char* fullPathC); void save_file_async_js(const char* fullPathC);
} }

View File

@ -1147,6 +1147,14 @@ DEF_ATTR_RD_SIMPLE(Tilemap, Visible, bool, p->visible)
DEF_ATTR_RD_SIMPLE(Tilemap, OX, int, p->origin.x) DEF_ATTR_RD_SIMPLE(Tilemap, OX, int, p->origin.x)
DEF_ATTR_RD_SIMPLE(Tilemap, OY, int, p->origin.y) DEF_ATTR_RD_SIMPLE(Tilemap, OY, int, p->origin.y)
void invalidateBitmap(void * tilemap) {
((Tilemap *) tilemap)->invalidate();
}
void Tilemap::invalidate() {
p->invalidateAtlasContents();
}
void Tilemap::setTileset(Bitmap *value) void Tilemap::setTileset(Bitmap *value)
{ {
guardDisposed(); guardDisposed();
@ -1159,6 +1167,9 @@ void Tilemap::setTileset(Bitmap *value)
if (!value) if (!value)
return; return;
value->reloadCallback = invalidateBitmap;
value->reloadCallbackData = (void *) this;
p->invalidateAtlasSize(); p->invalidateAtlasSize();
p->tilesetCon.disconnect(); p->tilesetCon.disconnect();
p->tilesetCon = value->modified.connect p->tilesetCon = value->modified.connect

View File

@ -54,6 +54,7 @@ public:
~Tilemap(); ~Tilemap();
void update(); void update();
void invalidate();
Autotiles &getAutotiles(); Autotiles &getAutotiles();
Viewport *getViewport() const; Viewport *getViewport() const;