One big happy family

- Add ModelTree
- Add LoweringVisitor to traverse a MeshTree and emit ModelTree nodes
  and Models
- Provide some default visitor creation methods
- Abstract ModelCache -> ResourceReloadCache
- Abstract ModelHolder -> ResourceReloadHolder
- Add ModelTreeCache to hide lookup cost if it gets extreme
This commit is contained in:
Jozufozu 2024-09-15 14:38:15 -07:00
parent 904933e22e
commit 31b3507d62
13 changed files with 361 additions and 141 deletions

View file

@ -1,40 +1,11 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public final class ModelCache<T> {
private static final Set<ModelCache<?>> ALL = FlwUtil.createWeakHashSet();
private final Function<T, Model> factory;
private final Map<T, Model> map = new ConcurrentHashMap<>();
public final class ModelCache<T> extends ResourceReloadCache<T, Model> {
public ModelCache(Function<T, Model> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
public Model get(T key) {
return map.computeIfAbsent(key, factory);
}
public void clear() {
map.clear();
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ModelCache<?> cache : ALL) {
cache.clear();
}
super(factory);
}
}

View file

@ -1,60 +1,11 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Set;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public final class ModelHolder {
private static final Set<ModelHolder> ALL = FlwUtil.createWeakHashSet();
private final Supplier<Model> factory;
@Nullable
private volatile Model model;
public final class ModelHolder extends ResourceReloadHolder<Model> {
public ModelHolder(Supplier<Model> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
public Model get() {
Model model = this.model;
if (model == null) {
synchronized (this) {
model = this.model;
if (model == null) {
this.model = model = factory.get();
}
}
}
return model;
}
public void clear() {
Model model = this.model;
if (model != null) {
synchronized (this) {
model = this.model;
if (model != null) {
this.model = null;
}
}
}
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ModelHolder holder : ALL) {
holder.clear();
}
super(factory);
}
}

View file

@ -0,0 +1,44 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public class ResourceReloadCache<T, U> implements Function<T, U> {
private static final Set<ResourceReloadCache<?, ?>> ALL = FlwUtil.createWeakHashSet();
private final Function<T, U> factory;
private final Map<T, U> map = new ConcurrentHashMap<>();
public ResourceReloadCache(Function<T, U> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
public final U get(T key) {
return map.computeIfAbsent(key, factory);
}
@Override
public final U apply(T t) {
return get(t);
}
public final void clear() {
map.clear();
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ResourceReloadCache<?, ?> cache : ALL) {
cache.clear();
}
}
}

View file

@ -0,0 +1,60 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Set;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public class ResourceReloadHolder<T> implements Supplier<T> {
private static final Set<ResourceReloadHolder<?>> ALL = FlwUtil.createWeakHashSet();
private final Supplier<T> factory;
@Nullable
private volatile T obj;
public ResourceReloadHolder(Supplier<T> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
@Override
public final T get() {
T obj = this.obj;
if (obj == null) {
synchronized (this) {
obj = this.obj;
if (obj == null) {
this.obj = obj = factory.get();
}
}
}
return obj;
}
public final void clear() {
T obj = this.obj;
if (obj != null) {
synchronized (this) {
obj = this.obj;
if (obj != null) {
this.obj = null;
}
}
}
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ResourceReloadHolder<?> holder : ALL) {
holder.clear();
}
}
}

View file

@ -1,7 +1,6 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.NoSuchElementException;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.ObjIntConsumer;
@ -15,23 +14,16 @@ import org.joml.Vector3fc;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
import dev.engine_room.flywheel.lib.transform.Affine;
import dev.engine_room.flywheel.lib.transform.TransformStack;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
public final class InstanceTree {
private static final ModelCache<Model.ConfiguredMesh> MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material()));
private final MeshTree source;
private final ModelTree source;
@Nullable
private final TransformedInstance instance;
private final InstanceTree[] children;
@ -54,7 +46,7 @@ public final class InstanceTree {
private boolean changed;
private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, InstanceTree[] children) {
private InstanceTree(ModelTree source, @Nullable TransformedInstance instance, InstanceTree[] children) {
this.source = source;
this.instance = instance;
this.children = children;
@ -68,21 +60,16 @@ public final class InstanceTree {
resetPose();
}
private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction<String, Mesh, Model.ConfiguredMesh> meshFinalizerFunc, String path) {
public static InstanceTree create(InstancerProvider provider, ModelTree meshTree) {
InstanceTree[] children = new InstanceTree[meshTree.childCount()];
String pathSlash = path + "/";
for (int i = 0; i < meshTree.childCount(); i++) {
var meshTreeChild = meshTree.child(i);
String name = meshTree.childName(i);
children[i] = create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name);
children[i] = create(provider, meshTree.child(i));
}
Mesh mesh = meshTree.mesh();
Model model = meshTree.model();
TransformedInstance instance;
if (mesh != null) {
Model.ConfiguredMesh configuredMesh = meshFinalizerFunc.apply(path, mesh);
instance = provider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(configuredMesh))
if (model != null) {
instance = provider.instancer(InstanceTypes.TRANSFORMED, model)
.createInstance();
} else {
instance = null;
@ -91,22 +78,6 @@ public final class InstanceTree {
return new InstanceTree(meshTree, instance, children);
}
public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction<String, Mesh, Model.ConfiguredMesh> meshFinalizerFunc) {
return create(provider, meshTree, meshFinalizerFunc, "");
}
public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, BiFunction<String, Mesh, Model.ConfiguredMesh> meshFinalizerFunc) {
return create(provider, MeshTree.of(layer), meshFinalizerFunc);
}
public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, Material material) {
return create(provider, meshTree, (path, mesh) -> new Model.ConfiguredMesh(material, mesh));
}
public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, Material material) {
return create(provider, MeshTree.of(layer), material);
}
@Nullable
public TransformedInstance instance() {
return instance;

View file

@ -0,0 +1,93 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.ArrayList;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.model.RetexturedMesh;
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
/**
* A tree walking visitor that lowers a MeshTree to a ModelTree.
*/
public interface LoweringVisitor {
static ModelTree leaf(Model model) {
return leaf(model, PartPose.ZERO);
}
static ModelTree leaf(Model model, PartPose initialPose) {
return ModelTree.create(model, initialPose, new ModelTree[0], new String[0]);
}
static LoweringVisitor materialApplyingVisitor(Material material) {
return (path, mesh) -> new SingleMeshModel(mesh, material);
}
static LoweringVisitor retexturingVisitor(Material material, TextureAtlasSprite sprite) {
return (path, mesh) -> new SingleMeshModel(new RetexturedMesh(mesh, sprite), material);
}
static String append(String path, String child) {
if (path.isEmpty()) {
return child;
}
return path + "/" + child;
}
/**
* Walk the given MeshTree, lowering its Mesh to a Model, and lowering all children using the given visitor.
*
* @param path The absolute path to the MeshTree node.
* @param meshTree The MeshTree to walk.
* @param loweringVisitor The visitor to use to lower the Mesh and MeshTree nodes.
* @return The lowered ModelTree.
*/
static ModelTree walk(String path, MeshTree meshTree, LoweringVisitor loweringVisitor) {
Model out = null;
if (meshTree.mesh() != null) {
out = loweringVisitor.visit(path, meshTree.mesh());
}
ArrayList<ModelTree> children = new ArrayList<>();
ArrayList<String> childNames = new ArrayList<>();
for (int i = 0; i < meshTree.childCount(); i++) {
var child = loweringVisitor.visit(append(path, meshTree.childName(i)), meshTree.child(i));
if (child != null) {
children.add(child);
childNames.add(meshTree.childName(i));
}
}
return ModelTree.create(out, meshTree.initialPose(), children.toArray(new ModelTree[0]), childNames.toArray(new String[0]));
}
/**
* Visit the given Mesh, converting it to a Model.
*
* @param path The absolute path to the MeshTree node containing the Mesh.
* @param mesh The Mesh to lower.
* @return The lowered Model, or null if the Model should be omitted.
*/
@Nullable Model visit(String path, Mesh mesh);
/**
* Visit the given MeshTree, converting it to a ModelTree.
*
* @param path The absolute path to the MeshTree node.
* @param meshTree The MeshTree to lower.
* @return The lowered ModelTree, or null if the ModelTree should be omitted.
*/
@Nullable
default ModelTree visit(String path, MeshTree meshTree) {
return walk(path, meshTree, this);
}
}

View file

@ -0,0 +1,107 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.model.Model;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.PartPose;
public class ModelTree {
private static final Map<ModelTreeKey, ModelTree> CACHE = new ConcurrentHashMap<>();
@Nullable
private final Model model;
private final PartPose initialPose;
private final ModelTree[] children;
private final String[] childNames;
private ModelTree(@Nullable Model model, PartPose initialPose, ModelTree[] children, String[] childNames) {
this.model = model;
this.initialPose = initialPose;
this.children = children;
this.childNames = childNames;
}
/**
* Create and memoize a ModelTree root.
*
* <p>This method is intended for use by Visuals.
*
* @param modelLayerLocation The model location to lower.
* @param loweringVisitor The visitor to use to lower the model.
* @return The cached ModelTree root.
*/
public static ModelTree of(ModelLayerLocation modelLayerLocation, LoweringVisitor loweringVisitor) {
return CACHE.computeIfAbsent(new ModelTreeKey(modelLayerLocation, loweringVisitor), k -> {
var meshTree = MeshTree.of(k.modelLayerLocation());
var out = k.loweringVisitor()
.visit("", meshTree);
if (out == null) {
// Should this be an error, or a missing model?
return ModelTree.create(null, PartPose.ZERO, new ModelTree[0], new String[0]);
}
return out;
});
}
/**
* Create a new ModelTree node.
*
* <p>This method is intended for use by {@link LoweringVisitor} implementations.
*
* @param model The model to associate with this node, or null if this node does not render.
* @param initialPose The initial pose of this node.
* @param children The children of this node.
* @param childNames The names of the children of this node.
* @return A new ModelTree node.
* @throws IllegalArgumentException if children and childNames have different lengths.
*/
public static ModelTree create(@Nullable Model model, PartPose initialPose, ModelTree[] children, String[] childNames) {
if (children.length != childNames.length) {
throw new IllegalArgumentException("children and childNames must have the same length (%s != %s)".formatted(children.length, childNames.length));
}
return new ModelTree(model, initialPose, children, childNames);
}
public int childCount() {
return children.length;
}
public ModelTree child(int index) {
return children[index];
}
public String childName(int index) {
return childNames[index];
}
public PartPose initialPose() {
return initialPose;
}
@Nullable
public Model model() {
return model;
}
public int childIndex(String name) {
return Arrays.binarySearch(childNames, name);
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
CACHE.clear();
}
private record ModelTreeKey(ModelLayerLocation modelLayerLocation, LoweringVisitor loweringVisitor) {
}
}

View file

@ -0,0 +1,15 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.function.Function;
import dev.engine_room.flywheel.lib.model.ResourceReloadCache;
/**
* If the lookup to create your model tree directly through {@link ModelTree#of} is particularly expensive,
* you can memoize the arguments here to hide the cost.
*/
public class ModelTreeCache<T> extends ResourceReloadCache<T, ModelTree> {
public ModelTreeCache(Function<T, ModelTree> factory) {
super(factory);
}
}

View file

@ -7,16 +7,16 @@ import org.joml.Matrix4fc;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.RetexturedMesh;
import dev.engine_room.flywheel.lib.model.ResourceReloadHolder;
import dev.engine_room.flywheel.lib.model.part.InstanceTree;
import dev.engine_room.flywheel.lib.model.part.LoweringVisitor;
import dev.engine_room.flywheel.lib.model.part.ModelTree;
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.blockentity.BellRenderer;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.entity.BellBlockEntity;
@ -26,6 +26,9 @@ public class BellVisual extends AbstractBlockEntityVisual<BellBlockEntity> imple
.mipmap(false)
.build();
// Need to hold the visitor in a ResourceReloadHolder to ensure we have a valid sprite.
private static final ResourceReloadHolder<LoweringVisitor> VISITOR = new ResourceReloadHolder<>(() -> LoweringVisitor.retexturingVisitor(MATERIAL, BellRenderer.BELL_RESOURCE_LOCATION.sprite()));
private final InstanceTree instances;
private final InstanceTree bellBody;
@ -36,10 +39,7 @@ public class BellVisual extends AbstractBlockEntityVisual<BellBlockEntity> imple
public BellVisual(VisualizationContext ctx, BellBlockEntity blockEntity, float partialTick) {
super(ctx, blockEntity, partialTick);
TextureAtlasSprite sprite = BellRenderer.BELL_RESOURCE_LOCATION.sprite();
instances = InstanceTree.create(instancerProvider(), ModelLayers.BELL, (path, mesh) -> {
return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite));
});
instances = InstanceTree.create(instancerProvider(), ModelTree.of(ModelLayers.BELL, VISITOR.get()));
bellBody = instances.childOrThrow("bell_body");
BlockPos visualPos = getVisualPosition();

