diff --git a/core/assets/maps/hidden-serpulo/0.msav b/core/assets/maps/hidden-serpulo/0.msav index 6dec7ac800..4437244046 100644 Binary files a/core/assets/maps/hidden-serpulo/0.msav and b/core/assets/maps/hidden-serpulo/0.msav differ diff --git a/core/src/mindustry/core/UI.java b/core/src/mindustry/core/UI.java index 124e9ce07d..58f4dfa5ab 100644 --- a/core/src/mindustry/core/UI.java +++ b/core/src/mindustry/core/UI.java @@ -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 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 diff --git a/core/src/mindustry/maps/SectorSubmissions.java b/core/src/mindustry/maps/SectorSubmissions.java index 9a62e0e287..67db33f168 100644 --- a/core/src/mindustry/maps/SectorSubmissions.java +++ b/core/src/mindustry/maps/SectorSubmissions.java @@ -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"); diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index f356f882fd..aef65e50ec 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -40,10 +40,6 @@ public class Mods implements Loadable{ private ObjectMap> bundles = new ObjectMap<>(); private ObjectSet specialFolders = ObjectSet.with("bundles", "sprites", "sprites-override", ".git"); - private int totalSprites; - private ObjectFloatMap textureResize = new ObjectFloatMap<>(); - private MultiPacker packer; - /** Ordered mods cache. Set to null to invalidate. */ private @Nullable Seq 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(); + int[] totalSprites = {0}; //all packing tasks to await var tasks = new Seq>(); @@ -155,11 +154,11 @@ public class Mods implements Loadable{ Seq sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png")); Seq 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[] entries = new Seq[PageType.all.length]; + for(int i = 0; i < PageType.all.length; i++){ + entries[i] = new Seq<>(); + } + + ObjectMap 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 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 sprites, LoadedMod mod, boolean prefix, Seq> tasks){ + private void packSprites(MultiPacker packer, Seq sprites, LoadedMod mod, boolean prefix, Seq> tasks, ObjectFloatMap 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[] entries = new Seq[PageType.all.length]; - for(int i = 0; i < PageType.all.length; i++){ - entries[i] = new Seq<>(); - } - - ObjectMap 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 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){