package net.minecraft.client.renderer.texture; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.logging.LogUtils; import com.mojang.realmsclient.gui.screens.AddRealmPopupScreen; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.resources.ResourceManager; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class TextureManager implements PreparableReloadListener, Tickable, AutoCloseable { private static final Logger LOGGER = LogUtils.getLogger(); public static final ResourceLocation INTENTIONAL_MISSING_TEXTURE = ResourceLocation.withDefaultNamespace(""); private final Map byPath = Maps.newHashMap(); private final Set tickableTextures = Sets.newHashSet(); private final Map prefixRegister = Maps.newHashMap(); private final ResourceManager resourceManager; public TextureManager(ResourceManager resourceManager) { this.resourceManager = resourceManager; } public void register(ResourceLocation path, AbstractTexture texture) { texture = this.loadTexture(path, texture); AbstractTexture abstractTexture = (AbstractTexture)this.byPath.put(path, texture); if (abstractTexture != texture) { if (abstractTexture != null && abstractTexture != MissingTextureAtlasSprite.getTexture()) { this.safeClose(path, abstractTexture); } if (texture instanceof Tickable) { this.tickableTextures.add((Tickable)texture); } } } private void safeClose(ResourceLocation path, AbstractTexture texture) { if (texture != MissingTextureAtlasSprite.getTexture()) { this.tickableTextures.remove(texture); try { texture.close(); } catch (Exception var4) { LOGGER.warn("Failed to close texture {}", path, var4); } } texture.releaseId(); } private AbstractTexture loadTexture(ResourceLocation path, AbstractTexture texture) { try { texture.load(this.resourceManager); return texture; } catch (IOException var6) { if (path != INTENTIONAL_MISSING_TEXTURE) { LOGGER.warn("Failed to load texture: {}", path, var6); } return MissingTextureAtlasSprite.getTexture(); } catch (Throwable var7) { CrashReport crashReport = CrashReport.forThrowable(var7, "Registering texture"); CrashReportCategory crashReportCategory = crashReport.addCategory("Resource location being registered"); crashReportCategory.setDetail("Resource location", path); crashReportCategory.setDetail("Texture object class", (CrashReportDetail)(() -> texture.getClass().getName())); throw new ReportedException(crashReport); } } public AbstractTexture getTexture(ResourceLocation path) { AbstractTexture abstractTexture = (AbstractTexture)this.byPath.get(path); if (abstractTexture == null) { abstractTexture = new SimpleTexture(path); this.register(path, abstractTexture); } return abstractTexture; } public AbstractTexture getTexture(ResourceLocation path, AbstractTexture defaultTexture) { return (AbstractTexture)this.byPath.getOrDefault(path, defaultTexture); } public ResourceLocation register(String name, DynamicTexture texture) { Integer integer = (Integer)this.prefixRegister.get(name); if (integer == null) { integer = 1; } else { integer = integer + 1; } this.prefixRegister.put(name, integer); ResourceLocation resourceLocation = ResourceLocation.withDefaultNamespace(String.format(Locale.ROOT, "dynamic/%s_%d", name, integer)); this.register(resourceLocation, texture); return resourceLocation; } public CompletableFuture preload(ResourceLocation path, Executor backgroundExecutor) { if (!this.byPath.containsKey(path)) { PreloadedTexture preloadedTexture = new PreloadedTexture(this.resourceManager, path, backgroundExecutor); this.byPath.put(path, preloadedTexture); return preloadedTexture.getFuture().thenRunAsync(() -> this.register(path, preloadedTexture), TextureManager::execute); } else { return CompletableFuture.completedFuture(null); } } private static void execute(Runnable runnable) { Minecraft.getInstance().execute(() -> RenderSystem.recordRenderCall(runnable::run)); } @Override public void tick() { for (Tickable tickable : this.tickableTextures) { tickable.tick(); } } public void release(ResourceLocation path) { AbstractTexture abstractTexture = (AbstractTexture)this.byPath.remove(path); if (abstractTexture != null) { this.safeClose(path, abstractTexture); } } public void close() { this.byPath.forEach(this::safeClose); this.byPath.clear(); this.tickableTextures.clear(); this.prefixRegister.clear(); } @Override public CompletableFuture reload( PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, Executor executor, Executor executor2 ) { CompletableFuture completableFuture = new CompletableFuture(); TitleScreen.preloadResources(this, executor).thenCompose(preparationBarrier::wait).thenAcceptAsync(void_ -> { MissingTextureAtlasSprite.getTexture(); AddRealmPopupScreen.updateCarouselImages(this.resourceManager); Iterator> iterator = this.byPath.entrySet().iterator(); while (iterator.hasNext()) { Entry entry = (Entry)iterator.next(); ResourceLocation resourceLocation = (ResourceLocation)entry.getKey(); AbstractTexture abstractTexture = (AbstractTexture)entry.getValue(); if (abstractTexture == MissingTextureAtlasSprite.getTexture() && !resourceLocation.equals(MissingTextureAtlasSprite.getLocation())) { iterator.remove(); } else { abstractTexture.reset(this, resourceManager, resourceLocation, executor2); } } Minecraft.getInstance().schedule(() -> completableFuture.complete(null)); }, runnable -> RenderSystem.recordRenderCall(runnable::run)); return completableFuture; } public void dumpAllSheets(Path path) { if (!RenderSystem.isOnRenderThread()) { RenderSystem.recordRenderCall(() -> this._dumpAllSheets(path)); } else { this._dumpAllSheets(path); } } private void _dumpAllSheets(Path path) { try { Files.createDirectories(path); } catch (IOException var3) { LOGGER.error("Failed to create directory {}", path, var3); return; } this.byPath.forEach((resourceLocation, abstractTexture) -> { if (abstractTexture instanceof Dumpable dumpable) { try { dumpable.dumpContents(resourceLocation, path); } catch (IOException var5) { LOGGER.error("Failed to dump texture {}", resourceLocation, var5); } } }); } }