diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index 17c91358c..cf8f22037 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -1,10 +1,6 @@ package dev.engine_room.flywheel.lib.model.part; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.NoSuchElementException; -import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.ObjIntConsumer; @@ -32,11 +28,12 @@ import net.minecraft.client.model.geom.PartPose; public final class InstanceTree { private static final ModelCache MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material())); + private final MeshTree source; @Nullable private final TransformedInstance instance; private final PartPose initialPose; @Unmodifiable - private final Map children; + private final InstanceTree[] children; private final Quaternionf rotation = new Quaternionf(); @@ -52,7 +49,8 @@ public final class InstanceTree { public boolean visible = true; public boolean skipDraw; - private InstanceTree(@Nullable TransformedInstance instance, PartPose initialPose, @Unmodifiable Map children) { + private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, PartPose initialPose, InstanceTree[] children) { + this.source = source; this.instance = instance; this.initialPose = initialPose; this.children = children; @@ -60,12 +58,15 @@ public final class InstanceTree { } private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc, String path) { - Map children = new HashMap<>(); + InstanceTree[] children = new InstanceTree[meshTree.childCount()]; String pathSlash = path + "/"; - meshTree.children().forEach((name, meshTreeChild) -> { - children.put(name, InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name)); - }); + for (int i = 0; i < meshTree.childCount(); i++) { + var meshTreeChild = meshTree.child(i); + String name = meshTree.childName(i); + children[i] = InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name); + + } Mesh mesh = meshTree.mesh(); TransformedInstance instance; @@ -77,7 +78,7 @@ public final class InstanceTree { instance = null; } - return new InstanceTree(instance, meshTree.initialPose(), Collections.unmodifiableMap(children)); + return new InstanceTree(meshTree, instance, meshTree.initialPose(), children); } public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc) { @@ -105,18 +106,17 @@ public final class InstanceTree { return initialPose; } - @Unmodifiable - public Map children() { - return children; - } - - public boolean hasChild(String name) { - return children.containsKey(name); + @Nullable + public InstanceTree child(int index) { + if (index < 0 || index >= children.length) { + return null; + } + return children[index]; } @Nullable public InstanceTree child(String name) { - return children.get(name); + return child(source.childIndex(name)); } public InstanceTree childOrThrow(String name) { @@ -133,7 +133,7 @@ public final class InstanceTree { if (instance != null) { consumer.accept(instance); } - for (InstanceTree child : children.values()) { + for (InstanceTree child : children) { child.traverse(consumer); } } @@ -143,7 +143,7 @@ public final class InstanceTree { if (instance != null) { consumer.accept(instance, i); } - for (InstanceTree child : children.values()) { + for (InstanceTree child : children) { child.traverse(i, consumer); } } @@ -153,7 +153,7 @@ public final class InstanceTree { if (instance != null) { consumer.accept(instance, i, j); } - for (InstanceTree child : children.values()) { + for (InstanceTree child : children) { child.traverse(i, j, consumer); } } @@ -171,16 +171,6 @@ public final class InstanceTree { } public void updateInstances(PoseStack poseStack) { - // Need to use an anonymous class so it can reference this. - updateInstancesInner(poseStack, new Walker() { - @Override - public void accept(InstanceTree child) { - child.updateInstancesInner(poseStack, this); - } - }); - } - - private void updateInstancesInner(PoseStack poseStack, Walker walker) { if (visible) { poseStack.pushPose(); translateAndRotate(poseStack); @@ -192,7 +182,9 @@ public final class InstanceTree { // Use the bare HashMap.forEach because .values() always allocates a new collection. // We also don't want to store an array of children because that would statically use a lot more memory. - children.forEach(walker); + for (InstanceTree child : children) { + child.updateInstances(poseStack); + } poseStack.popPose(); } @@ -294,8 +286,9 @@ public final class InstanceTree { if (instance != null) { instance.delete(); } - children.values() - .forEach(InstanceTree::delete); + for (InstanceTree child : children) { + child.delete(); + } } @ApiStatus.Experimental @@ -303,14 +296,4 @@ public final class InstanceTree { public interface ObjIntIntConsumer { void accept(T t, int i, int j); } - - // Helper interface for writing walking classes. - private interface Walker extends BiConsumer { - void accept(InstanceTree child); - - @Override - default void accept(String name, InstanceTree child) { - accept(child); - } - } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java index d097266d3..60c0e8eba 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.lib.model.part; -import java.util.Collections; -import java.util.HashMap; +import java.util.Arrays; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; @@ -9,7 +8,6 @@ import java.util.function.Consumer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; import com.mojang.blaze3d.vertex.PoseStack; @@ -35,12 +33,13 @@ public final class MeshTree { @Nullable private final Mesh mesh; private final PartPose initialPose; - @Unmodifiable - private final Map children; + private final MeshTree[] children; + private final String[] childKeys; - private MeshTree(@Nullable Mesh mesh, PartPose initialPose, @Unmodifiable Map children) { + private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, String[] childKeys) { this.mesh = mesh; this.initialPose = initialPose; + this.childKeys = childKeys; this.children = children; } @@ -58,13 +57,17 @@ public final class MeshTree { private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) { var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); - Map children = new HashMap<>(); - modelPartChildren.forEach((name, modelPartChild) -> { - children.put(name, convert(modelPartChild, objects)); - }); + // Freeze the ordering here. Maybe we want to sort this? + String[] childKeys = modelPartChildren.keySet() + .toArray(new String[0]); - return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), Collections.unmodifiableMap(children)); + MeshTree[] children = new MeshTree[modelPartChildren.size()]; + for (int i = 0; i < childKeys.length; i++) { + children[i] = convert(modelPartChildren.get(childKeys[i]), objects); + } + + return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), children, childKeys); } @Nullable @@ -91,18 +94,35 @@ public final class MeshTree { return initialPose; } - @Unmodifiable - public Map children() { - return children; + public int childCount() { + return children.length; + } + + public MeshTree child(int index) { + return children[index]; + } + + public String childName(int index) { + return childKeys[index]; + } + + public int childIndex(String name) { + return Arrays.binarySearch(childKeys, name); } public boolean hasChild(String name) { - return children.containsKey(name); + return childIndex(name) >= 0; } @Nullable public MeshTree child(String name) { - return children.get(name); + int index = childIndex(name); + + if (index < 0) { + return null; + } + + return child(index); } public MeshTree childOrThrow(String name) { @@ -119,7 +139,7 @@ public final class MeshTree { if (mesh != null) { consumer.accept(mesh); } - for (MeshTree child : children.values()) { + for (MeshTree child : children) { child.traverse(consumer); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java index 0c0fefebe..fb73e087a 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java @@ -24,7 +24,7 @@ public class RecyclingPoseStack extends PoseStack { super.pushPose(); } else { var last = last(); - var recycle = recycleBin.pop(); + var recycle = recycleBin.removeLast(); recycle.pose() .set(last.pose()); recycle.normal() @@ -36,7 +36,7 @@ public class RecyclingPoseStack extends PoseStack { @Override public void popPose() { - recycleBin.push(FlwLibLink.INSTANCE.getPoseStack(this) + recycleBin.addLast(FlwLibLink.INSTANCE.getPoseStack(this) .removeLast()); } }