View file

@ -4,6 +4,7 @@ import java.util.Calendar;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
@ -15,8 +16,11 @@ import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.material.CutoutShaders;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.RetexturedMesh;
import dev.engine_room.flywheel.lib.model.ResourceReloadCache;
import dev.engine_room.flywheel.lib.model.part.InstanceTree;
import dev.engine_room.flywheel.lib.model.part.LoweringVisitor;
import dev.engine_room.flywheel.lib.model.part.ModelTree;
import dev.engine_room.flywheel.lib.transform.TransformStack;
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
import it.unimi.dsi.fastutil.floats.Float2FloatFunction;
@ -53,6 +57,8 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT);
}
private static final Function<TextureAtlasSprite, LoweringVisitor> VISITOR = new ResourceReloadCache<>(s -> LoweringVisitor.retexturingVisitor(MATERIAL, s));
@Nullable
private final InstanceTree instances;
@Nullable
@ -77,10 +83,7 @@ public class ChestVisual<T extends BlockEntity & LidBlockEntity> extends Abstrac
if (block instanceof AbstractChestBlock<?> chestBlock) {
ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE;
TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite();
instances = InstanceTree.create(instancerProvider(), LAYER_LOCATIONS.get(chestType), (path, mesh) -> {
return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite));
});
instances = InstanceTree.create(instancerProvider(), ModelTree.of(LAYER_LOCATIONS.get(chestType), VISITOR.apply(sprite)));
lid = instances.childOrThrow("lid");
lock = instances.childOrThrow("lock");

