package net.minecraft.client.renderer.texture; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.logging.LogUtils; import com.mojang.realmsclient.gui.screens.AddRealmPopupScreen; import java.io.FileNotFoundException; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.ReportedException; 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 = new HashMap(); private final Set tickableTextures = new HashSet(); private final ResourceManager resourceManager; public TextureManager(ResourceManager resourceManager) { this.resourceManager = resourceManager; NativeImage nativeImage = MissingTextureAtlasSprite.generateMissingImage(); this.register(MissingTextureAtlasSprite.getLocation(), new DynamicTexture(() -> "(intentionally-)Missing Texture", nativeImage)); } public void registerAndLoad(ResourceLocation textureId, ReloadableTexture texture) { try { texture.apply(this.loadContentsSafe(textureId, texture)); } catch (Throwable var6) { CrashReport crashReport = CrashReport.forThrowable(var6, "Uploading texture"); CrashReportCategory crashReportCategory = crashReport.addCategory("Uploaded texture"); crashReportCategory.setDetail("Resource location", texture.resourceId()); crashReportCategory.setDetail("Texture id", textureId); throw new ReportedException(crashReport); } this.register(textureId, texture); } private TextureContents loadContentsSafe(ResourceLocation textureId, ReloadableTexture texture) { try { return loadContents(this.resourceManager, textureId, texture); } catch (Exception var4) { LOGGER.error("Failed to load texture {} into slot {}", texture.resourceId(), textureId, var4); return TextureContents.createMissing(); } } public void registerForNextReload(ResourceLocation textureId) { this.register(textureId, new SimpleTexture(textureId)); } public void register(ResourceLocation path, AbstractTexture texture) { AbstractTexture abstractTexture = (AbstractTexture)this.byPath.put(path, texture); if (abstractTexture != texture) { if (abstractTexture != null) { this.safeClose(path, abstractTexture); } if (texture instanceof Tickable tickable) { this.tickableTextures.add(tickable); } } } private void safeClose(ResourceLocation path, AbstractTexture texture) { this.tickableTextures.remove(texture); try { texture.close(); } catch (Exception var4) { LOGGER.warn("Failed to close texture {}", path, var4); } } public AbstractTexture getTexture(ResourceLocation path) { AbstractTexture abstractTexture = (AbstractTexture)this.byPath.get(path); if (abstractTexture != null) { return abstractTexture; } else { SimpleTexture simpleTexture = new SimpleTexture(path); this.registerAndLoad(path, simpleTexture); return simpleTexture; } } @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(); } @Override public CompletableFuture reload( PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, Executor executor, Executor executor2 ) { List list = new ArrayList(); this.byPath.forEach((resourceLocation, abstractTexture) -> { if (abstractTexture instanceof ReloadableTexture reloadableTexture) { list.add(scheduleLoad(resourceManager, resourceLocation, reloadableTexture, executor)); } }); return CompletableFuture.allOf((CompletableFuture[])list.stream().map(TextureManager.PendingReload::newContents).toArray(CompletableFuture[]::new)) .thenCompose(preparationBarrier::wait) .thenAcceptAsync(void_ -> { AddRealmPopupScreen.updateCarouselImages(this.resourceManager); for (TextureManager.PendingReload pendingReload : list) { pendingReload.texture.apply((TextureContents)pendingReload.newContents.join()); } }, executor2); } public 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); } } }); } private static TextureContents loadContents(ResourceManager resourceManager, ResourceLocation textureId, ReloadableTexture texture) throws IOException { try { return texture.loadContents(resourceManager); } catch (FileNotFoundException var4) { if (textureId != INTENTIONAL_MISSING_TEXTURE) { LOGGER.warn("Missing resource {} referenced from {}", texture.resourceId(), textureId); } return TextureContents.createMissing(); } } private static TextureManager.PendingReload scheduleLoad( ResourceManager resourceManager, ResourceLocation textureId, ReloadableTexture texture, Executor executor ) { return new TextureManager.PendingReload(texture, CompletableFuture.supplyAsync(() -> { try { return loadContents(resourceManager, textureId, texture); } catch (IOException var4) { throw new UncheckedIOException(var4); } }, executor)); } @Environment(EnvType.CLIENT) record PendingReload(ReloadableTexture texture, CompletableFuture newContents) { } }