mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-27 07:26:48 +01:00
Toggleable update limiting
- Extract update limiting behavior to interface - Move original impl to BandedPrimeLimiter - Add dummy NonLimiter impl - Add command/config to toggle update limiting - Refactor InstanceManager to be more consistent between frame updates and tick updates - Bump version - 0.6.1
This commit is contained in:
parent
b403ca3d2b
commit
9219fef20a
13 changed files with 162 additions and 54 deletions
|
@ -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
|
||||
|
|
|
@ -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<T> implements InstancingEngine.OriginShiftListener {
|
||||
|
||||
|
@ -33,8 +34,8 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
|
|||
protected final Object2ObjectOpenHashMap<T, TickableInstance> tickableInstances;
|
||||
protected final Object2ObjectOpenHashMap<T, DynamicInstance> 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<T> 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<T> implements InstancingEngine.OriginShift
|
|||
* </p>
|
||||
*/
|
||||
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<T> 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<T> 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<T> implements InstancingEngine.OriginShift
|
|||
List<DynamicInstance> 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<T> 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<T> 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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -154,9 +154,9 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
|
|||
@Override
|
||||
public void addDebugInfo(List<String> 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
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Consumer<BooleanDirective>> receiver;
|
||||
final Consumer<BooleanDirective> receiver;
|
||||
|
||||
BooleanConfig(Supplier<Consumer<BooleanDirective>> receiver) {
|
||||
BooleanConfig(Consumer<BooleanDirective> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,17 +20,17 @@ public class BooleanConfigCommand {
|
|||
public ArgumentBuilder<CommandSourceStack, ?> 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;
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -13,15 +13,20 @@ public class FlwCommands {
|
|||
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
|
||||
|
||||
dispatcher.register(Commands.literal("flywheel")
|
||||
.then(debugCommand())
|
||||
.then(debugNormalsCommand())
|
||||
.then(backendCommand())
|
||||
.then(limitUpdatesCommand())
|
||||
);
|
||||
}
|
||||
|
||||
private static ArgumentBuilder<CommandSourceStack, ?> debugCommand() {
|
||||
private static ArgumentBuilder<CommandSourceStack, ?> debugNormalsCommand() {
|
||||
return new BooleanConfigCommand("debugNormals", BooleanConfig.NORMAL_OVERLAY).register();
|
||||
}
|
||||
|
||||
private static ArgumentBuilder<CommandSourceStack, ?> limitUpdatesCommand() {
|
||||
return new BooleanConfigCommand("limitUpdates", BooleanConfig.LIMIT_UPDATES).register();
|
||||
}
|
||||
|
||||
private static ArgumentBuilder<CommandSourceStack, ?> backendCommand() {
|
||||
return Commands.literal("backend")
|
||||
.executes(context -> {
|
||||
|
|
|
@ -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<FlwEngine> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public class ForgeEvents {
|
|||
|
||||
if (Minecraft.getInstance().options.renderDebug) {
|
||||
|
||||
InstancedRenderDispatcher.getDebugString(event.getLeft());
|
||||
InstancedRenderDispatcher.getDebugString(event.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue