mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-12-15 15:20:57 -08:00
Moved more packing logic to asset thread to prevent ANRs
This commit is contained in:
parent
a226a74578
commit
c06c76d296
4 changed files with 186 additions and 166 deletions
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,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(){
|
||||
|
|
@ -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;
|
||||
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
|
||||
public void loadSync(){
|
||||
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){
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue