From a4e6522c52762159561038c936106f1156152afd Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Mon, 10 Apr 2023 19:25:01 -0400 Subject: [PATCH] Rewrite model material gathering to use blockstate files --- .../dynamic_resources/ModelBakeryMixin.java | 192 ++++++++++++------ 1 file changed, 131 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java index e81e8733..3bf8c51d 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -6,10 +6,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalNotification; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; +import com.google.gson.*; import com.mojang.datafixers.util.Pair; import com.mojang.math.Transformation; import net.minecraft.Util; @@ -25,6 +22,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; @@ -33,6 +31,8 @@ import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.ForgeRegistryEntry; import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.ModernFix; @@ -56,6 +56,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; @Mixin(ModelBakery.class) public abstract class ModelBakeryMixin { @@ -89,6 +90,8 @@ public abstract class ModelBakeryMixin { @Shadow @Final public static BlockModel BLOCK_ENTITY_MARKER; + @Shadow public abstract Set getSpecialModels(); + private Cache, BakedModel> loadedBakedModels; private Cache loadedModels; @@ -135,78 +138,145 @@ public abstract class ModelBakeryMixin { private UnbakedModel missingModel; - /** - * @author embeddedt - * @reason don't load any models initially, just set up initial data structures - */ - @Overwrite - protected void processLoading(ProfilerFiller arg, int maxMipLevels) { - ModelLoaderRegistry.onModelLoadingStart(); - try { - this.missingModel = this.loadBlockModel(MISSING_MODEL_LOCATION); - } catch (IOException var10) { - ModernFix.LOGGER.error("Error loading missing model, should never happen :(", var10); - throw new RuntimeException(var10); - } - // Gather model materials - Set initialMaterials = new HashSet<>(UNREFERENCED_TEXTURES); - gatherModelMaterials(initialMaterials); - ForgeHooksClient.gatherFluidTextures(initialMaterials); - Map> map = initialMaterials.stream().collect(Collectors.groupingBy(Material::atlasLocation)); - this.atlasPreparations = Maps.newHashMap(); - for(Map.Entry> entry : map.entrySet()) { - TextureAtlas atlas = new TextureAtlas(entry.getKey()); - TextureAtlas.Preparations atlastexture$sheetdata = atlas.prepareToStitch(this.resourceManager, entry.getValue().stream().map(Material::texture), arg, maxMipLevels); - this.atlasPreparations.put(entry.getKey(), Pair.of(atlas, atlastexture$sheetdata)); - } + private Set blockStateFiles; + private Set modelFiles; + + @Redirect(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelBakery;loadBlockModel(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/renderer/block/model/BlockModel;", ordinal = 0)) + private BlockModel captureMissingModel(ModelBakery bakery, ResourceLocation location) throws IOException { + this.missingModel = this.loadBlockModel(location); + this.blockStateFiles = new HashSet<>(); + this.modelFiles = new HashSet<>(); + return (BlockModel)this.missingModel; } /** - * Scan the models folder and try to load, parse, and get materials from as many models as possible. + * @author embeddedt + * @reason don't actually load the model. instead, keep track of if we need to load a blockstate or a model, + * and save the info into the two lists + */ + @Overwrite + private void loadTopLevel(ModelResourceLocation location) { + if(Objects.equals(location.getVariant(), "inventory")) { + modelFiles.add(new ResourceLocation(location.getNamespace(), "item/" + location.getPath())); + } else { + blockStateFiles.add(new ResourceLocation(location.getNamespace(), location.getPath())); + } + } + + @Redirect(method = "processLoading", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/ForgeHooksClient;gatherFluidTextures(Ljava/util/Set;)V", remap = false)) + private void gatherModelTextures(Set materialSet) { + ForgeHooksClient.gatherFluidTextures(materialSet); + gatherModelMaterials(materialSet); + } + + /** + * Load all blockstate JSONs and model files, collect textures. */ private void gatherModelMaterials(Set materialSet) { Stopwatch stopwatch = Stopwatch.createStarted(); - List allModels = new ArrayList<>(this.resourceManager.listResources("models", path -> path.endsWith(".json"))); - // for KubeJS, etc. - allModels.addAll(ResourcePackHandler.getExtraResources(this.resourceManager, "models", path -> path.endsWith(".json"))); - List>> modelBytes = new ArrayList<>(); - for(ResourceLocation fileLocation : allModels) { - modelBytes.add(CompletableFuture.supplyAsync(() -> { + List>> blockStateData = new ArrayList<>(); + for(ResourceLocation blockstate : blockStateFiles) { + blockStateData.add(CompletableFuture.supplyAsync(() -> { + ResourceLocation fileLocation = new ResourceLocation(blockstate.getNamespace(), "blockstates/" + blockstate.getPath() + ".json"); try(Resource resource = this.resourceManager.getResource(fileLocation)) { JsonParser parser = new JsonParser(); - // strip models/ and .json from the name - ResourceLocation model = new ResourceLocation(fileLocation.getNamespace(), fileLocation.getPath().substring(7, fileLocation.getPath().length()-5)); - return Pair.of(model, parser.parse(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))); + return Pair.of(blockstate, parser.parse(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))); } catch(IOException | JsonParseException e) { - ModernFix.LOGGER.error("Error reading model {}: {}", fileLocation, e); - return Pair.of(fileLocation, null); + ModernFix.LOGGER.error("Error reading blockstate {}: {}", blockstate, e); } + return Pair.of(blockstate, null); }, Util.backgroundExecutor())); } - allModels.clear(); - CompletableFuture.allOf(modelBytes.toArray(new CompletableFuture[0])).join(); - Set> errorSet = Sets.newLinkedHashSet(); - Map basicModels = new HashMap<>(); - try { - basicModels.put(MISSING_MODEL_LOCATION, this.loadBlockModel(MISSING_MODEL_LOCATION)); - basicModels.put(new ResourceLocation("builtin/generated"), GENERATION_MARKER); - basicModels.put(new ResourceLocation("builtin/entity"), BLOCK_ENTITY_MARKER); - } catch(IOException e) { - throw new RuntimeException("Exception when populating built-in models", e); - } - for(CompletableFuture> future : modelBytes) { - Pair pair = future.join(); - try { - if(pair.getSecond() != null) { - BlockModel model = ModelLoaderRegistry.ExpandedBlockModelDeserializer.INSTANCE.fromJson(pair.getSecond(), BlockModel.class); - model.name = pair.getFirst().toString(); - basicModels.put(pair.getFirst(), model); + blockStateFiles = null; + CompletableFuture.allOf(blockStateData.toArray(new CompletableFuture[0])).join(); + for(CompletableFuture> result : blockStateData) { + Pair pair = result.join(); + if(pair.getSecond() != null) { + try { + JsonObject obj = pair.getSecond().getAsJsonObject(); + if(obj.has("variants")) { + JsonObject eachVariant = obj.getAsJsonObject("variants"); + for(Map.Entry entry : eachVariant.entrySet()) { + JsonElement variantData = entry.getValue(); + List variantModels; + if(variantData.isJsonArray()) { + variantModels = new ArrayList<>(); + for(JsonElement model : variantData.getAsJsonArray()) { + variantModels.add(model.getAsJsonObject()); + } + } else + variantModels = Collections.singletonList(variantData.getAsJsonObject()); + for(JsonObject variant : variantModels) { + modelFiles.add(new ResourceLocation(variant.get("model").getAsString())); + } + } + + } else { + JsonArray multipartData = obj.get("multipart").getAsJsonArray(); + for(JsonElement element : multipartData) { + JsonObject self = element.getAsJsonObject(); + JsonElement apply = self.get("apply"); + List applyObjects; + if(apply.isJsonArray()) { + applyObjects = new ArrayList<>(); + for(JsonElement e : apply.getAsJsonArray()) { + applyObjects.add(e.getAsJsonObject()); + } + } else + applyObjects = Collections.singletonList(apply.getAsJsonObject()); + for(JsonObject applyEntry : applyObjects) { + modelFiles.add(new ResourceLocation(applyEntry.get("model").getAsString())); + } + } + + } + } catch(RuntimeException e) { + ModernFix.LOGGER.error("Error with blockstate {}: {}", pair.getFirst(), e); } - } catch(Throwable e) { - ModernFix.LOGGER.warn("Unable to parse {}: {}", pair.getFirst(), e); + } } - modelBytes.clear(); + blockStateData = null; + Map basicModels = new HashMap<>(); + basicModels.put(MISSING_MODEL_LOCATION, (BlockModel)missingModel); + basicModels.put(new ResourceLocation("builtin/generated"), GENERATION_MARKER); + basicModels.put(new ResourceLocation("builtin/entity"), BLOCK_ENTITY_MARKER); + Set> errorSet = Sets.newLinkedHashSet(); + while(modelFiles.size() > 0) { + List>> modelBytes = new ArrayList<>(); + for(ResourceLocation model : modelFiles) { + if(basicModels.containsKey(model)) + continue; + ResourceLocation fileLocation = new ResourceLocation(model.getNamespace(), "models/" + model.getPath() + ".json"); + modelBytes.add(CompletableFuture.supplyAsync(() -> { + try(Resource resource = this.resourceManager.getResource(fileLocation)) { + JsonParser parser = new JsonParser(); + return Pair.of(model, parser.parse(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))); + } catch(IOException | JsonParseException e) { + ModernFix.LOGGER.error("Error reading model {}: {}", fileLocation, e); + return Pair.of(fileLocation, null); + } + }, Util.backgroundExecutor())); + } + modelFiles.clear(); + CompletableFuture.allOf(modelBytes.toArray(new CompletableFuture[0])).join(); + for(CompletableFuture> future : modelBytes) { + Pair pair = future.join(); + try { + if(pair.getSecond() != null) { + BlockModel model = ModelLoaderRegistry.ExpandedBlockModelDeserializer.INSTANCE.fromJson(pair.getSecond(), BlockModel.class); + model.name = pair.getFirst().toString(); + modelFiles.addAll(model.getDependencies()); + basicModels.put(pair.getFirst(), model); + continue; + } + } catch(Throwable e) { + ModernFix.LOGGER.warn("Unable to parse {}: {}", pair.getFirst(), e); + } + basicModels.put(pair.getFirst(), (BlockModel)missingModel); + } + } + modelFiles = null; Function modelGetter = loc -> basicModels.getOrDefault(loc, (BlockModel)this.missingModel); for(BlockModel model : basicModels.values()) { materialSet.addAll(model.getMaterials(modelGetter, errorSet));