diff --git a/gradle.properties b/gradle.properties index 0476c1c99..ef7e50b34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs = -Xmx3G org.gradle.daemon = false # mod version info -mod_version = 0.6.0 +mod_version = 0.6.1 mc_update_version = 1.18 minecraft_version = 1.18.1 forge_version = 39.0.59 diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java index 38936f656..871572127 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java @@ -14,13 +14,14 @@ import com.jozufozu.flywheel.api.instance.DynamicInstance; import com.jozufozu.flywheel.api.instance.TickableInstance; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; +import com.jozufozu.flywheel.backend.instancing.ratelimit.DistanceUpdateLimiter; +import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.light.LightUpdater; import com.mojang.math.Vector3f; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.client.Camera; import net.minecraft.core.BlockPos; -import net.minecraft.util.Mth; public abstract class InstanceManager implements InstancingEngine.OriginShiftListener { @@ -33,8 +34,8 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift protected final Object2ObjectOpenHashMap tickableInstances; protected final Object2ObjectOpenHashMap dynamicInstances; - protected int frame; - protected int tick; + protected DistanceUpdateLimiter frame; + protected DistanceUpdateLimiter tick; public InstanceManager(MaterialManager materialManager) { this.materialManager = materialManager; @@ -44,6 +45,10 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift this.dynamicInstances = new Object2ObjectOpenHashMap<>(); this.tickableInstances = new Object2ObjectOpenHashMap<>(); + + FlwConfig config = FlwConfig.get(); + frame = config.createUpdateLimiter(); + tick = config.createUpdateLimiter(); } /** @@ -86,7 +91,7 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift *

