Moved more packing logic to asset thread to prevent ANRs
This commit is contained in:
Binary file not shown.
@@ -25,6 +25,7 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.mod.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.ui.fragments.*;
|
||||
@@ -147,7 +148,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
@Override
|
||||
public Seq<AssetDescriptor> getDependencies(){
|
||||
return Seq.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", Font.class), new AssetDescriptor<>("default", Font.class));
|
||||
return Seq.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", Font.class), new AssetDescriptor<>("default", Font.class), new AssetDescriptor<>(Mods.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -59,7 +59,7 @@ public class SectorSubmissions{
|
||||
registerSerpuloSector(133, "wpx", "https://discord.com/channels/391020510269669376/1379926871227240770/1417920499761156126");
|
||||
registerSerpuloSector(185, "quad", "https://discord.com/channels/391020510269669376/1379926892181983283/1419231958336016458");
|
||||
registerSerpuloSector(254, "wpx", "https://discord.com/channels/391020510269669376/1379928045577703424/1420456601667502193");
|
||||
registerSerpuloSector(0, "Jamespire", "https://discord.com/channels/391020510269669376/1379926780860698784/1418590967384117311");
|
||||
registerSerpuloSector(0, "iqtik123", "https://discord.com/channels/391020510269669376/1379926780860698784/1431356682834940115");
|
||||
registerSerpuloSector(103, "enwyz", "https://discord.com/channels/391020510269669376/1379926839559979030/1429203869514207255");
|
||||
registerSerpuloSector(30, "cyan", "https://discord.com/channels/391020510269669376/1379926800854945823/1423932799647481910");
|
||||
|
||||
|
||||
@@ -40,10 +40,6 @@ public class Mods implements Loadable{
|
||||
private ObjectMap<String, Seq<Fi>> bundles = new ObjectMap<>();
|
||||
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites", "sprites-override", ".git");
|
||||
|
||||
private int totalSprites;
|
||||
private ObjectFloatMap<String> textureResize = new ObjectFloatMap<>();
|
||||
private MultiPacker packer;
|
||||
|
||||
/** Ordered mods cache. Set to null to invalidate. */
|
||||
private @Nullable Seq<LoadedMod> lastOrderedMods = new Seq<>();
|
||||
|
||||
@@ -144,10 +140,13 @@ public class Mods implements Loadable{
|
||||
@Override
|
||||
public void loadAsync(){
|
||||
if(!mods.contains(LoadedMod::enabled)) return;
|
||||
Time.mark();
|
||||
|
||||
long startTime = Time.millis();
|
||||
|
||||
//TODO this should estimate sprite sizes per page
|
||||
packer = new MultiPacker();
|
||||
MultiPacker packer = new MultiPacker();
|
||||
var textureResize = new ObjectFloatMap<String>();
|
||||
int[] totalSprites = {0};
|
||||
//all packing tasks to await
|
||||
var tasks = new Seq<Future<Runnable>>();
|
||||
|
||||
@@ -155,11 +154,11 @@ public class Mods implements Loadable{
|
||||
Seq<Fi> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png"));
|
||||
Seq<Fi> overrides = mod.root.child("sprites-override").findAll(f -> f.extension().equals("png"));
|
||||
|
||||
packSprites(sprites, mod, true, tasks);
|
||||
packSprites(overrides, mod, false, tasks);
|
||||
packSprites(packer, sprites, mod, true, tasks, textureResize);
|
||||
packSprites(packer, overrides, mod, false, tasks, textureResize);
|
||||
|
||||
Log.debug("Packed @ images for mod '@'.", sprites.size + overrides.size, mod.meta.name);
|
||||
totalSprites += sprites.size + overrides.size;
|
||||
totalSprites[0] += sprites.size + overrides.size;
|
||||
});
|
||||
|
||||
for(var result : tasks){
|
||||
@@ -179,78 +178,10 @@ public class Mods implements Loadable{
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("Time to pack textures: @", Time.elapsed());
|
||||
}
|
||||
Log.debug("Total sprites: @", totalSprites[0]);
|
||||
|
||||
private void loadIcons(){
|
||||
for(LoadedMod mod : mods){
|
||||
loadIcon(mod);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadIcon(LoadedMod mod){
|
||||
//try to load icon for each mod that can have one
|
||||
if(mod.root.child("icon.png").exists() && !headless){
|
||||
try{
|
||||
mod.iconTexture = new Texture(mod.root.child("icon.png"));
|
||||
mod.iconTexture.setFilter(TextureFilter.linear);
|
||||
}catch(Throwable t){
|
||||
Log.err("Failed to load icon for mod '" + mod.name + "'.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void packSprites(Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<Future<Runnable>> tasks){
|
||||
boolean bleed = Core.settings.getBool("linear", true) && !mod.meta.pregenerated;
|
||||
float textureScale = mod.meta.texturescale;
|
||||
|
||||
for(Fi file : sprites){
|
||||
String
|
||||
baseName = file.nameWithoutExtension(),
|
||||
regionName = baseName.contains(".") ? baseName.substring(0, baseName.indexOf(".")) : baseName;
|
||||
|
||||
if(!prefix && !Core.atlas.has(regionName)){
|
||||
Log.warn("Sprite '@' in mod '@' attempts to override a non-existent sprite.", regionName, mod.name);
|
||||
}
|
||||
|
||||
//read and bleed pixmaps in parallel
|
||||
tasks.add(mainExecutor.submit(() -> {
|
||||
|
||||
try{
|
||||
Pixmap pix = new Pixmap(file.readBytes());
|
||||
//only bleeds when linear filtering is on at startup
|
||||
if(bleed){
|
||||
Pixmaps.bleed(pix, 2);
|
||||
}
|
||||
//this returns a *runnable* which actually packs the resulting pixmap; this has to be done synchronously outside the method
|
||||
return () -> {
|
||||
//don't prefix with mod name if it's already prefixed by a category, e.g. `block-modname-content-full`.
|
||||
int hyphen = baseName.indexOf('-');
|
||||
String fullName = ((prefix && !(hyphen != -1 && baseName.substring(hyphen + 1).startsWith(mod.name + "-"))) ? mod.name + "-" : "") + baseName;
|
||||
|
||||
packer.add(getPage(file), fullName, new PixmapRegion(pix));
|
||||
if(textureScale != 1.0f){
|
||||
textureResize.put(fullName, textureScale);
|
||||
}
|
||||
pix.dispose();
|
||||
};
|
||||
}catch(Exception e){
|
||||
//rethrow exception with details about the cause of failure
|
||||
throw new Exception("Failed to load image " + file + " for mod " + mod.name, e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSync(){
|
||||
loadIcons();
|
||||
|
||||
if(packer == null) return;
|
||||
Time.mark();
|
||||
|
||||
//get textures packed
|
||||
if(totalSprites > 0){
|
||||
TextureFilter filter = Core.settings.getBool("linear", true) ? TextureFilter.linear : TextureFilter.nearest;
|
||||
Texture[] whiteToDispose = {null};
|
||||
|
||||
class RegionEntry{
|
||||
String name;
|
||||
@@ -297,17 +228,32 @@ public class Mods implements Loadable{
|
||||
}
|
||||
}
|
||||
|
||||
waitForMain(() -> {
|
||||
Core.atlas.dispose();
|
||||
|
||||
//dead shadow-atlas for getting regions, but not pixmaps
|
||||
var shadow = Core.atlas;
|
||||
//dummy texture atlas that returns the 'shadow' regions; used for mod loading
|
||||
Core.atlas = new TextureAtlas(){
|
||||
boolean foundWhite;
|
||||
AtlasRegion whiteRegion;
|
||||
|
||||
{
|
||||
//needed for the correct operation of the found() method in the TextureRegion
|
||||
error = shadow.find("error");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtlasRegion white(){
|
||||
if(Core.app.isOnMainThread() && !foundWhite){
|
||||
Pixmap pixmap = Pixmaps.blankPixmap();
|
||||
Texture tex = new Texture(pixmap);
|
||||
whiteToDispose[0] = tex;
|
||||
return whiteRegion = new AtlasRegion(tex, 0, 0, 1, 1);
|
||||
}
|
||||
return super.white();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtlasRegion find(String name){
|
||||
var base = packer.get(name);
|
||||
@@ -346,14 +292,11 @@ public class Mods implements Loadable{
|
||||
return out;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
TextureFilter filter = Core.settings.getBool("linear", true) ? TextureFilter.linear : TextureFilter.nearest;
|
||||
|
||||
Time.mark();
|
||||
//generate new icons
|
||||
for(Seq<Content> arr : content.getContentMap()){
|
||||
arr.each(c -> {
|
||||
//TODO this can be done in parallel
|
||||
if(c instanceof UnlockableContent u && c.minfo.mod != null){
|
||||
u.load();
|
||||
u.loadIcon();
|
||||
@@ -363,22 +306,20 @@ public class Mods implements Loadable{
|
||||
}
|
||||
});
|
||||
}
|
||||
Log.debug("Time to generate icons: @", Time.elapsed());
|
||||
|
||||
waitForMain(() -> {
|
||||
if(whiteToDispose[0] != null){
|
||||
whiteToDispose[0].dispose();
|
||||
}
|
||||
|
||||
//replace old atlas data
|
||||
Core.atlas = packer.flush(filter, new TextureAtlas(){
|
||||
PixmapRegion fake = new PixmapRegion(new Pixmap(1, 1));
|
||||
boolean didWarn = false;
|
||||
|
||||
@Override
|
||||
public PixmapRegion getPixmap(AtlasRegion region){
|
||||
var other = super.getPixmap(region);
|
||||
if(other.pixmap.isDisposed()){
|
||||
if(!didWarn){
|
||||
Log.err(new RuntimeException("Calling getPixmap outside of createIcons is not supported! This will be a crash in the future."));
|
||||
didWarn = true;
|
||||
}
|
||||
return fake;
|
||||
throw new RuntimeException("Calling getPixmap outside of createIcons is not supported!");
|
||||
}
|
||||
|
||||
return other;
|
||||
@@ -393,11 +334,89 @@ public class Mods implements Loadable{
|
||||
packer.printStats();
|
||||
|
||||
Events.fire(new AtlasPackEvent());
|
||||
}
|
||||
|
||||
packer.dispose();
|
||||
packer = null;
|
||||
Log.debug("Total time to generate & flush textures synchronously: @", Time.elapsed());
|
||||
|
||||
Log.debug("Total time to pack and generate sprites: @ms", Time.timeSinceMillis(startTime));
|
||||
});
|
||||
}
|
||||
|
||||
private void loadIcons(){
|
||||
for(LoadedMod mod : mods){
|
||||
loadIcon(mod);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadIcon(LoadedMod mod){
|
||||
//try to load icon for each mod that can have one
|
||||
if(mod.root.child("icon.png").exists() && !headless){
|
||||
try{
|
||||
mod.iconTexture = new Texture(mod.root.child("icon.png"));
|
||||
mod.iconTexture.setFilter(TextureFilter.linear);
|
||||
}catch(Throwable t){
|
||||
Log.err("Failed to load icon for mod '" + mod.name + "'.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void packSprites(MultiPacker packer, Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<Future<Runnable>> tasks, ObjectFloatMap<String> textureResize){
|
||||
boolean bleed = Core.settings.getBool("linear", true) && !mod.meta.pregenerated;
|
||||
float textureScale = mod.meta.texturescale;
|
||||
|
||||
for(Fi file : sprites){
|
||||
String
|
||||
baseName = file.nameWithoutExtension(),
|
||||
regionName = baseName.contains(".") ? baseName.substring(0, baseName.indexOf(".")) : baseName;
|
||||
|
||||
if(!prefix && !Core.atlas.has(regionName)){
|
||||
Log.warn("Sprite '@' in mod '@' attempts to override a non-existent sprite.", regionName, mod.name);
|
||||
}
|
||||
|
||||
//read and bleed pixmaps in parallel
|
||||
tasks.add(mainExecutor.submit(() -> {
|
||||
|
||||
try{
|
||||
Pixmap pix = new Pixmap(file.readBytes());
|
||||
//only bleeds when linear filtering is on at startup
|
||||
if(bleed){
|
||||
Pixmaps.bleed(pix, 2);
|
||||
}
|
||||
//this returns a *runnable* which actually packs the resulting pixmap; this has to be done synchronously outside the method
|
||||
return () -> {
|
||||
//don't prefix with mod name if it's already prefixed by a category, e.g. `block-modname-content-full`.
|
||||
int hyphen = baseName.indexOf('-');
|
||||
String fullName = ((prefix && !(hyphen != -1 && baseName.substring(hyphen + 1).startsWith(mod.name + "-"))) ? mod.name + "-" : "") + baseName;
|
||||
|
||||
packer.add(getPage(file), fullName, new PixmapRegion(pix));
|
||||
if(textureScale != 1.0f){
|
||||
textureResize.put(fullName, textureScale);
|
||||
}
|
||||
pix.dispose();
|
||||
};
|
||||
}catch(Exception e){
|
||||
//rethrow exception with details about the cause of failure
|
||||
throw new Exception("Failed to load image " + file + " for mod " + mod.name, e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void waitForMain(Runnable run){
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Core.app.post(() -> {
|
||||
run.run();
|
||||
latch.countDown();
|
||||
});
|
||||
try{
|
||||
latch.await();
|
||||
}catch(InterruptedException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSync(){
|
||||
loadIcons();
|
||||
}
|
||||
|
||||
private PageType getPage(Fi file){
|
||||
|
||||
Reference in New Issue
Block a user