/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.HashMultimap * com.google.common.collect.Multimap * com.google.common.collect.Multimaps * com.google.common.collect.Sets * com.google.common.collect.Sets$SetView * com.mojang.datafixers.util.Pair * com.mojang.logging.LogUtils * it.unimi.dsi.fastutil.objects.Object2IntMap * it.unimi.dsi.fastutil.objects.Object2IntMaps * org.jspecify.annotations.Nullable * org.slf4j.Logger */ package net.minecraft.client.resources.model; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMaps; import java.io.BufferedReader; import java.io.Reader; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; import java.util.function.Supplier; import java.util.stream.Collectors; import net.minecraft.client.color.block.BlockColors; import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.client.renderer.PlayerSkinRenderCache; import net.minecraft.client.renderer.SpecialBlockModelRenderer; import net.minecraft.client.renderer.block.BlockModelShaper; import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.block.model.BlockStateModel; import net.minecraft.client.renderer.block.model.ItemModelGenerator; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.special.SpecialModelRenderer; import net.minecraft.client.renderer.texture.SpriteLoader; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.AtlasManager; import net.minecraft.client.resources.model.BlockStateModelLoader; import net.minecraft.client.resources.model.ClientItemInfoLoader; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.MissingBlockModel; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelDebugName; import net.minecraft.client.resources.model.ModelDiscovery; import net.minecraft.client.resources.model.ModelGroupCollector; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.client.resources.model.SpriteGetter; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.AtlasIds; import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.Util; import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.Zone; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; public class ModelManager implements PreparableReloadListener { public static final Identifier BLOCK_OR_ITEM = Identifier.withDefaultNamespace("block_or_item"); private static final Logger LOGGER = LogUtils.getLogger(); private static final FileToIdConverter MODEL_LISTER = FileToIdConverter.json("models"); private Map bakedItemStackModels = Map.of(); private Map itemProperties = Map.of(); private final AtlasManager atlasManager; private final PlayerSkinRenderCache playerSkinRenderCache; private final BlockModelShaper blockModelShaper; private final BlockColors blockColors; private EntityModelSet entityModelSet = EntityModelSet.EMPTY; private SpecialBlockModelRenderer specialBlockModelRenderer = SpecialBlockModelRenderer.EMPTY; private ModelBakery.MissingModels missingModels; private Object2IntMap modelGroups = Object2IntMaps.emptyMap(); public ModelManager(BlockColors blockColors, AtlasManager atlasManager, PlayerSkinRenderCache playerSkinRenderCache) { this.blockColors = blockColors; this.atlasManager = atlasManager; this.playerSkinRenderCache = playerSkinRenderCache; this.blockModelShaper = new BlockModelShaper(this); } public BlockStateModel getMissingBlockStateModel() { return this.missingModels.block(); } public ItemModel getItemModel(Identifier id) { return this.bakedItemStackModels.getOrDefault(id, this.missingModels.item()); } public ClientItem.Properties getItemProperties(Identifier id) { return this.itemProperties.getOrDefault(id, ClientItem.Properties.DEFAULT); } public BlockModelShaper getBlockModelShaper() { return this.blockModelShaper; } @Override public final CompletableFuture reload(PreparableReloadListener.SharedState currentReload, Executor taskExecutor, PreparableReloadListener.PreparationBarrier preparationBarrier, Executor reloadExecutor) { ResourceManager manager = currentReload.resourceManager(); CompletableFuture entityModelSet = CompletableFuture.supplyAsync(EntityModelSet::vanilla, taskExecutor); CompletionStage specialBlockModelRenderer = entityModelSet.thenApplyAsync(entityModels -> SpecialBlockModelRenderer.vanilla(new SpecialModelRenderer.BakingContext.Simple((EntityModelSet)entityModels, this.atlasManager, this.playerSkinRenderCache)), taskExecutor); CompletableFuture> modelCache = ModelManager.loadBlockModels(manager, taskExecutor); CompletableFuture blockStateModels = BlockStateModelLoader.loadBlockStates(manager, taskExecutor); CompletableFuture itemStackModels = ClientItemInfoLoader.scheduleLoad(manager, taskExecutor); CompletionStage modelDiscovery = CompletableFuture.allOf(modelCache, blockStateModels, itemStackModels).thenApplyAsync(unused -> ModelManager.discoverModelDependencies((Map)modelCache.join(), (BlockStateModelLoader.LoadedModels)blockStateModels.join(), (ClientItemInfoLoader.LoadedClientInfos)itemStackModels.join()), taskExecutor); CompletionStage modelGroups = blockStateModels.thenApplyAsync(models -> ModelManager.buildModelGroups(this.blockColors, models), taskExecutor); AtlasManager.PendingStitchResults pendingStitches = currentReload.get(AtlasManager.PENDING_STITCH); CompletableFuture pendingBlockAtlasSprites = pendingStitches.get(AtlasIds.BLOCKS); CompletableFuture pendingItemAtlasSprites = pendingStitches.get(AtlasIds.ITEMS); return ((CompletableFuture)((CompletableFuture)CompletableFuture.allOf(new CompletableFuture[]{pendingBlockAtlasSprites, pendingItemAtlasSprites, modelDiscovery, modelGroups, blockStateModels, itemStackModels, entityModelSet, specialBlockModelRenderer, modelCache}).thenComposeAsync(arg_0 -> this.lambda$reload$4(pendingBlockAtlasSprites, pendingItemAtlasSprites, (CompletableFuture)modelDiscovery, (CompletableFuture)modelGroups, modelCache, entityModelSet, blockStateModels, itemStackModels, (CompletableFuture)specialBlockModelRenderer, taskExecutor, arg_0), taskExecutor)).thenCompose(preparationBarrier::wait)).thenAcceptAsync(this::apply, reloadExecutor); } private static CompletableFuture> loadBlockModels(ResourceManager manager, Executor executor) { return CompletableFuture.supplyAsync(() -> MODEL_LISTER.listMatchingResources(manager), executor).thenCompose(resources -> { ArrayList> result = new ArrayList>(resources.size()); for (Map.Entry resource : resources.entrySet()) { result.add(CompletableFuture.supplyAsync(() -> { Pair pair; block8: { Identifier modelId = MODEL_LISTER.fileToId((Identifier)resource.getKey()); @Nullable BufferedReader reader = ((Resource)resource.getValue()).openAsReader(); try { pair = Pair.of((Object)modelId, (Object)BlockModel.fromStream(reader)); if (reader == null) break block8; } catch (Throwable throwable) { try { if (reader != null) { try { ((Reader)reader).close(); } catch (Throwable throwable2) { throwable.addSuppressed(throwable2); } } throw throwable; } catch (Exception e) { LOGGER.error("Failed to load model {}", resource.getKey(), (Object)e); return null; } } ((Reader)reader).close(); } return pair; }, executor)); } return Util.sequence(result).thenApply(pairs -> pairs.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond))); }); } private static ResolvedModels discoverModelDependencies(Map allModels, BlockStateModelLoader.LoadedModels blockStateModels, ClientItemInfoLoader.LoadedClientInfos itemInfos) { try (Zone ignored = Profiler.get().zone("dependencies");){ ModelDiscovery result = new ModelDiscovery(allModels, MissingBlockModel.missingModel()); result.addSpecialModel(ItemModelGenerator.GENERATED_ITEM_MODEL_ID, new ItemModelGenerator()); blockStateModels.models().values().forEach(result::addRoot); itemInfos.contents().values().forEach(info -> result.addRoot(info.model())); ResolvedModels resolvedModels = new ResolvedModels(result.missingModel(), result.resolve()); return resolvedModels; } } private static CompletableFuture loadModels(final SpriteLoader.Preparations blockAtlas, final SpriteLoader.Preparations itemAtlas, ModelBakery bakery, Object2IntMap modelGroups, EntityModelSet entityModelSet, SpecialBlockModelRenderer specialBlockModelRenderer, Executor taskExecutor) { final Multimap missingMaterials = Multimaps.synchronizedMultimap((Multimap)HashMultimap.create()); final Multimap missingReferences = Multimaps.synchronizedMultimap((Multimap)HashMultimap.create()); return bakery.bakeModels(new SpriteGetter(){ private final TextureAtlasSprite blockMissing; private final TextureAtlasSprite itemMissing; { this.blockMissing = blockAtlas.missing(); this.itemMissing = itemAtlas.missing(); } @Override public TextureAtlasSprite get(Material material, ModelDebugName name) { TextureAtlasSprite result; Identifier atlasId = material.atlasLocation(); boolean itemOrBlock = atlasId.equals(BLOCK_OR_ITEM); boolean onlyItem = atlasId.equals(TextureAtlas.LOCATION_ITEMS); boolean onlyBlock = atlasId.equals(TextureAtlas.LOCATION_BLOCKS); if ((itemOrBlock || onlyItem) && (result = itemAtlas.getSprite(material.texture())) != null) { return result; } if ((itemOrBlock || onlyBlock) && (result = blockAtlas.getSprite(material.texture())) != null) { return result; } missingMaterials.put((Object)name.debugName(), (Object)material); return onlyItem ? this.itemMissing : this.blockMissing; } @Override public TextureAtlasSprite reportMissingReference(String reference, ModelDebugName responsibleModel) { missingReferences.put((Object)responsibleModel.debugName(), (Object)reference); return this.blockMissing; } }, taskExecutor).thenApply(bakingResult -> { missingMaterials.asMap().forEach((location, materials) -> LOGGER.warn("Missing textures in model {}:\n{}", location, (Object)materials.stream().sorted(Material.COMPARATOR).map(m -> " " + String.valueOf(m.atlasLocation()) + ":" + String.valueOf(m.texture())).collect(Collectors.joining("\n")))); missingReferences.asMap().forEach((location, references) -> LOGGER.warn("Missing texture references in model {}:\n{}", location, (Object)references.stream().sorted().map(reference -> " " + reference).collect(Collectors.joining("\n")))); Map modelByStateCache = ModelManager.createBlockStateToModelDispatch(bakingResult.blockStateModels(), bakingResult.missingModels().block()); return new ReloadState((ModelBakery.BakingResult)bakingResult, modelGroups, modelByStateCache, entityModelSet, specialBlockModelRenderer); }); } private static Map createBlockStateToModelDispatch(Map bakedModels, BlockStateModel missingModel) { try (Zone ignored = Profiler.get().zone("block state dispatch");){ IdentityHashMap modelByStateCache = new IdentityHashMap(bakedModels); for (Block block : BuiltInRegistries.BLOCK) { block.getStateDefinition().getPossibleStates().forEach(state -> { if (bakedModels.putIfAbsent((BlockState)state, missingModel) == null) { LOGGER.warn("Missing model for variant: '{}'", state); } }); } IdentityHashMap identityHashMap = modelByStateCache; return identityHashMap; } } private static Object2IntMap buildModelGroups(BlockColors blockColors, BlockStateModelLoader.LoadedModels blockStateModels) { try (Zone ignored = Profiler.get().zone("block groups");){ Object2IntMap object2IntMap = ModelGroupCollector.build(blockColors, blockStateModels); return object2IntMap; } } private void apply(ReloadState preparations) { ModelBakery.BakingResult bakedModels = preparations.bakedModels; this.bakedItemStackModels = bakedModels.itemStackModels(); this.itemProperties = bakedModels.itemProperties(); this.modelGroups = preparations.modelGroups; this.missingModels = bakedModels.missingModels(); this.blockModelShaper.replaceCache(preparations.modelCache); this.specialBlockModelRenderer = preparations.specialBlockModelRenderer; this.entityModelSet = preparations.entityModelSet; } public boolean requiresRender(BlockState oldState, BlockState newState) { int newModelGroup; if (oldState == newState) { return false; } int oldModelGroup = this.modelGroups.getInt((Object)oldState); if (oldModelGroup != -1 && oldModelGroup == (newModelGroup = this.modelGroups.getInt((Object)newState))) { FluidState newFluidState; FluidState oldFluidState = oldState.getFluidState(); return oldFluidState != (newFluidState = newState.getFluidState()); } return true; } public Supplier specialBlockModelRenderer() { return () -> this.specialBlockModelRenderer; } public Supplier entityModels() { return () -> this.entityModelSet; } private /* synthetic */ CompletionStage lambda$reload$4(CompletableFuture pendingBlockAtlasSprites, CompletableFuture pendingItemAtlasSprites, CompletableFuture modelDiscovery, CompletableFuture modelGroups, CompletableFuture modelCache, CompletableFuture entityModelSet, CompletableFuture blockStateModels, CompletableFuture itemStackModels, CompletableFuture specialBlockModelRenderer, Executor taskExecutor, Void unused) { SpriteLoader.Preparations blockAtlasSprites = (SpriteLoader.Preparations)pendingBlockAtlasSprites.join(); SpriteLoader.Preparations itemAtlasSprites = (SpriteLoader.Preparations)pendingItemAtlasSprites.join(); ResolvedModels resolvedModels = (ResolvedModels)modelDiscovery.join(); Object2IntMap groups = (Object2IntMap)modelGroups.join(); Sets.SetView unreferencedModels = Sets.difference(((Map)modelCache.join()).keySet(), resolvedModels.models.keySet()); if (!unreferencedModels.isEmpty()) { LOGGER.debug("Unreferenced models: \n{}", (Object)unreferencedModels.stream().sorted().map(modelId -> "\t" + String.valueOf(modelId) + "\n").collect(Collectors.joining())); } ModelBakery bakery = new ModelBakery((EntityModelSet)entityModelSet.join(), this.atlasManager, this.playerSkinRenderCache, ((BlockStateModelLoader.LoadedModels)blockStateModels.join()).models(), ((ClientItemInfoLoader.LoadedClientInfos)itemStackModels.join()).contents(), resolvedModels.models(), resolvedModels.missing()); return ModelManager.loadModels(blockAtlasSprites, itemAtlasSprites, bakery, (Object2IntMap)groups, (EntityModelSet)entityModelSet.join(), (SpecialBlockModelRenderer)specialBlockModelRenderer.join(), taskExecutor); } private record ResolvedModels(ResolvedModel missing, Map models) { } private record ReloadState(ModelBakery.BakingResult bakedModels, Object2IntMap modelGroups, Map modelCache, EntityModelSet entityModelSet, SpecialBlockModelRenderer specialBlockModelRenderer) { } }