diff --git a/arc-core/src/arc/assets/AssetLoadingTask.java b/arc-core/src/arc/assets/AssetLoadingTask.java index bc0edb29..89ded783 100644 --- a/arc-core/src/arc/assets/AssetLoadingTask.java +++ b/arc-core/src/arc/assets/AssetLoadingTask.java @@ -140,10 +140,12 @@ private void removeDuplicates(Seq 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; + AssetDescriptor ai = array.get(i); + final String fn = ai.fileName; + final Class type = ai.type; for(int j = array.size - 1; j > i; --j){ - if(type == array.get(j).type && fn.equals(array.get(j).fileName)) + AssetDescriptor aj = array.get(j); + if(type == aj.type && fn.equals(aj.fileName)) array.remove(j); } } diff --git a/arc-core/src/arc/assets/AssetManager.java b/arc-core/src/arc/assets/AssetManager.java index 4e707244..7f6720eb 100644 --- a/arc-core/src/arc/assets/AssetManager.java +++ b/arc-core/src/arc/assets/AssetManager.java @@ -510,12 +510,7 @@ synchronized void injectDependencies(String parentAssetFilename, Seq dependencies = assetDependencies.get(parentAssetFilename); - if(dependencies == null){ - dependencies = new Seq(); - assetDependencies.put(parentAssetFilename, dependencies); - } - dependencies.add(dependendAssetDesc.fileName); + assetDependencies.get(parentAssetFilename, new Seq<>()).add(dependendAssetDesc.fileName); // if the asset is already loaded, increase its reference count. if(isLoaded(dependendAssetDesc.fileName)){ diff --git a/arc-core/src/arc/assets/loaders/TextureAtlasLoader.java b/arc-core/src/arc/assets/loaders/TextureAtlasLoader.java index 942d34bb..73fd025b 100644 --- a/arc-core/src/arc/assets/loaders/TextureAtlasLoader.java +++ b/arc-core/src/arc/assets/loaders/TextureAtlasLoader.java @@ -8,6 +8,9 @@ import arc.graphics.g2d.TextureAtlas.*; import arc.graphics.g2d.TextureAtlas.TextureAtlasData.*; import arc.struct.*; +import arc.util.*; + +import java.util.concurrent.*; /** * {@link AssetLoader} to load {@link TextureAtlas} instances. Passing a {@link TextureAtlasParameter} to @@ -15,17 +18,71 @@ * on the y-axis or not. * @author mzechner */ -public class TextureAtlasLoader extends SynchronousAssetLoader{ +public class TextureAtlasLoader extends AsynchronousAssetLoader{ TextureAtlasData data; + ExecutorCompletionService pool; + int numTasks; public TextureAtlasLoader(FileHandleResolver resolver){ super(resolver); } @Override - public TextureAtlas load(AssetManager assetManager, String fileName, Fi file, TextureAtlasParameter parameter){ + public void loadAsync(AssetManager manager, String fileName, Fi atlasFile, TextureAtlasParameter parameter){ + Fi imgDir = atlasFile.parent(); + data = new TextureAtlasData(atlasFile, imgDir, parameter != null && parameter.flip); + + ExecutorService exec = null; // Used so we can shut it down in this method + Seq dependencies = new Seq<>(); + numTasks = 0; + for(AtlasPage page : data.getPages()){ + String pageFileName = page.textureFile.path().replaceAll("\\\\", "/"); + if(!manager.isLoaded(pageFileName)){ + TextureParameter params = new TextureParameter(); + params.genMipMaps = page.useMipMaps; + params.minFilter = page.minFilter; + params.magFilter = page.magFilter; + if(manager.getLoader(Texture.class, pageFileName).getClass() == TextureLoader.class){ // We cannot trust whatever subclass a mod may use, we should fall back to vanilla behavior + if(pool == null) pool = new ExecutorCompletionService(exec = Threads.executor("Texture Atlas Loader")); + // A single TextureLoader instance cannot be used by multiple threads at once, create a new one for every page to be safe + TextureLoader pageLoader = new TextureLoader(manager.getFileHandleResolver()); + numTasks++; + pool.submit(() -> pageLoader.loadAsync(manager, pageFileName, page.textureFile, params), pageLoader); + }else{ // Add dependencies so that vanilla behavior is used instead + dependencies.add(new AssetDescriptor(page.textureFile, Texture.class, params)); + } + } + } + if(dependencies.any()) Reflect.invoke(manager, "injectDependencies", new Object[]{fileName, dependencies}, String.class, Seq.class); // Emulate vanilla behavior of getDependencies. + if(exec != null) exec.shutdown(); // shut down the executor but do not wait for it + } + + @Override + public TextureAtlas loadSync(AssetManager manager, String fileName, Fi atlasFile, TextureAtlasParameter parameter){ + if(pool != null){ + long await = Time.nanos(); + try { + for(int i = 0; i < numTasks; i++){ // Funnily enough, the longest part of the atlas loading process is just waiting for the first page to finish loadAsync + TextureLoader pageLoader = pool.take().get(); + AtlasPage page = data.getPages().find(p -> p.textureFile.path().replaceAll("\\\\", "/").equals(pageLoader.info.filename)); + TextureParameter params = new TextureParameter(); + params.genMipMaps = page.useMipMaps; + params.minFilter = page.minFilter; + params.magFilter = page.magFilter; + // Run the sync portion + page.texture = pageLoader.loadSync(manager, pageLoader.info.filename, page.textureFile, params); + } + }catch(InterruptedException | ExecutionException e){ + throw new ArcRuntimeException(e); + } + pool = null; + Log.debug("Awaited atlas pool for: @ms", Time.millisSinceNanos(await)); + } + + // If a mod has caused the vanilla behavior fallback, the page textures won't have been set by the block above for(AtlasPage page : data.getPages()){ - page.texture = assetManager.get(page.textureFile.path(), Texture.class); + if(page.texture != null) continue; // This page was already set by the block above + page.texture = manager.get(page.textureFile.path(), Texture.class); } TextureAtlas atlas = new TextureAtlas(data); @@ -35,23 +92,7 @@ public TextureAtlas load(AssetManager assetManager, String fileName, Fi file, Te @Override public Seq getDependencies(String fileName, Fi atlasFile, TextureAtlasParameter parameter){ - Fi imgDir = atlasFile.parent(); - - if(parameter != null){ - data = new TextureAtlasData(atlasFile, imgDir, parameter.flip); - }else{ - data = new TextureAtlasData(atlasFile, imgDir, false); - } - - Seq dependencies = new Seq<>(); - for(AtlasPage page : data.getPages()){ - TextureParameter params = new TextureParameter(); - params.genMipMaps = page.useMipMaps; - params.minFilter = page.minFilter; - params.magFilter = page.magFilter; - dependencies.add(new AssetDescriptor<>(page.textureFile, Texture.class, params)); - } - return dependencies; + return null; // We will inject the dependencies if they're needed later } public static class TextureAtlasParameter extends AssetLoaderParameters{ diff --git a/arc-core/src/arc/assets/loaders/TextureLoader.java b/arc-core/src/arc/assets/loaders/TextureLoader.java index 6c9581b1..22b48579 100644 --- a/arc-core/src/arc/assets/loaders/TextureLoader.java +++ b/arc-core/src/arc/assets/loaders/TextureLoader.java @@ -40,7 +40,7 @@ public void loadAsync(AssetManager manager, String fileName, Fi file, TexturePar info.texture = parameter.texture; } if(!info.data.isPrepared()) info.data.prepare(); - // Log.infoTag("Textures", "Async: " + fileName + " in " + Time.millisSinceNanos(start) + "ms"); + Log.infoTag("Textures", "Async: " + fileName + " in " + Time.millisSinceNanos(start) + "ms"); } @Override @@ -52,7 +52,7 @@ public Texture loadSync(AssetManager manager, String fileName, Fi file, TextureP }else{ long texS = Time.nanos(); texture = new Texture(info.data); - Log.debug("Made the @ texture in @", fileName, Time.millisSinceNanos(texS)); + Log.debug("Made the @ texture in @ms", fileName, Time.millisSinceNanos(texS)); } if(parameter != null){ texture.setFilter(parameter.minFilter, parameter.magFilter);