Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up asset downloader, preloader and some improvements #120

Merged
merged 2 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- Call dispose when browser closes
- Improve clipboard text copy/paste.
- Change default sound/music api to howler.js
- add shouldEncodePreference config
- add localStoragePrefix config
- AssetManager can now download assets

[1.0.0-b9]
- add TeaClassFilter printAllowedClasses() and printExcludedClasses()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Void> {
AssetManager manager;
final AssetDescriptor assetDesc;
final AssetLoader loader;
final AsyncExecutor executor;
final long startTime;

volatile boolean asyncDone;
volatile boolean dependenciesLoaded;
volatile Array<AssetDescriptor> dependencies;
volatile AsyncResult<Void> depsFuture;
volatile AsyncResult<Void> 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<AssetDescriptor> 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;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ public void load (String libraryName) {
Preloader preloader = app.getPreloader();
preloader.loadScript(false, libraryName + ".js", new AssetLoaderListener<Object>() {
@Override
public boolean onSuccess(String url, Object result) {
return true;
public void onSuccess(String url, Object result) {
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -481,18 +481,16 @@ public enum AppState {
private void initGdx() {
preloader.loadScript(true, "gdx.wasm.js", new AssetLoaderListener<Object>() {
@Override
public boolean onSuccess(String url, Object result) {
return true;
public void onSuccess(String url, Object result) {
}
});
}

private void initSound() {
preloader.loadScript(true, "howler.js", new AssetLoaderListener<Object>() {
@Override
public boolean onSuccess(String url, Object result) {
return true;
public void onSuccess(String url, Object result) {
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -91,7 +91,7 @@ public boolean isExternalStorageAvailable() {

@Override
public String getLocalStoragePath() {
return storagePath;
return localStoragePrefix;
}

@Override
Expand Down
Loading