From 175619cfb1ce6a1fa1e3294771059cef2b6638ab Mon Sep 17 00:00:00 2001 From: Natan Date: Mon, 8 Jul 2024 14:55:12 -0300 Subject: [PATCH] Clean up asset downloader, preloader and some improvements --- CHANGES.md | 2 + .../gdx/assets/AssetLoadingTaskEmu.java | 173 ++++++++++++ .../gdx/assets/loaders/AssetLoaderEmu.java | 22 -- .../loaders/AsynchronousAssetLoaderEmu.java | 21 -- .../com/badlogic/gdx/graphics/PixmapEmu.java | 5 +- .../gdx/utils/SharedLibraryLoaderEmu.java | 3 +- .../backends/teavm/AssetLoaderListener.java | 3 +- .../gdx/backends/teavm/TeaApplication.java | 10 +- .../teavm/TeaApplicationConfiguration.java | 6 +- .../xpenatan/gdx/backends/teavm/TeaFiles.java | 6 +- .../gdx/backends/teavm/TeaPreferences.java | 32 ++- .../gdx/backends/teavm/config/AssetsCopy.java | 19 +- .../filesystem/types/LocalDBStorage.java | 2 +- .../teavm/preloader/AssetDownloadImpl.java | 267 ++++-------------- .../teavm/preloader/AssetDownloader.java | 19 +- .../backends/teavm/preloader/AssetType.java | 6 +- .../backends/teavm/preloader/Preloader.java | 83 ++++-- .../teavm/launcher/TeaVMTestLauncher.java | 2 +- .../example/tests/imgui/ImGuiTestsApp.java | 26 +- .../graphics/g2d/freetype/FreeTypeEmu.java | 3 +- 20 files changed, 360 insertions(+), 350 deletions(-) create mode 100644 backends/backend-teavm/emu/com/badlogic/gdx/assets/AssetLoadingTaskEmu.java delete mode 100644 backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AssetLoaderEmu.java delete mode 100644 backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AsynchronousAssetLoaderEmu.java diff --git a/CHANGES.md b/CHANGES.md index 87859d5e..b64f46f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ - Call dispose when browser closes - Improve clipboard text copy/paste. - Change default sound/music api to howler.js +- add shouldEncodePreference config +- add localStoragePrefix config [1.0.0-b9] - add TeaClassFilter printAllowedClasses() and printExcludedClasses() diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/assets/AssetLoadingTaskEmu.java b/backends/backend-teavm/emu/com/badlogic/gdx/assets/AssetLoadingTaskEmu.java new file mode 100644 index 00000000..7087c549 --- /dev/null +++ b/backends/backend-teavm/emu/com/badlogic/gdx/assets/AssetLoadingTaskEmu.java @@ -0,0 +1,173 @@ +package com.badlogic.gdx.assets; + +import com.badlogic.gdx.Files; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.assets.loaders.AssetLoader; +import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader; +import com.badlogic.gdx.assets.loaders.SynchronousAssetLoader; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.Logger; +import com.badlogic.gdx.utils.TimeUtils; +import com.badlogic.gdx.utils.async.AsyncExecutor; +import com.badlogic.gdx.utils.async.AsyncResult; +import com.badlogic.gdx.utils.async.AsyncTask; +import com.github.xpenatan.gdx.backends.teavm.TeaApplication; +import com.github.xpenatan.gdx.backends.teavm.gen.Emulate; +import com.github.xpenatan.gdx.backends.teavm.preloader.AssetType; +import com.github.xpenatan.gdx.backends.teavm.preloader.Preloader; + +@Emulate(AssetLoadingTask.class) +class AssetLoadingTaskEmu implements AsyncTask { + AssetManager manager; + final AssetDescriptor assetDesc; + final AssetLoader loader; + final AsyncExecutor executor; + final long startTime; + + volatile boolean asyncDone; + volatile boolean dependenciesLoaded; + volatile Array dependencies; + volatile AsyncResult depsFuture; + volatile AsyncResult loadFuture; + volatile Object asset; + + int ticks = 0; + volatile boolean cancel; + + public AssetLoadingTaskEmu(AssetManager manager, AssetDescriptor assetDesc, AssetLoader loader, AsyncExecutor threadPool) { + this.manager = manager; + this.assetDesc = assetDesc; + this.loader = loader; + this.executor = threadPool; + startTime = manager.log.getLevel() == Logger.DEBUG ? TimeUtils.nanoTime() : 0; + } + + /** + * Loads parts of the asset asynchronously if the loader is an {@link AsynchronousAssetLoader}. + */ + @Override + public Void call() throws Exception { + if(cancel) return null; + AsynchronousAssetLoader asyncLoader = (AsynchronousAssetLoader)loader; + if(!dependenciesLoaded) { + dependencies = asyncLoader.getDependencies(assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + if(dependencies != null) { + removeDuplicates(dependencies); + manager.injectDependencies(assetDesc.fileName, dependencies); + } + else { + // if we have no dependencies, we load the async part of the task immediately. + asyncLoader.loadAsync(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + asyncDone = true; + } + } + else { + asyncLoader.loadAsync(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + asyncDone = true; + } + return null; + } + + /** + * Updates the loading of the asset. In case the asset is loaded with an {@link AsynchronousAssetLoader}, the loaders + * {@link AsynchronousAssetLoader#loadAsync(AssetManager, String, FileHandle, AssetLoaderParameters)} method is first called on + * a worker thread. Once this method returns, the rest of the asset is loaded on the rendering thread via + * {@link AsynchronousAssetLoader#loadSync(AssetManager, String, FileHandle, AssetLoaderParameters)}. + * + * @return true in case the asset was fully loaded, false otherwise + * @throws GdxRuntimeException + */ + public boolean update() { + ticks++; + + // GTW: check if we have a file that was not preloaded and is not done loading yet + Preloader preloader = ((TeaApplication)Gdx.app).getPreloader(); + if(!preloader.isAssetLoaded(Files.FileType.Internal, assetDesc.fileName)) { + preloader.loadAsset(AssetType.Binary, Files.FileType.Internal, assetDesc.fileName); + boolean assetInQueue = preloader.isAssetInQueue(assetDesc.fileName); + // Loader.finishLoading breaks everything + if(!assetInQueue && ticks > 100000) + throw new GdxRuntimeException("File not prefetched, but finishLoading was probably called: " + assetDesc.fileName); + } + else { + if(loader instanceof SynchronousAssetLoader) + handleSyncLoader(); + else + handleAsyncLoader(); + } + return asset != null; + } + + private void handleSyncLoader() { + SynchronousAssetLoader syncLoader = (SynchronousAssetLoader)loader; + if(!dependenciesLoaded) { + dependenciesLoaded = true; + dependencies = syncLoader.getDependencies(assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + if(dependencies == null) { + asset = syncLoader.load(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + return; + } + removeDuplicates(dependencies); + manager.injectDependencies(assetDesc.fileName, dependencies); + } + else + asset = syncLoader.load(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + } + + private void handleAsyncLoader() { + AsynchronousAssetLoader asyncLoader = (AsynchronousAssetLoader)loader; + if(!dependenciesLoaded) { + if(depsFuture == null) + depsFuture = executor.submit(this); + else if(depsFuture.isDone()) { + try { + depsFuture.get(); + } catch(Exception e) { + throw new GdxRuntimeException("Couldn't load dependencies of asset: " + assetDesc.fileName, e); + } + dependenciesLoaded = true; + if(asyncDone) + asset = asyncLoader.loadSync(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + } + } + else if(loadFuture == null && !asyncDone) + loadFuture = executor.submit(this); + else if(asyncDone) + asset = asyncLoader.loadSync(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + else if(loadFuture.isDone()) { + try { + loadFuture.get(); + } catch(Exception e) { + throw new GdxRuntimeException("Couldn't load asset: " + assetDesc.fileName, e); + } + asset = asyncLoader.loadSync(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + } + } + + /** + * Called when this task is the task that is currently being processed and it is unloaded. + */ + public void unload() { + if(loader instanceof AsynchronousAssetLoader) + ((AsynchronousAssetLoader)loader).unloadAsync(manager, assetDesc.fileName, resolve(loader, assetDesc), assetDesc.params); + } + + private FileHandle resolve(AssetLoader loader, AssetDescriptor assetDesc) { + if(assetDesc.file == null) assetDesc.file = loader.resolve(assetDesc.fileName); + return assetDesc.file; + } + + private void removeDuplicates(Array array) { + boolean ordered = array.ordered; + array.ordered = true; + for(int i = 0; i < array.size; ++i) { + final String fn = array.get(i).fileName; + final Class type = array.get(i).type; + for(int j = array.size - 1; j > i; --j) + if(type == array.get(j).type && fn.equals(array.get(j).fileName)) array.removeIndex(j); + } + array.ordered = ordered; + } +} diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AssetLoaderEmu.java b/backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AssetLoaderEmu.java deleted file mode 100644 index 04d4eed1..00000000 --- a/backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AssetLoaderEmu.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.badlogic.gdx.assets.loaders; - -import com.badlogic.gdx.assets.AssetDescriptor; -import com.badlogic.gdx.assets.AssetLoaderParameters; -import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.utils.Array; -import com.github.xpenatan.gdx.backends.teavm.gen.Emulate; - -@Emulate(AssetLoader.class) -public abstract class AssetLoaderEmu> { - private FileHandleResolver resolver; - - public AssetLoaderEmu (FileHandleResolver resolver) { - this.resolver = resolver; - } - - public FileHandle resolve (String fileName) { - return resolver.resolve(fileName); - } - - public abstract Array getDependencies (String fileName, FileHandle file, P parameter); -} \ No newline at end of file diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AsynchronousAssetLoaderEmu.java b/backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AsynchronousAssetLoaderEmu.java deleted file mode 100644 index 2339e1e8..00000000 --- a/backends/backend-teavm/emu/com/badlogic/gdx/assets/loaders/AsynchronousAssetLoaderEmu.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.badlogic.gdx.assets.loaders; - -import com.badlogic.gdx.assets.AssetLoaderParameters; -import com.badlogic.gdx.assets.AssetManager; -import com.badlogic.gdx.files.FileHandle; -import com.github.xpenatan.gdx.backends.teavm.gen.Emulate; - -@Emulate(AsynchronousAssetLoader.class) -public abstract class AsynchronousAssetLoaderEmu> extends AssetLoader { - - public AsynchronousAssetLoaderEmu (FileHandleResolver resolver) { - super(resolver); - } - - public abstract void loadAsync (AssetManager manager, String fileName, FileHandle file, P parameter); - - public void unloadAsync (AssetManager manager, String fileName, FileHandle file, P parameter) { - } - - public abstract T loadSync (AssetManager manager, String fileName, FileHandle file, P parameter); -} \ No newline at end of file diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java b/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java index a076c726..b224075a 100644 --- a/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java +++ b/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java @@ -81,15 +81,14 @@ public void onFailure(String url) { } @Override - public boolean onSuccess(String url, Blob result) { + public void onSuccess(String url, Blob result) { Int8ArrayWrapper data = result.getData(); byte[] byteArray = TypedArrays.toByteArray(data); Pixmap pixmapEmu = new Pixmap(byteArray, 0, byteArray.length); responseListener.downloadComplete(pixmapEmu); - return false; } }; - AssetDownloader.getInstance().load(true, url, AssetType.Binary, null, listener); + AssetDownloader.getInstance().load(true, url, AssetType.Binary, listener); } public PixmapEmu(FileHandle file) { diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/utils/SharedLibraryLoaderEmu.java b/backends/backend-teavm/emu/com/badlogic/gdx/utils/SharedLibraryLoaderEmu.java index 90d767e1..af974f76 100644 --- a/backends/backend-teavm/emu/com/badlogic/gdx/utils/SharedLibraryLoaderEmu.java +++ b/backends/backend-teavm/emu/com/badlogic/gdx/utils/SharedLibraryLoaderEmu.java @@ -14,8 +14,7 @@ public void load (String libraryName) { Preloader preloader = app.getPreloader(); preloader.loadScript(false, libraryName + ".js", new AssetLoaderListener() { @Override - public boolean onSuccess(String url, Object result) { - return true; + public void onSuccess(String url, Object result) { } @Override diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/AssetLoaderListener.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/AssetLoaderListener.java index 348e4985..0cfda2d5 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/AssetLoaderListener.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/AssetLoaderListener.java @@ -10,7 +10,6 @@ public void onProgress(double amount) { public void onFailure(String url) { } - public boolean onSuccess(String url, T result) { - return false; + public void onSuccess(String url, T result) { } } \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java index b831445b..6006135c 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java @@ -423,7 +423,7 @@ public Preferences getPreferences(String name) { Preferences pref = prefs.get(name); if(pref == null) { Storage storage = Storage.getLocalStorage();; - pref = new TeaPreferences(storage, config.storagePrefix + name); + pref = new TeaPreferences(storage, config.storagePrefix + ":" + name, config.shouldEncodePreference); prefs.put(name, pref); } return pref; @@ -481,8 +481,7 @@ public enum AppState { private void initGdx() { preloader.loadScript(true, "gdx.wasm.js", new AssetLoaderListener() { @Override - public boolean onSuccess(String url, Object result) { - return true; + public void onSuccess(String url, Object result) { } }); } @@ -490,9 +489,8 @@ public boolean onSuccess(String url, Object result) { private void initSound() { preloader.loadScript(true, "howler.js", new AssetLoaderListener() { @Override - public boolean onSuccess(String url, Object result) { - return true; + public void onSuccess(String url, Object result) { } }); } -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java index d4cb5119..bced0dfa 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java @@ -29,7 +29,11 @@ public class TeaApplicationConfiguration { * browser is not shared between the applications. If you leave the storage prefix at "", all the data * and files stored will be shared between the applications. */ - public String storagePrefix = "db/assets"; + public String storagePrefix = "app"; + + public String localStoragePrefix = "db/assets"; + + public boolean shouldEncodePreference = false; /** * Show download logs. diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java index 1563dca2..1dabdf20 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java @@ -17,13 +17,13 @@ public class TeaFiles implements Files { public MemoryFileStorage internalStorage; public MemoryFileStorage classpathStorage; public MemoryFileStorage localStorage; - public String storagePath; + public String localStoragePrefix; public TeaFiles(TeaApplicationConfiguration config, TeaApplication teaApplication) { this.internalStorage = new InternalStorage(); this.classpathStorage = new ClasspathStorage(); this.localStorage = new LocalDBStorage(teaApplication); - storagePath = config.storagePrefix; + localStoragePrefix = config.localStoragePrefix; } public FileDB getFileDB(FileType type) { @@ -91,7 +91,7 @@ public boolean isExternalStorageAvailable() { @Override public String getLocalStoragePath() { - return storagePath; + return localStoragePrefix; } @Override diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaPreferences.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaPreferences.java index 61d65fac..6f926ff3 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaPreferences.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaPreferences.java @@ -23,19 +23,31 @@ public class TeaPreferences implements Preferences { private Storage storage; - public TeaPreferences(Storage storage, String prefix) { + private boolean shouldEncode; + + public TeaPreferences(Storage storage, String prefix, boolean shouldEncode) { this.storage = storage; this.prefix = ID_FOR_PREF + prefix + ":"; + this.shouldEncode = shouldEncode; int prefixLength = this.prefix.length(); try { for(int i = 0; i < storage.getLength(); i++) { String keyEncoded = storage.key(i); - String key = new String(HEXCoder.decode(keyEncoded)); + String key = keyEncoded; + if(shouldEncode) { + key = new String(HEXCoder.decode(keyEncoded)); + } boolean flag = key.startsWith(this.prefix); if(flag) { String value = storage.getItem(keyEncoded); String keyStr = key.substring(prefixLength, key.length() - 1); - Object object = toObject(key, new String(HEXCoder.decode(value))); + Object object; + if(shouldEncode) { + object = toObject(key, new String(HEXCoder.decode(value))); + } + else { + object = toObject(key, value); + } values.put(keyStr, object); } } @@ -68,9 +80,12 @@ public void flush() { // remove all old values for(int i = 0; i < storage.getLength(); i++) { String keyEncoded = storage.key(i); - String key = new String(HEXCoder.decode(keyEncoded)); + String key = keyEncoded; + if(shouldEncode) { + key = new String(HEXCoder.decode(keyEncoded)); + } if(key.startsWith(prefix)) { - storage.removeItem(key); + storage.removeItem(keyEncoded); } } @@ -78,7 +93,12 @@ public void flush() { for(String key : values.keys()) { String storageKey = toStorageKey(key, values.get(key)); String storageValue = "" + values.get(key).toString(); - storage.setItem(HEXCoder.encode(storageKey.getBytes()), HEXCoder.encode(storageValue.getBytes())); + if(shouldEncode) { + storage.setItem(HEXCoder.encode(storageKey.getBytes()), HEXCoder.encode(storageValue.getBytes())); + } + else { + storage.setItem(storageKey, storageValue); + } } } catch(Exception e) { diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java index 90bd94e0..07630d29 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java @@ -301,9 +301,6 @@ private static void copyDirectory(FileHandle sourceDir, FileHandle destDir, Asse @Deprecated public static AssetType getType(String file) { String extension = extension(file).toLowerCase(); - if(isImage(extension)) return AssetType.Image; - if(isAudio(extension)) return AssetType.Audio; - if(isText(extension)) return AssetType.Text; return AssetType.Binary; } @@ -313,18 +310,4 @@ private static String extension(String file) { if(dotIndex == -1) return ""; return name.substring(dotIndex + 1); } - - private static boolean isImage(String extension) { - return extension.equals("jpg") || extension.equals("jpeg") || extension.equals("png") || extension.equals("bmp") || extension.equals("gif"); - } - - private static boolean isText(String extension) { - return extension.equals("json") || extension.equals("xml") || extension.equals("txt") || extension.equals("glsl") - || extension.equals("fnt") || extension.equals("pack") || extension.equals("obj") || extension.equals("atlas") - || extension.equals("g3dj"); - } - - private static boolean isAudio(String extension) { - return extension.equals("mp3") || extension.equals("ogg") || extension.equals("wav"); - } -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java index a25c0d01..7aae416d 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java @@ -38,7 +38,7 @@ private void setupIndexedDB(TeaApplication teaApplication) { teaApplication.delayInitCount++; IDBFactory instance = IDBFactory.getInstance(); - String databaseName = config.storagePrefix; + String databaseName = config.localStoragePrefix; IDBOpenDBRequest request = instance.open(databaseName, 1); request.setOnUpgradeNeeded(evt -> { diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloadImpl.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloadImpl.java index c8a0571f..075df90f 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloadImpl.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloadImpl.java @@ -2,16 +2,7 @@ import com.badlogic.gdx.utils.GdxRuntimeException; import com.github.xpenatan.gdx.backends.teavm.AssetLoaderListener; -import com.github.xpenatan.gdx.backends.teavm.dom.DocumentWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.EventHandlerWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.EventListenerWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.EventWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.HTMLElementWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.HTMLImageElementWrapper; import com.github.xpenatan.gdx.backends.teavm.dom.LocationWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.NodeWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.ProgressEventWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.XMLHttpRequestWrapper; import com.github.xpenatan.gdx.backends.teavm.dom.impl.TeaWindow; import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.ArrayBufferWrapper; import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int8ArrayWrapper; @@ -19,24 +10,19 @@ import com.github.xpenatan.gdx.backends.teavm.preloader.AssetDownloader.AssetDownload; import org.teavm.jso.ajax.XMLHttpRequest; import org.teavm.jso.browser.Window; +import org.teavm.jso.dom.html.HTMLDocument; +import org.teavm.jso.dom.html.HTMLScriptElement; public class AssetDownloadImpl implements AssetDownload { private int queue; - private boolean useBrowserCache = false; - private boolean useInlineBase64 = false; - - private boolean showLogs = true; + private final boolean showLogs; + private boolean showDownloadProgress; public AssetDownloadImpl(boolean showDownloadLogs) { showLogs = showDownloadLogs; } - @Override - public boolean isUseBrowserCache() { - return useBrowserCache; - } - @Override public String getHostPageBaseURL() { TeaWindow currentWindow = TeaWindow.get(); @@ -61,20 +47,11 @@ public void addQueue() { } @Override - public void load(boolean async, String url, AssetType type, String mimeType, AssetLoaderListener listener) { + public void load(boolean async, String url, AssetType type, AssetLoaderListener listener) { switch(type) { - case Text: - loadText(async, url, (AssetLoaderListener)listener); - break; - case Image: - loadImage(async, url, mimeType, (AssetLoaderListener)listener); - break; case Binary: loadBinary(async, url, (AssetLoaderListener)listener); break; - case Audio: - loadAudio(async, url, (AssetLoaderListener)listener); - break; case Directory: listener.onSuccess(url, null); break; @@ -83,118 +60,29 @@ public void load(boolean async, String url, AssetType type, String mimeType, Ass } } - @Override - public void loadText(boolean async, String url, AssetLoaderListener listener) { - if(showLogs) - System.out.println("Loading asset : " + url); - - // don't load on main thread - addQueue(); - new Thread() { - public void run() { - final XMLHttpRequestWrapper request = (XMLHttpRequestWrapper)XMLHttpRequest.create(); - request.setOnreadystatechange(new EventHandlerWrapper() { - @Override - public void handleEvent(EventWrapper evt) { - if(request.getReadyState() == XMLHttpRequestWrapper.DONE) { - if(request.getStatus() != 200) { - if ((request.getStatus() != 404) && - (request.getStatus() != 403)) { - // re-try: e.g. failure due to ERR_HTTP2_SERVER_REFUSED_STREAM (too many requests) - try { - Thread.sleep(100); - } - catch (Throwable e) { - // ignored - } - loadText(async, url, listener); - } - else { - listener.onFailure(url); - } - } - else { - if(showLogs) - System.out.println("Asset loaded: " + url); - listener.onSuccess(url, request.getResponseText()); - } - subtractQueue(); - } - } - }); - setOnProgress(request, listener); - request.open("GET", url, async); - request.setRequestHeader("Content-Type", "text/plain; charset=utf-8"); - request.send(); - } - }.start(); - } - @Override public void loadScript(boolean async, String url, AssetLoaderListener listener) { if(showLogs) System.out.println("Loading script : " + url); - // don't load on main thread - addQueue(); - final XMLHttpRequestWrapper request = (XMLHttpRequestWrapper)XMLHttpRequest.create(); - request.setOnreadystatechange(new EventHandlerWrapper() { + loadBinary(async, url, new AssetLoaderListener<>() { @Override - public void handleEvent(EventWrapper evt) { - if(request.getReadyState() == XMLHttpRequestWrapper.DONE) { - if(request.getStatus() != 200) { - if ((request.getStatus() != 404) && - (request.getStatus() != 403)) { - // re-try: e.g. failure due to ERR_HTTP2_SERVER_REFUSED_STREAM (too many requests) - try { - Thread.sleep(100); - } - catch (Throwable e) { - // ignored - } - loadScript(async, url, listener); - } - else { - listener.onFailure(url); - } - } - else { - if(showLogs) - System.out.println("Script loaded: " + url); - NodeWrapper response = request.getResponse(); - TeaWindow currentWindow = TeaWindow.get(); - DocumentWrapper document = currentWindow.getDocument(); - HTMLElementWrapper scriptElement = document.createElement("script"); - scriptElement.appendChild(document.createTextNode(response)); - document.getBody().appendChild(scriptElement); - listener.onSuccess(url, request.getResponseText()); - } - subtractQueue(); - } - } - }); - setOnProgress(request, listener); - request.open("GET", url, async); - request.setRequestHeader("Content-Type", "text/plain; charset=utf-8"); - request.send(); - } - - public void loadAudio(boolean async, final String url, final AssetLoaderListener listener) { - loadBinary(async, url, new AssetLoaderListener() { - @Override - public void onProgress(double amount) { - listener.onProgress(amount); + public void onSuccess(String url, Blob result) { + Int8ArrayWrapper data = result.getData(); + byte[] byteArray = TypedArrays.toByteArray(data); + String script = new String(byteArray); + Window current = Window.current(); + HTMLDocument document = current.getDocument(); + HTMLScriptElement scriptElement = (HTMLScriptElement)document.createElement("script"); + scriptElement.setText(script); + document.getBody().appendChild(scriptElement); + listener.onSuccess(url, script); } @Override public void onFailure(String url) { listener.onFailure(url); } - - @Override - public boolean onSuccess(String url, Blob result) { - return listener.onSuccess(url, null); - } }); } @@ -206,41 +94,41 @@ public void loadBinary(boolean async, final String url, final AssetLoaderListene addQueue(); new Thread() { public void run() { - XMLHttpRequestWrapper request = (XMLHttpRequestWrapper)XMLHttpRequest.create(); - request.setOnreadystatechange(new EventHandlerWrapper() { - @Override - public void handleEvent(EventWrapper evt) { - if(request.getReadyState() == XMLHttpRequestWrapper.DONE) { - if(request.getStatus() != 200) { - if ((request.getStatus() != 404) && - (request.getStatus() != 403)) { - // re-try: e.g. failure due to ERR_HTTP2_SERVER_REFUSED_STREAM (too many requests) - try { - Thread.sleep(100); - } - catch (Throwable e) { - // ignored - } - loadBinary(async, url, listener); + XMLHttpRequest request = new XMLHttpRequest(); + request.setOnReadyStateChange(evt -> { + if(request.getReadyState() == XMLHttpRequest.DONE) { + int status = request.getStatus(); + if(status == 0) { + listener.onFailure(url); + } + else if(status != 200) { + if ((status != 404) && (status != 403)) { + // re-try: e.g. failure due to ERR_HTTP2_SERVER_REFUSED_STREAM (too many requests) + try { + Thread.sleep(100); } - else { - listener.onFailure(url); + catch (Throwable e) { + // ignored } + loadBinary(async, url, listener); } else { - if(showLogs) - System.out.println("Asset loaded: " + url); - - ArrayBufferWrapper response = (ArrayBufferWrapper)request.getResponse(); - Int8ArrayWrapper data = TypedArrays.createInt8Array(response); - listener.onSuccess(url, new Blob(response, data)); + listener.onFailure(url); } - subtractQueue(); } + else { + if(showLogs) + System.out.println("Asset loaded: " + url); + + ArrayBufferWrapper response = (ArrayBufferWrapper)request.getResponse(); + Int8ArrayWrapper data = TypedArrays.createInt8Array(response); + listener.onSuccess(url, new Blob(response, data)); + } + subtractQueue(); } }); - setOnProgress(request, listener); + setOnProgress(request, url, listener); request.open("GET", url, async); if(async) { request.setResponseType("arraybuffer"); @@ -250,68 +138,15 @@ public void handleEvent(EventWrapper evt) { }.start(); } - public void loadImage(boolean async, final String url, final String mimeType, - final AssetLoaderListener listener) { - loadImage(async, url, mimeType, null, listener); - } - - public void loadImage(boolean async, final String url, final String mimeType, final String crossOrigin, - final AssetLoaderListener listener) { - loadBinary(async, url, new AssetLoaderListener() { - @Override - public void onProgress(double amount) { - listener.onProgress(amount); - } - - @Override - public void onFailure(String url) { - listener.onFailure(url); - } - - @Override - public boolean onSuccess(String url, Blob result) { - DocumentWrapper document = (DocumentWrapper)Window.current().getDocument(); - final HTMLImageElementWrapper image = (HTMLImageElementWrapper)document.createElement("img"); - - if(crossOrigin != null) { - image.setAttribute("crossOrigin", crossOrigin); - } - addQueue(); - hookImgListener(image, new EventListenerWrapper() { - @Override - public void handleEvent(EventWrapper evt) { - if(evt.getType().equals("error")) - listener.onFailure(url); - else { - result.setImage(image); - listener.onSuccess(url, result); - } - subtractQueue(); - } - }); - if(useInlineBase64) { - image.setSrc("data:" + mimeType + ";base64," + result.toBase64()); - } - else { - image.setSrc(url); - } - return false; - } - }); - } - - static void hookImgListener(HTMLImageElementWrapper img, final EventListenerWrapper listener) { - img.addEventListener("load", listener, false); - img.addEventListener("error", listener, false); - } - - private void setOnProgress(XMLHttpRequestWrapper req, final AssetLoaderListener listener) { - req.setOnprogress(new EventHandlerWrapper() { - @Override - public void handleEvent(EventWrapper evt) { - ProgressEventWrapper progressEvent = (ProgressEventWrapper)evt; - listener.onProgress(progressEvent.getLoaded()); + private void setOnProgress(XMLHttpRequest req, String url, final AssetLoaderListener listener) { + req.onProgress(evt -> { + int loaded = evt.getLoaded(); + int total = evt.getTotal(); + double percent = (double)loaded / total; + if(showDownloadProgress) { + System.out.println("Total: " + total + " loaded: " + loaded + " URL: " + url); } + listener.onProgress(loaded); }); } -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloader.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloader.java index e1999fd1..fb1f4ad9 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloader.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetDownloader.java @@ -19,21 +19,18 @@ public static void setInstance(AssetDownload instance) { AssetDownloader.instance = instance; } - static public interface AssetDownload { - public void load(boolean async, final String url, AssetType type, String mimeType, AssetLoaderListener listener); + public interface AssetDownload { - public void loadText(boolean async, final String url, final AssetLoaderListener listener); + void load(boolean async, final String url, AssetType type, AssetLoaderListener listener); - public void loadScript(boolean async, final String url, final AssetLoaderListener listener); + void loadScript(boolean async, final String url, final AssetLoaderListener listener); - public boolean isUseBrowserCache(); + String getHostPageBaseURL(); - public String getHostPageBaseURL(); + int getQueue(); - public int getQueue(); + void subtractQueue(); - public void subtractQueue(); - - public void addQueue(); + void addQueue(); } -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetType.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetType.java index 62d86aa5..e29912cf 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetType.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/AssetType.java @@ -4,11 +4,11 @@ * @author xpenatan */ public enum AssetType { - Image("i"), Audio("a"), Text("t"), Binary("b"), Directory("d"); + Binary("b"), Directory("d"); public final String code; - private AssetType(String code) { + AssetType(String code) { this.code = code; } -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java index 630cddbc..f301fe26 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java @@ -2,6 +2,7 @@ import com.badlogic.gdx.Files.FileType; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.StreamUtils; @@ -24,6 +25,7 @@ import com.github.xpenatan.gdx.backends.teavm.filesystem.FileData; import java.io.IOException; import java.io.OutputStream; +import java.util.HashSet; import org.teavm.jso.core.JSArray; import org.teavm.jso.core.JSArrayReader; import org.teavm.jso.core.JSPromise; @@ -39,9 +41,11 @@ public class Preloader { public final String baseUrl; + private HashSet assetInQueue; + public Preloader(String newBaseURL, HTMLCanvasElementWrapper canvas, TeaApplication teaApplication) { baseUrl = newBaseURL; - + assetInQueue = new HashSet<>(); setupFileDrop(canvas, teaApplication); } @@ -137,19 +141,13 @@ private String getScriptUrl() { } public void preload(TeaApplicationConfiguration config, final String assetFileUrl) { - AssetDownloader.getInstance().loadText(true, getAssetUrl() + assetFileUrl, new AssetLoaderListener() { - @Override - public void onProgress(double amount) { - } - - @Override - public void onFailure(String url) { - System.out.println("ErrorLoading: " + assetFileUrl); - } - + AssetLoaderListener listener = new AssetLoaderListener<>() { @Override - public boolean onSuccess(String url, String result) { - String[] lines = result.split("\n"); + public void onSuccess(String url, Blob result) { + Int8ArrayWrapper data = result.getData(); + byte[] byteArray = TypedArrays.toByteArray(data); + String assets = new String(byteArray); + String[] lines = assets.split("\n"); assetTotal = lines.length; @@ -179,34 +177,62 @@ else if(fileTypeStr.equals("l")) { loadAsset(assetType, fileType, assetUrl); } - return false; } - }); + + @Override + public void onFailure(String url) { + System.out.println("ErrorLoading: " + assetFileUrl); + } + }; + + AssetDownloader.getInstance().load(true, getAssetUrl() + assetFileUrl, AssetType.Binary, listener); } - public void loadAsset(AssetType assetType, FileType fileType, String path1) { - path1 = path1.trim().replace("\\", "/"); - if(path1.startsWith("/")) { - path1 = path1.substring(1); - } + public boolean isAssetInQueue(String path) { + String path1 = fixPath(path); + return assetInQueue.contains(path1); + } + + public boolean isAssetLoaded(FileType fileType, String path) { + String path1 = fixPath(path); + FileHandle fileHandle = Gdx.files.getFileHandle(path1, fileType); + return fileHandle.exists(); + } + + public void loadAsset(AssetType assetType, FileType fileType, String path) { + String path1 = fixPath(path); + if(path1.isEmpty()) { return; } - String path = path1; - AssetDownloader.getInstance().load(true, getAssetUrl() + path, AssetType.Binary, null, new AssetLoaderListener<>() { + + if(assetInQueue.contains(path1)) { + return; + } + + FileHandle fileHandle = Gdx.files.getFileHandle(path1, fileType); + if(fileHandle.exists()) { + // File already exist, don't download it again. + return; + } + + assetInQueue.add(path1); + AssetDownloader.getInstance().load(true, getAssetUrl() + path1, AssetType.Binary, new AssetLoaderListener<>() { @Override public void onProgress(double amount) { } @Override public void onFailure(String url) { + assetInQueue.remove(path1); } @Override - public boolean onSuccess(String url, Object result) { + public void onSuccess(String url, Object result) { + assetInQueue.remove(path1); Blob blob = (Blob)result; AssetType type = AssetType.Binary; - TeaFileHandle internalFile = (TeaFileHandle)Gdx.files.getFileHandle(path, fileType); + TeaFileHandle internalFile = (TeaFileHandle)Gdx.files.getFileHandle(path1, fileType); if(assetType == AssetType.Directory) { internalFile.mkdirsInternal(); } @@ -224,7 +250,6 @@ public boolean onSuccess(String url, Object result) { StreamUtils.closeQuietly(output); } } - return false; } }); } @@ -236,4 +261,12 @@ public void loadScript(boolean async, final String url, final AssetLoaderListene public int getQueue() { return AssetDownloader.getInstance().getQueue(); } + + private String fixPath(String path1) { + path1 = path1.trim().replace("\\", "/"); + if(path1.startsWith("/")) { + path1 = path1.substring(1); + } + return path1; + } } \ No newline at end of file diff --git a/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/launcher/TeaVMTestLauncher.java b/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/launcher/TeaVMTestLauncher.java index 17c4809d..c8545d76 100644 --- a/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/launcher/TeaVMTestLauncher.java +++ b/examples/core/teavm/src/main/java/com/github/xpenatan/gdx/examples/teavm/launcher/TeaVMTestLauncher.java @@ -20,7 +20,7 @@ public static void main(String[] args) { @Override public void create() { TeaFiles files = (TeaFiles)Gdx.files; - files.localStorage.debug = true; + files.localStorage.debug = false; super.create(); } diff --git a/examples/gdx-tests/core/src/main/java/com/github/xpenatan/imgui/example/tests/imgui/ImGuiTestsApp.java b/examples/gdx-tests/core/src/main/java/com/github/xpenatan/imgui/example/tests/imgui/ImGuiTestsApp.java index 946dadc2..1b37ea16 100644 --- a/examples/gdx-tests/core/src/main/java/com/github/xpenatan/imgui/example/tests/imgui/ImGuiTestsApp.java +++ b/examples/gdx-tests/core/src/main/java/com/github/xpenatan/imgui/example/tests/imgui/ImGuiTestsApp.java @@ -2,6 +2,7 @@ import com.badlogic.gdx.Application; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Preferences; import com.badlogic.gdx.Screen; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; @@ -16,8 +17,10 @@ import com.github.xpenatan.imgui.example.tests.frame.GameFrame; import imgui.ImDrawData; import imgui.ImGui; +import imgui.ImGuiCond; import imgui.ImGuiConfigFlags; import imgui.ImGuiIO; +import imgui.ImGuiInternal; import imgui.ImVec2; import imgui.gdx.ImGuiGdxImpl; import imgui.gdx.ImGuiGdxInputMultiplexer; @@ -32,9 +35,8 @@ public class ImGuiTestsApp implements Screen { private ImGuiGdxImpl impl; private ImGuiGdxInputMultiplexer input; - private boolean gdxTestInit = false; - private int selected = -1; + private boolean scrollTo = false; private TeaVMGdxTests.TeaVMInstancer[] testList; @@ -74,17 +76,23 @@ public boolean touchDown (int screenX, int screenY, int pointer, int button) { } }; ((TeaVMInputWrapper)Gdx.input).multiplexer.addProcessor(input); + + Preferences gdxTests = Gdx.app.getPreferences("gdxTests"); + selected = gdxTests.getInteger("selected", selected); + if(selected != -1) { + scrollTo = true; + } } private void drawTestListWindow() { - if(!gdxTestInit) { - gdxTestInit = true; - ImGui.SetNextWindowSize(ImVec2.TMP_1.set(250, 500)); - ImGui.SetNextWindowPos(ImVec2.TMP_1.set(20, 20)); - } + ImGui.SetNextWindowSize(ImVec2.TMP_1.set(250, 500), ImGuiCond.ImGuiCond_FirstUseEver); + ImGui.SetNextWindowPos(ImVec2.TMP_1.set(20, 20), ImGuiCond.ImGuiCond_FirstUseEver); ImGui.Begin("GdxTests"); if(ImGui.Button("Start Test")) { if(selected >= 0 && selected < testList.length) { + Preferences gdxTests = Gdx.app.getPreferences("gdxTests"); + gdxTests.putInteger("selected", selected); + gdxTests.flush(); ((TeaVMInputWrapper)Gdx.input).multiplexer.removeProcessor(input); test = testList[selected].instance(); Gdx.app.log("GdxTest", "Clicked on " + test.getClass().getName()); @@ -102,6 +110,10 @@ private void drawTestListWindow() { selected = i; } } + if(isSelected && scrollTo) { + scrollTo = false; + ImGuiInternal.ScrollToItem(); + } } ImGui.EndChild(); ImGui.End(); diff --git a/extensions/gdx-freetype-teavm/src/main/java/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeEmu.java b/extensions/gdx-freetype-teavm/src/main/java/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeEmu.java index dbb1c5d5..74258997 100644 --- a/extensions/gdx-freetype-teavm/src/main/java/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeEmu.java +++ b/extensions/gdx-freetype-teavm/src/main/java/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeEmu.java @@ -850,9 +850,8 @@ public static LibraryEmu initFreeType() { Preloader preloader = app.getPreloader(); preloader.loadScript(false, "freetype.js", new AssetLoaderListener() { @Override - public boolean onSuccess(String url, Object result) { + public void onSuccess(String url, Object result) { freeTypeInit = true; - return true; } @Override