*/ public void tick(TaskEngine taskEngine, double cameraX, double cameraY, double cameraZ) { - tick++; + tick.tick(); processQueuedUpdates(); // integer camera pos as a micro-optimization @@ -112,7 +117,7 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift } } - private void tickInstance(int cX, int cY, int cZ, TickableInstance instance) { + protected void tickInstance(int cX, int cY, int cZ, TickableInstance instance) { if (!instance.decreaseTickRateWithDistance()) { instance.tick(); return; @@ -124,11 +129,11 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift int dY = pos.getY() - cY; int dZ = pos.getZ() - cZ; - if ((tick % getUpdateDivisor(dX, dY, dZ)) == 0) instance.tick(); + if (tick.shouldUpdate(dX, dY, dZ)) instance.tick(); } public void beginFrame(TaskEngine taskEngine, Camera info) { - frame++; + frame.tick(); processQueuedAdditions(); Vector3f look = info.getLookVector(); @@ -151,8 +156,7 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift List sub = instances.subList(start, end); taskEngine.submit(() -> { for (DynamicInstance dyn : sub) { - if (!dyn.decreaseFramerateWithDistance() || shouldFrameUpdate(dyn.getWorldPosition(), lookX, lookY, lookZ, cX, cY, cZ)) - dyn.beginFrame(); + updateInstance(dyn, lookX, lookY, lookZ, cX, cY, cZ); } }); @@ -160,6 +164,28 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift } } + protected void updateInstance(DynamicInstance dyn, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) { + if (!dyn.decreaseFramerateWithDistance()) { + dyn.beginFrame(); + return; + } + + BlockPos worldPos = dyn.getWorldPosition(); + int dX = worldPos.getX() - cX; + int dY = worldPos.getY() - cY; + int dZ = worldPos.getZ() - cZ; + + // is it more than 2 blocks behind the camera? + int dist = 2; + float dot = (dX + lookX * dist) * lookX + (dY + lookY * dist) * lookY + (dZ + lookZ * dist) * lookZ; + if (dot < 0) { + return; + } + + if (frame.shouldUpdate(dX, dY, dZ)) + dyn.beginFrame(); + } + public void add(T obj) { if (!Backend.isOn()) return; @@ -268,29 +294,6 @@ public abstract class InstanceManager implements InstancingEngine.OriginShift } } - protected boolean shouldFrameUpdate(BlockPos worldPos, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) { - int dX = worldPos.getX() - cX; - int dY = worldPos.getY() - cY; - int dZ = worldPos.getZ() - cZ; - - // is it more than 2 blocks behind the camera? - int dist = 2; - float dot = (dX + lookX * dist) * lookX + (dY + lookY * dist) * lookY + (dZ + lookZ * dist) * lookZ; - if (dot < 0) return false; - - return (frame % getUpdateDivisor(dX, dY, dZ)) == 0; - } - - // 1 followed by the prime numbers - private static final int[] divisorSequence = new int[] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 }; - protected int getUpdateDivisor(int dX, int dY, int dZ) { - int dSq = dX * dX + dY * dY + dZ * dZ; - - int i = (dSq / 2048); - - return divisorSequence[Mth.clamp(i, 0, divisorSequence.length - 1)]; - } - protected void addInternal(T obj) { if (!Backend.isOn()) return; diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java index e1a4eb904..ebf6a8e56 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java @@ -4,6 +4,8 @@ import java.util.List; import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.backend.Backend; +import com.jozufozu.flywheel.config.BooleanConfig; +import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.event.RenderLayerEvent; @@ -123,6 +125,7 @@ public class InstancedRenderDispatcher { if (Backend.isOn()) { InstanceWorld instanceWorld = instanceWorlds.get(Minecraft.getInstance().level); + debug.add("Update limiting: " + BooleanConfig.boolToText(FlwConfig.get().limitUpdates()).getString()); debug.add("B: " + instanceWorld.blockEntityInstanceManager.getObjectCount() + ", E: " + instanceWorld.entityInstanceManager.getObjectCount()); instanceWorld.engine.addDebugInfo(debug); } else { diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java index 42cca0e13..e3efc8a71 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java @@ -154,9 +154,9 @@ public class InstancingEngine

implements Engine { @Override public void addDebugInfo(List info) { info.add("GL33 Instanced Arrays"); - info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ()); info.add("Instances: " + getGroupsToRender(null).mapToInt(InstancedMaterialGroup::getInstanceCount).sum()); info.add("Vertices: " + getGroupsToRender(null).mapToInt(InstancedMaterialGroup::getVertexCount).sum()); + info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ()); } @FunctionalInterface diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/BandedPrimeLimiter.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/BandedPrimeLimiter.java new file mode 100644 index 000000000..278f5ff1e --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/BandedPrimeLimiter.java @@ -0,0 +1,28 @@ +package com.jozufozu.flywheel.backend.instancing.ratelimit; + +import net.minecraft.util.Mth; + +public class BandedPrimeLimiter implements DistanceUpdateLimiter { + // 1 followed by the prime numbers + private static final int[] divisorSequence = new int[] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 }; + + private int tickCount = 0; + + @Override + public void tick() { + tickCount++; + } + + @Override + public boolean shouldUpdate(int dX, int dY, int dZ) { + return (tickCount % getUpdateDivisor(dX, dY, dZ)) == 0; + } + + protected int getUpdateDivisor(int dX, int dY, int dZ) { + int dSq = dX * dX + dY * dY + dZ * dZ; + + int i = (dSq / 2048); + + return divisorSequence[Mth.clamp(i, 0, divisorSequence.length - 1)]; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/DistanceUpdateLimiter.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/DistanceUpdateLimiter.java new file mode 100644 index 000000000..7fb3ceff6 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/DistanceUpdateLimiter.java @@ -0,0 +1,20 @@ +package com.jozufozu.flywheel.backend.instancing.ratelimit; + +/** + * Interface for rate-limiting updates based on an object's distance from the camera. + */ +public interface DistanceUpdateLimiter { + /** + * Call this before every update. + */ + void tick(); + + /** + * Check to see if an object at the given position relative to the camera should be updated. + * @param dX The X distance from the camera. + * @param dY The Y distance from the camera. + * @param dZ The Z distance from the camera. + * @return {@code true} if the object should be updated, {@code false} otherwise. + */ + boolean shouldUpdate(int dX, int dY, int dZ); +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/NonLimiter.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/NonLimiter.java new file mode 100644 index 000000000..ea5afb230 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/ratelimit/NonLimiter.java @@ -0,0 +1,13 @@ +package com.jozufozu.flywheel.backend.instancing.ratelimit; + +public class NonLimiter implements DistanceUpdateLimiter { + @Override + public void tick() { + // noop + } + + @Override + public boolean shouldUpdate(int dX, int dY, int dZ) { + return true; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/config/BooleanConfig.java b/src/main/java/com/jozufozu/flywheel/config/BooleanConfig.java index 4014fe5bd..2cdf578b6 100644 --- a/src/main/java/com/jozufozu/flywheel/config/BooleanConfig.java +++ b/src/main/java/com/jozufozu/flywheel/config/BooleanConfig.java @@ -1,7 +1,8 @@ package com.jozufozu.flywheel.config; import java.util.function.Consumer; -import java.util.function.Supplier; + +import com.jozufozu.flywheel.backend.Backend; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -9,20 +10,36 @@ import net.minecraft.client.player.LocalPlayer; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.TextComponent; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; public enum BooleanConfig { - NORMAL_OVERLAY(() -> BooleanConfig::normalOverlay), - ; + NORMAL_OVERLAY(BooleanConfig::normalOverlay), + LIMIT_UPDATES(BooleanConfig::limitUpdates); - final Supplier> receiver; + final Consumer receiver; - BooleanConfig(Supplier> receiver) { + BooleanConfig(Consumer receiver) { this.receiver = receiver; } - @OnlyIn(Dist.CLIENT) + private static void limitUpdates(BooleanDirective booleanDirective) { + LocalPlayer player = Minecraft.getInstance().player; + if (player == null || booleanDirective == null) return; + + if (booleanDirective == BooleanDirective.DISPLAY) { + Component text = new TextComponent("Update limiting is currently: ").append(boolToText(FlwConfig.get().limitUpdates())); + player.displayClientMessage(text, false); + return; + } + + FlwConfig.get().client.limitUpdates.set(booleanDirective.get()); + + Component text = boolToText(FlwConfig.get().limitUpdates()).append(new TextComponent(" update limiting.").withStyle(ChatFormatting.WHITE)); + + player.displayClientMessage(text, false); + + Backend.reloadWorldRenderers(); + } + private static void normalOverlay(BooleanDirective state) { LocalPlayer player = Minecraft.getInstance().player; if (player == null || state == null) return; @@ -40,7 +57,7 @@ public enum BooleanConfig { player.displayClientMessage(text, false); } - private static MutableComponent boolToText(boolean b) { + public static MutableComponent boolToText(boolean b) { return b ? new TextComponent("enabled").withStyle(ChatFormatting.DARK_GREEN) : new TextComponent("disabled").withStyle(ChatFormatting.RED); } } diff --git a/src/main/java/com/jozufozu/flywheel/config/BooleanConfigCommand.java b/src/main/java/com/jozufozu/flywheel/config/BooleanConfigCommand.java index 9c690af30..d3be59e10 100644 --- a/src/main/java/com/jozufozu/flywheel/config/BooleanConfigCommand.java +++ b/src/main/java/com/jozufozu/flywheel/config/BooleanConfigCommand.java @@ -20,17 +20,17 @@ public class BooleanConfigCommand { public ArgumentBuilder register() { return Commands.literal(name) .executes(context -> { - value.receiver.get().accept(BooleanDirective.DISPLAY); + value.receiver.accept(BooleanDirective.DISPLAY); return Command.SINGLE_SUCCESS; }) .then(Commands.literal("on") .executes(context -> { - value.receiver.get().accept(BooleanDirective.TRUE); + value.receiver.accept(BooleanDirective.TRUE); return Command.SINGLE_SUCCESS; })) .then(Commands.literal("off") .executes(context -> { - value.receiver.get().accept(BooleanDirective.FALSE); + value.receiver.accept(BooleanDirective.FALSE); return Command.SINGLE_SUCCESS; })); } diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java index 5d945cc26..018e8efce 100644 --- a/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java +++ b/src/main/java/com/jozufozu/flywheel/config/FlwCommands.java @@ -13,15 +13,20 @@ public class FlwCommands { CommandDispatcher dispatcher = event.getDispatcher(); dispatcher.register(Commands.literal("flywheel") - .then(debugCommand()) - .then(backendCommand()) + .then(debugNormalsCommand()) + .then(backendCommand()) + .then(limitUpdatesCommand()) ); } - private static ArgumentBuilder debugCommand() { + private static ArgumentBuilder debugNormalsCommand() { return new BooleanConfigCommand("debugNormals", BooleanConfig.NORMAL_OVERLAY).register(); } + private static ArgumentBuilder limitUpdatesCommand() { + return new BooleanConfigCommand("limitUpdates", BooleanConfig.LIMIT_UPDATES).register(); + } + private static ArgumentBuilder backendCommand() { return Commands.literal("backend") .executes(context -> { diff --git a/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java b/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java index a7457e70d..468252db0 100644 --- a/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java +++ b/src/main/java/com/jozufozu/flywheel/config/FlwConfig.java @@ -2,6 +2,10 @@ package com.jozufozu.flywheel.config; import org.apache.commons.lang3.tuple.Pair; +import com.jozufozu.flywheel.backend.instancing.ratelimit.BandedPrimeLimiter; +import com.jozufozu.flywheel.backend.instancing.ratelimit.DistanceUpdateLimiter; +import com.jozufozu.flywheel.backend.instancing.ratelimit.NonLimiter; + import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec.BooleanValue; import net.minecraftforge.fml.ModLoadingContext; @@ -34,12 +38,25 @@ public class FlwConfig { return client.debugNormals.get(); } + public boolean limitUpdates() { + return client.limitUpdates.get(); + } + + public DistanceUpdateLimiter createUpdateLimiter() { + if (limitUpdates()) { + return new BandedPrimeLimiter(); + } else { + return new NonLimiter(); + } + } + public static void init() { } public static class ClientConfig { public final ForgeConfigSpec.EnumValue engine; public final BooleanValue debugNormals; + public final BooleanValue limitUpdates; public ClientConfig(ForgeConfigSpec.Builder builder) { @@ -48,6 +65,9 @@ public class FlwConfig { debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal") .define("debugNormals", false); + + limitUpdates = builder.comment("Enable or disable instance update limiting with distance.") + .define("limitUpdates", true); } } } diff --git a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java index edfcd2c0c..3d8abed17 100644 --- a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingInstanceManager.java @@ -1,10 +1,9 @@ package com.jozufozu.flywheel.core.crumbling; import com.jozufozu.flywheel.api.MaterialManager; +import com.jozufozu.flywheel.api.instance.DynamicInstance; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager; -import net.minecraft.core.BlockPos; - public class CrumblingInstanceManager extends BlockEntityInstanceManager { public CrumblingInstanceManager(MaterialManager materialManager) { @@ -12,7 +11,7 @@ public class CrumblingInstanceManager extends BlockEntityInstanceManager { } @Override - protected boolean shouldFrameUpdate(BlockPos worldPos, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) { - return true; + protected void updateInstance(DynamicInstance dyn, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) { + dyn.beginFrame(); } } diff --git a/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java b/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java index 64aec0c2a..056966700 100644 --- a/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java +++ b/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java @@ -21,7 +21,7 @@ public class ForgeEvents { if (Minecraft.getInstance().options.renderDebug) { - InstancedRenderDispatcher.getDebugString(event.getLeft()); + InstancedRenderDispatcher.getDebugString(event.getRight()); } }