Skip to content

Commit

Permalink
One big happy family
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Jozufozu committed Sep 15, 2024
1 parent 904933e commit 31b3507
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,23 +14,16 @@
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;
Expand All @@ -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;
Expand All @@ -68,21 +60,16 @@ private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, In
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;
Expand All @@ -91,22 +78,6 @@ private static InstanceTree create(InstancerProvider provider, MeshTree meshTree
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 31b3507

Please sign in to comment.