View file

@ -14,6 +14,8 @@ import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.Models;
import dev.engine_room.flywheel.lib.model.part.InstanceTree;
import dev.engine_room.flywheel.lib.model.part.LoweringVisitor;
import dev.engine_room.flywheel.lib.model.part.ModelTree;
import dev.engine_room.flywheel.lib.visual.ComponentEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual;
@ -34,6 +36,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends ComponentEntityV
.texture(TEXTURE)
.mipmap(false)
.build();
private static final LoweringVisitor VISITOR = LoweringVisitor.materialApplyingVisitor(MATERIAL);
private final InstanceTree instances;
@Nullable
@ -47,7 +50,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends ComponentEntityV
public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelLayerLocation layerLocation) {
super(ctx, entity, partialTick);
instances = InstanceTree.create(instancerProvider(), layerLocation, MATERIAL);
instances = InstanceTree.create(instancerProvider(), ModelTree.of(layerLocation, VISITOR));
blockState = entity.getDisplayBlockState();
contents = createContentsInstance();

View file

@ -9,10 +9,11 @@ import dev.engine_room.flywheel.backend.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler;
import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.ModelHolder;
import dev.engine_room.flywheel.lib.model.ResourceReloadCache;
import dev.engine_room.flywheel.lib.model.ResourceReloadHolder;
import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler;
import dev.engine_room.flywheel.lib.model.part.MeshTree;
import dev.engine_room.flywheel.lib.model.part.ModelTree;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
@ -68,12 +69,11 @@ public final class FlywheelFabric implements ClientModInitializer {
}
private static void setupLib() {
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) ->
ModelCache.onEndClientResourceReload());
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) ->
ModelHolder.onEndClientResourceReload());
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadCache.onEndClientResourceReload());
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadHolder.onEndClientResourceReload());
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) ->
MeshTree.onEndClientResourceReload());
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ModelTree.onEndClientResourceReload());
ModelLoadingPlugin.register(ctx -> {
ctx.addModels(PartialModelEventHandler.onRegisterAdditional());

View file

@ -10,10 +10,11 @@ import dev.engine_room.flywheel.backend.LightSmoothnessArgument;
import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler;
import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.ModelHolder;
import dev.engine_room.flywheel.lib.model.ResourceReloadCache;
import dev.engine_room.flywheel.lib.model.ResourceReloadHolder;
import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler;
import dev.engine_room.flywheel.lib.model.part.MeshTree;
import dev.engine_room.flywheel.lib.model.part.ModelTree;
import dev.engine_room.flywheel.lib.util.LevelAttached;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
@ -110,9 +111,10 @@ public final class FlywheelForge {
private static void registerLibEventListeners(IEventBus forgeEventBus, IEventBus modEventBus) {
forgeEventBus.addListener((LevelEvent.Unload e) -> LevelAttached.invalidateLevel(e.getLevel()));
modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload());
modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelHolder.onEndClientResourceReload());
modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadCache.onEndClientResourceReload());
modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadHolder.onEndClientResourceReload());
modEventBus.addListener((EndClientResourceReloadEvent e) -> MeshTree.onEndClientResourceReload());
modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelTree.onEndClientResourceReload());
modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional);
modEventBus.addListener(PartialModelEventHandler::onBakingCompleted);