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.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
import mindustry.logic.*;
|
import mindustry.logic.*;
|
||||||
|
import mindustry.mod.*;
|
||||||
import mindustry.ui.*;
|
import mindustry.ui.*;
|
||||||
import mindustry.ui.dialogs.*;
|
import mindustry.ui.dialogs.*;
|
||||||
import mindustry.ui.fragments.*;
|
import mindustry.ui.fragments.*;
|
||||||
@@ -147,7 +148,7 @@ public class UI implements ApplicationListener, Loadable{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Seq<AssetDescriptor> getDependencies(){
|
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
|
@Override
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class SectorSubmissions{
|
|||||||
registerSerpuloSector(133, "wpx", "https://discord.com/channels/391020510269669376/1379926871227240770/1417920499761156126");
|
registerSerpuloSector(133, "wpx", "https://discord.com/channels/391020510269669376/1379926871227240770/1417920499761156126");
|
||||||
registerSerpuloSector(185, "quad", "https://discord.com/channels/391020510269669376/1379926892181983283/1419231958336016458");
|
registerSerpuloSector(185, "quad", "https://discord.com/channels/391020510269669376/1379926892181983283/1419231958336016458");
|
||||||
registerSerpuloSector(254, "wpx", "https://discord.com/channels/391020510269669376/1379928045577703424/1420456601667502193");
|
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(103, "enwyz", "https://discord.com/channels/391020510269669376/1379926839559979030/1429203869514207255");
|
||||||
registerSerpuloSector(30, "cyan", "https://discord.com/channels/391020510269669376/1379926800854945823/1423932799647481910");
|
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 ObjectMap<String, Seq<Fi>> bundles = new ObjectMap<>();
|
||||||
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites", "sprites-override", ".git");
|
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. */
|
/** Ordered mods cache. Set to null to invalidate. */
|
||||||
private @Nullable Seq<LoadedMod> lastOrderedMods = new Seq<>();
|
private @Nullable Seq<LoadedMod> lastOrderedMods = new Seq<>();
|
||||||
|
|
||||||
@@ -144,10 +140,13 @@ public class Mods implements Loadable{
|
|||||||
@Override
|
@Override
|
||||||
public void loadAsync(){
|
public void loadAsync(){
|
||||||
if(!mods.contains(LoadedMod::enabled)) return;
|
if(!mods.contains(LoadedMod::enabled)) return;
|
||||||
Time.mark();
|
|
||||||
|
long startTime = Time.millis();
|
||||||
|
|
||||||
//TODO this should estimate sprite sizes per page
|
//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
|
//all packing tasks to await
|
||||||
var tasks = new Seq<Future<Runnable>>();
|
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> 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"));
|
Seq<Fi> overrides = mod.root.child("sprites-override").findAll(f -> f.extension().equals("png"));
|
||||||
|
|
||||||
packSprites(sprites, mod, true, tasks);
|
packSprites(packer, sprites, mod, true, tasks, textureResize);
|
||||||
packSprites(overrides, mod, false, tasks);
|
packSprites(packer, overrides, mod, false, tasks, textureResize);
|
||||||
|
|
||||||
Log.debug("Packed @ images for mod '@'.", sprites.size + overrides.size, mod.meta.name);
|
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){
|
for(var result : tasks){
|
||||||
@@ -179,7 +178,167 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.debug("Time to pack textures: @", Time.elapsed());
|
Log.debug("Total sprites: @", totalSprites[0]);
|
||||||
|
|
||||||
|
TextureFilter filter = Core.settings.getBool("linear", true) ? TextureFilter.linear : TextureFilter.nearest;
|
||||||
|
Texture[] whiteToDispose = {null};
|
||||||
|
|
||||||
|
class RegionEntry{
|
||||||
|
String name;
|
||||||
|
PixmapRegion region;
|
||||||
|
int[] splits, pads;
|
||||||
|
|
||||||
|
RegionEntry(String name, PixmapRegion region, int[] splits, int[] pads){
|
||||||
|
this.name = name;
|
||||||
|
this.region = region;
|
||||||
|
this.splits = splits;
|
||||||
|
this.pads = pads;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Seq<RegionEntry>[] entries = new Seq[PageType.all.length];
|
||||||
|
for(int i = 0; i < PageType.all.length; i++){
|
||||||
|
entries[i] = new Seq<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectMap<Texture, PageType> pageTypes = ObjectMap.of(
|
||||||
|
Core.atlas.find("white").texture, PageType.main,
|
||||||
|
Core.atlas.find("stone1").texture, PageType.environment,
|
||||||
|
Core.atlas.find("whiteui").texture, PageType.ui,
|
||||||
|
Core.atlas.find("rubble-1-0").texture, PageType.rubble
|
||||||
|
);
|
||||||
|
|
||||||
|
for(AtlasRegion region : Core.atlas.getRegions()){
|
||||||
|
PageType type = pageTypes.get(region.texture, PageType.main);
|
||||||
|
|
||||||
|
if(!packer.has(type, region.name)){
|
||||||
|
entries[type.ordinal()].add(new RegionEntry(region.name, Core.atlas.getPixmap(region), region.splits, region.pads));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//sort each page type by size first, for optimal packing
|
||||||
|
for(int i = 0; i < PageType.all.length; i++){
|
||||||
|
var rects = entries[i];
|
||||||
|
var type = PageType.all[i];
|
||||||
|
//TODO is this in reverse order?
|
||||||
|
rects.sort(Structs.comparingInt(o -> -Math.max(o.region.width, o.region.height)));
|
||||||
|
|
||||||
|
for(var entry : rects){
|
||||||
|
packer.add(type, entry.name, entry.region, entry.splits, entry.pads);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if(base != null){
|
||||||
|
var reg = new AtlasRegion(shadow.find(name).texture, base.x, base.y, base.width, base.height);
|
||||||
|
reg.name = name;
|
||||||
|
reg.pixmapRegion = base;
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shadow.find(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFound(TextureRegion region){
|
||||||
|
return region != shadow.find("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextureRegion find(String name, TextureRegion def){
|
||||||
|
return !has(name) ? def : find(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean has(String s){
|
||||||
|
return shadow.has(s) || packer.get(s) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//return the *actual* pixmap regions, not the disposed ones.
|
||||||
|
@Override
|
||||||
|
public PixmapRegion getPixmap(AtlasRegion region){
|
||||||
|
PixmapRegion out = packer.get(region.name);
|
||||||
|
//this should not happen in normal situations
|
||||||
|
if(out == null) return packer.get("error");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//generate new icons
|
||||||
|
for(Seq<Content> arr : content.getContentMap()){
|
||||||
|
arr.each(c -> {
|
||||||
|
if(c instanceof UnlockableContent u && c.minfo.mod != null){
|
||||||
|
u.load();
|
||||||
|
u.loadIcon();
|
||||||
|
if(u.generateIcons && !c.minfo.mod.meta.pregenerated){
|
||||||
|
u.createIcons(packer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForMain(() -> {
|
||||||
|
if(whiteToDispose[0] != null){
|
||||||
|
whiteToDispose[0].dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
//replace old atlas data
|
||||||
|
Core.atlas = packer.flush(filter, new TextureAtlas(){
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PixmapRegion getPixmap(AtlasRegion region){
|
||||||
|
var other = super.getPixmap(region);
|
||||||
|
if(other.pixmap.isDisposed()){
|
||||||
|
throw new RuntimeException("Calling getPixmap outside of createIcons is not supported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
textureResize.each(e -> Core.atlas.find(e.key).scale = e.value);
|
||||||
|
|
||||||
|
Core.atlas.setErrorRegion("error");
|
||||||
|
Log.debug("Total pages: @", Core.atlas.getTextures().size);
|
||||||
|
|
||||||
|
packer.printStats();
|
||||||
|
|
||||||
|
Events.fire(new AtlasPackEvent());
|
||||||
|
|
||||||
|
packer.dispose();
|
||||||
|
|
||||||
|
Log.debug("Total time to pack and generate sprites: @ms", Time.timeSinceMillis(startTime));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadIcons(){
|
private void loadIcons(){
|
||||||
@@ -200,7 +359,7 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void packSprites(Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<Future<Runnable>> tasks){
|
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;
|
boolean bleed = Core.settings.getBool("linear", true) && !mod.meta.pregenerated;
|
||||||
float textureScale = mod.meta.texturescale;
|
float textureScale = mod.meta.texturescale;
|
||||||
|
|
||||||
@@ -242,162 +401,22 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public void loadSync(){
|
public void loadSync(){
|
||||||
loadIcons();
|
loadIcons();
|
||||||
|
|
||||||
if(packer == null) return;
|
|
||||||
Time.mark();
|
|
||||||
|
|
||||||
//get textures packed
|
|
||||||
if(totalSprites > 0){
|
|
||||||
|
|
||||||
class RegionEntry{
|
|
||||||
String name;
|
|
||||||
PixmapRegion region;
|
|
||||||
int[] splits, pads;
|
|
||||||
|
|
||||||
RegionEntry(String name, PixmapRegion region, int[] splits, int[] pads){
|
|
||||||
this.name = name;
|
|
||||||
this.region = region;
|
|
||||||
this.splits = splits;
|
|
||||||
this.pads = pads;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Seq<RegionEntry>[] entries = new Seq[PageType.all.length];
|
|
||||||
for(int i = 0; i < PageType.all.length; i++){
|
|
||||||
entries[i] = new Seq<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectMap<Texture, PageType> pageTypes = ObjectMap.of(
|
|
||||||
Core.atlas.find("white").texture, PageType.main,
|
|
||||||
Core.atlas.find("stone1").texture, PageType.environment,
|
|
||||||
Core.atlas.find("whiteui").texture, PageType.ui,
|
|
||||||
Core.atlas.find("rubble-1-0").texture, PageType.rubble
|
|
||||||
);
|
|
||||||
|
|
||||||
for(AtlasRegion region : Core.atlas.getRegions()){
|
|
||||||
PageType type = pageTypes.get(region.texture, PageType.main);
|
|
||||||
|
|
||||||
if(!packer.has(type, region.name)){
|
|
||||||
entries[type.ordinal()].add(new RegionEntry(region.name, Core.atlas.getPixmap(region), region.splits, region.pads));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//sort each page type by size first, for optimal packing
|
|
||||||
for(int i = 0; i < PageType.all.length; i++){
|
|
||||||
var rects = entries[i];
|
|
||||||
var type = PageType.all[i];
|
|
||||||
//TODO is this in reverse order?
|
|
||||||
rects.sort(Structs.comparingInt(o -> -Math.max(o.region.width, o.region.height)));
|
|
||||||
|
|
||||||
for(var entry : rects){
|
|
||||||
packer.add(type, entry.name, entry.region, entry.splits, entry.pads);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(){
|
|
||||||
{
|
|
||||||
//needed for the correct operation of the found() method in the TextureRegion
|
|
||||||
error = shadow.find("error");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AtlasRegion find(String name){
|
|
||||||
var base = packer.get(name);
|
|
||||||
|
|
||||||
if(base != null){
|
|
||||||
var reg = new AtlasRegion(shadow.find(name).texture, base.x, base.y, base.width, base.height);
|
|
||||||
reg.name = name;
|
|
||||||
reg.pixmapRegion = base;
|
|
||||||
return reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shadow.find(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFound(TextureRegion region){
|
|
||||||
return region != shadow.find("error");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TextureRegion find(String name, TextureRegion def){
|
|
||||||
return !has(name) ? def : find(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean has(String s){
|
|
||||||
return shadow.has(s) || packer.get(s) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//return the *actual* pixmap regions, not the disposed ones.
|
|
||||||
@Override
|
|
||||||
public PixmapRegion getPixmap(AtlasRegion region){
|
|
||||||
PixmapRegion out = packer.get(region.name);
|
|
||||||
//this should not happen in normal situations
|
|
||||||
if(out == null) return packer.get("error");
|
|
||||||
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();
|
|
||||||
if(u.generateIcons && !c.minfo.mod.meta.pregenerated){
|
|
||||||
u.createIcons(packer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Log.debug("Time to generate icons: @", Time.elapsed());
|
|
||||||
|
|
||||||
//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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return other;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
textureResize.each(e -> Core.atlas.find(e.key).scale = e.value);
|
|
||||||
|
|
||||||
Core.atlas.setErrorRegion("error");
|
|
||||||
Log.debug("Total pages: @", Core.atlas.getTextures().size);
|
|
||||||
|
|
||||||
packer.printStats();
|
|
||||||
|
|
||||||
Events.fire(new AtlasPackEvent());
|
|
||||||
}
|
|
||||||
|
|
||||||
packer.dispose();
|
|
||||||
packer = null;
|
|
||||||
Log.debug("Total time to generate & flush textures synchronously: @", Time.elapsed());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PageType getPage(Fi file){
|
private PageType getPage(Fi file){
|
||||||
|
|||||||
Reference in New Issue
Block a user