Stripping instances

- Remove #removeNow and #getWorldPosition from Instance
- Add #distanceSquared for use in update limiting
- Refactor DistanceUpdateLimiter to directly accept distance squared
- Remove proper name from backend
- Misc. cleanup
  - ifs without braces
  - some method names
This commit is contained in:
Jozufozu 2023-04-04 17:17:06 -07:00
parent 632240abf0
commit 7326bdd3c2
21 changed files with 103 additions and 120 deletions

View file

@ -115,7 +115,7 @@ public class Flywheel {
VanillaInstances.init();
CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManager::getBackendDescriptor);
CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManager::getBackendNameForCrashReport);
// https://github.com/Jozufozu/Flywheel/issues/69
// Weird issue with accessor loading.

View file

@ -11,15 +11,24 @@ import net.minecraft.network.chat.Component;
public interface Backend {
static IdRegistry<Backend> REGISTRY = IdRegistryImpl.create();
// TODO: remove and use ID instead? Currently this is only used for the crash log string.
String getProperName();
Component getEngineMessage();
/**
* Get a message to display to the user when the engine is enabled.
*/
Component engineMessage();
/**
* Create a new engine instance.
*/
Engine createEngine();
/**
* Get a fallback backend in case this backend is not supported.
*/
Backend findFallback();
/**
* Check if this backend is supported.
*/
boolean isSupported();
@Nullable Pipeline pipelineShader();

View file

@ -16,8 +16,8 @@ public final class BackendManager {
/**
* Get a string describing the current backend.
*/
public static String getBackendDescriptor() {
return BackendManagerImpl.getBackendDescriptor();
public static String getBackendNameForCrashReport() {
return BackendManagerImpl.getBackendNameForCrashReport();
}
public static boolean isOn() {

View file

@ -2,13 +2,10 @@ package com.jozufozu.flywheel.api.instance;
import org.joml.FrustumIntersection;
import net.minecraft.core.BlockPos;
/**
* A general interface providing information about any type of thing that could use Flywheel's instanced rendering.
*/
public interface Instance {
BlockPos getWorldPosition();
/**
* Initialize parts here.
@ -39,17 +36,25 @@ public interface Instance {
/**
* Check this instance against a frustum.<p>
* An implementor may choose to return a constant to skip the frustum check.
*
* @param frustum A frustum intersection tester for the current frame.
* @return {@code true} if this instance should be considered for updates.
*/
boolean checkFrustum(FrustumIntersection frustum);
/**
* Calculate the distance squared between this instance and the given <em>world</em> position.
*
* @param x The x coordinate.
* @param y The y coordinate.
* @param z The z coordinate.
* @return The distance squared between this instance and the given position.
*/
double distanceSquared(double x, double y, double z);
/**
* Free any acquired resources.
*/
void delete();
// TODO
@Deprecated
void removeNow();
}

View file

@ -65,7 +65,7 @@ public class EffectInstanceManager extends InstanceManager<Effect> {
this.tickableInstances.removeAll(instances);
this.dynamicInstances.removeAll(instances);
for (Instance instance : instances) {
instance.removeNow();
instance.delete();
}
}

View file

@ -21,8 +21,6 @@ import com.jozufozu.flywheel.backend.instancing.ratelimit.NonLimiter;
import com.jozufozu.flywheel.backend.instancing.storage.Storage;
import com.jozufozu.flywheel.config.FlwConfig;
import net.minecraft.core.BlockPos;
public abstract class InstanceManager<T> {
private final Set<T> queuedAdditions = new HashSet<>(64);
private final Set<T> queuedUpdates = new HashSet<>(64);
@ -144,7 +142,7 @@ public abstract class InstanceManager<T> {
public void delete() {
for (Instance instance : getStorage().getAllInstances()) {
instance.removeNow();
instance.delete();
}
}
@ -191,28 +189,19 @@ public abstract class InstanceManager<T> {
tickLimiter.tick();
processQueuedUpdates();
// integer camera pos as a micro-optimization
int cX = (int) cameraX;
int cY = (int) cameraY;
int cZ = (int) cameraZ;
var instances = getStorage().getInstancesForTicking();
distributeWork(executor, instances, instance -> tickInstance(instance, cX, cY, cZ));
distributeWork(executor, instances, instance -> tickInstance(instance, cameraX, cameraY, cameraZ));
}
protected void tickInstance(TickableInstance instance, int cX, int cY, int cZ) {
protected void tickInstance(TickableInstance instance, double cX, double cY, double cZ) {
if (!instance.decreaseTickRateWithDistance()) {
instance.tick();
return;
}
BlockPos pos = instance.getWorldPosition();
var dsq = instance.distanceSquared(cX, cY, cZ);
int dX = pos.getX() - cX;
int dY = pos.getY() - cY;
int dZ = pos.getZ() - cZ;
if (!tickLimiter.shouldUpdate(dX, dY, dZ)) {
if (!tickLimiter.shouldUpdate(dsq)) {
return;
}
@ -223,11 +212,11 @@ public abstract class InstanceManager<T> {
frameLimiter.tick();
processQueuedAdditions();
// integer camera pos
BlockPos cameraIntPos = context.camera().getBlockPosition();
int cX = cameraIntPos.getX();
int cY = cameraIntPos.getY();
int cZ = cameraIntPos.getZ();
var cameraPos = context.camera()
.getPosition();
double cX = cameraPos.x;
double cY = cameraPos.y;
double cZ = cameraPos.z;
FrustumIntersection culler = context.culler();
var instances = getStorage().getInstancesForUpdate();
@ -251,18 +240,13 @@ public abstract class InstanceManager<T> {
}
}
protected void updateInstance(DynamicInstance instance, FrustumIntersection frustum, int cX, int cY, int cZ) {
protected void updateInstance(DynamicInstance instance, FrustumIntersection frustum, double cX, double cY, double cZ) {
if (!instance.decreaseFramerateWithDistance()) {
instance.beginFrame();
return;
}
BlockPos worldPos = instance.getWorldPosition();
int dX = worldPos.getX() - cX;
int dY = worldPos.getY() - cY;
int dZ = worldPos.getZ() - cZ;
if (!frameLimiter.shouldUpdate(dX, dY, dZ)) {
if (!frameLimiter.shouldUpdate(instance.distanceSquared(cX, cY, cZ))) {
return;
}

View file

@ -14,12 +14,12 @@ public class BandedPrimeLimiter implements DistanceUpdateLimiter {
}
@Override
public boolean shouldUpdate(int dX, int dY, int dZ) {
return (tickCount % getUpdateDivisor(dX, dY, dZ)) == 0;
public boolean shouldUpdate(double distanceSquared) {
return (tickCount % getUpdateDivisor(distanceSquared)) == 0;
}
protected int getUpdateDivisor(int dX, int dY, int dZ) {
int dSq = dX * dX + dY * dY + dZ * dZ;
protected int getUpdateDivisor(double distanceSquared) {
int dSq = Mth.ceil(distanceSquared);
int i = (dSq / 2048);

View file

@ -11,10 +11,9 @@ public interface DistanceUpdateLimiter {
/**
* 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.
*
* @param distanceSquared
* @return {@code true} if the object should be updated, {@code false} otherwise.
*/
boolean shouldUpdate(int dX, int dY, int dZ);
boolean shouldUpdate(double distanceSquared);
}

View file

@ -6,7 +6,7 @@ public class NonLimiter implements DistanceUpdateLimiter {
}
@Override
public boolean shouldUpdate(int dX, int dY, int dZ) {
public boolean shouldUpdate(double distanceSquared) {
return true;
}
}

View file

@ -46,7 +46,6 @@ public abstract class One2OneStorage<T> extends AbstractStorage<T> {
instance.delete();
dynamicInstances.remove(instance);
tickableInstances.remove(instance);
instance.removeNow();
}
@Override

View file

@ -53,7 +53,7 @@ public class FlwCommands {
return 0;
}
Component message = backend.getEngineMessage();
Component message = backend.engineMessage();
player.displayClientMessage(message, false);
}
return Command.SINGLE_SUCCESS;
@ -65,7 +65,7 @@ public class FlwCommands {
Backend backend = context.getArgument("id", Backend.class);
value.set(Backend.REGISTRY.getIdOrThrow(backend).toString());
Component message = backend.getEngineMessage();
Component message = backend.engineMessage();
player.displayClientMessage(message, false);
BackendUtil.reloadWorldRenderers();

View file

@ -18,8 +18,15 @@ public final class BackendManagerImpl {
return backend;
}
public static String getBackendDescriptor() {
return backend == null ? "Uninitialized" : backend.getProperName();
public static String getBackendNameForCrashReport() {
if (backend == null) {
return "Uninitialized";
}
var backendId = Backend.REGISTRY.getId(backend);
if (backendId == null) {
return "Unregistered";
}
return backendId.toString();
}
public static boolean isOn() {

View file

@ -15,7 +15,6 @@ import net.minecraft.network.chat.TextComponent;
public class Backends {
public static final Backend OFF = SimpleBackend.builder()
.properName("Off")
.engineMessage(new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED))
.engineSupplier(() -> {
throw new IllegalStateException("Cannot create engine when backend is off.");
@ -28,7 +27,6 @@ public class Backends {
* Use a thread pool to buffer instances in parallel on the CPU.
*/
public static final Backend BATCHING = SimpleBackend.builder()
.properName("Parallel Batching")
.engineMessage(new TextComponent("Using Batching Engine").withStyle(ChatFormatting.GREEN))
.engineSupplier(BatchingEngine::new)
.fallback(() -> Backends.OFF)
@ -39,7 +37,6 @@ public class Backends {
* Use GPU instancing to render everything.
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.properName("GL33 Instanced Arrays")
.engineMessage(new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN))
.engineSupplier(() -> new InstancingEngine(Contexts.WORLD, 100 * 100))
.fallback(() -> Backends.BATCHING)
@ -52,7 +49,6 @@ public class Backends {
* Use Compute shaders to cull instances.
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.properName("GL46 Compute Culling")
.engineMessage(new TextComponent("Using Indirect Engine").withStyle(ChatFormatting.GREEN))
.engineSupplier(() -> new IndirectEngine(Contexts.WORLD, 100 * 100))
.fallback(() -> Backends.INSTANCING)

View file

@ -13,15 +13,13 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
public class SimpleBackend implements Backend {
private final String properName;
private final Component engineMessage;
private final Supplier<Engine> engineSupplier;
private final Supplier<Backend> fallback;
private final BooleanSupplier isSupported;
private final Pipeline pipelineShader;
public SimpleBackend(String properName, Component engineMessage, Supplier<Engine> engineSupplier, Supplier<Backend> fallback, BooleanSupplier isSupported, @Nullable Pipeline pipelineShader) {
this.properName = properName;
public SimpleBackend(Component engineMessage, Supplier<Engine> engineSupplier, Supplier<Backend> fallback, BooleanSupplier isSupported, @Nullable Pipeline pipelineShader) {
this.engineMessage = engineMessage;
this.engineSupplier = engineSupplier;
this.fallback = fallback;
@ -34,12 +32,7 @@ public class SimpleBackend implements Backend {
}
@Override
public String getProperName() {
return properName;
}
@Override
public Component getEngineMessage() {
public Component engineMessage() {
return engineMessage;
}
@ -69,18 +62,12 @@ public class SimpleBackend implements Backend {
}
public static class Builder {
private String properName;
private Component engineMessage;
private Supplier<Engine> engineSupplier;
private Supplier<Backend> fallback;
private BooleanSupplier isSupported;
private Pipeline pipelineShader;
public Builder properName(String properName) {
this.properName = properName;
return this;
}
public Builder engineMessage(Component engineMessage) {
this.engineMessage = engineMessage;
return this;
@ -107,7 +94,7 @@ public class SimpleBackend implements Backend {
}
public Backend register(ResourceLocation id) {
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(properName, engineMessage, engineSupplier, fallback, isSupported, pipelineShader));
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(engineMessage, engineSupplier, fallback, isSupported, pipelineShader));
}
}
}

View file

@ -63,7 +63,7 @@ public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends
/**
* Just before {@link #update()} would be called, {@code shouldReset()} is checked.
* If this function returns {@code true}, then this instance will be {@link #remove removed},
* If this function returns {@code true}, then this instance will be {@link #delete removed},
* and another instance will be constructed to replace it. This allows for more sane resource
* acquisition compared to trying to update everything within the lifetime of an instance.
*
@ -85,11 +85,6 @@ public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends
return pos.subtract(instancerManager.getOriginCoordinate());
}
@Override
public BlockPos getWorldPosition() {
return pos;
}
@Override
public ImmutableBox getVolume() {
return MutableBox.from(pos);
@ -99,4 +94,9 @@ public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends
public boolean checkFrustum(FrustumIntersection frustum) {
return frustum.testAab(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
}
@Override
public double distanceSquared(double x, double y, double z) {
return pos.distToCenterSqr(x, y, z);
}
}

View file

@ -11,7 +11,6 @@ import com.jozufozu.flywheel.lib.box.MutableBox;
import com.jozufozu.flywheel.lib.light.TickingLightListener;
import com.mojang.math.Vector3f;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
@ -88,20 +87,17 @@ public abstract class AbstractEntityInstance<E extends Entity> extends AbstractI
Vec3i origin = instancerManager.getOriginCoordinate();
return new Vector3f(
(float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - origin.getX()),
(float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - origin.getY()),
(float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - origin.getZ())
);
}
@Override
public BlockPos getWorldPosition() {
return entity.blockPosition();
(float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - origin.getY()), (float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - origin.getZ()));
}
@Override
public boolean checkFrustum(FrustumIntersection frustum) {
AABB aabb = entity.getBoundingBox();
return frustum.testAab((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ,
(float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
return frustum.testAab((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ, (float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
}
@Override
public double distanceSquared(double x, double y, double z) {
return entity.distanceToSqr(x, y, z);
}
}

View file

@ -86,8 +86,4 @@ public abstract class AbstractInstance implements Instance, LightListener {
.setSkyLight(sky));
}
@Override
public final void removeNow() {
LightUpdater.get(level).removeListener(this);
}
}

View file

@ -41,15 +41,18 @@ public class BellInstance extends AbstractBlockEntityInstance<BellBlockEntity> i
@Override
public void beginFrame() {
float ringTime = (float)blockEntity.ticks + AnimationTickHolder.getPartialTicks();
float ringTime = (float) blockEntity.ticks + AnimationTickHolder.getPartialTicks();
if (ringTime == lastRingTime) return;
if (ringTime == lastRingTime) {
return;
}
lastRingTime = ringTime;
if (blockEntity.shaking) {
float angle = Mth.sin(ringTime / (float) Math.PI) / (4.0F + ringTime / 3.0F);
Vector3f ringAxis = blockEntity.clickDirection.getCounterClockWise().step();
Vector3f ringAxis = blockEntity.clickDirection.getCounterClockWise()
.step();
bell.setRotation(ringAxis.rotation(angle));
} else {
@ -59,7 +62,7 @@ public class BellInstance extends AbstractBlockEntityInstance<BellBlockEntity> i
@Override
public void updateLight() {
relight(getWorldPosition(), bell);
relight(pos, bell);
}
@Override

View file

@ -71,11 +71,9 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
body.setRotation(baseRotation);
DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> wrapper = chestBlock.combine(blockState, level, getWorldPosition(), true);
DoubleBlockCombiner.NeighborCombineResult<? extends ChestBlockEntity> wrapper = chestBlock.combine(blockState, level, pos, true);
this.lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity));
} else {
baseRotation = Quaternion.ONE;
lidProgress = $ -> 0f;
@ -86,7 +84,9 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
public void beginFrame() {
float progress = lidProgress.get(AnimationTickHolder.getPartialTicks());
if (lastProgress == progress) return;
if (lastProgress == progress) {
return;
}
lastProgress = progress;
@ -108,7 +108,7 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
@Override
public void updateLight() {
relight(getWorldPosition(), body, lid);
relight(pos, body, lid);
}
@Override
@ -123,13 +123,11 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
}
private OrientedPart baseInstance() {
return instancerManager.getInstancer(StructTypes.ORIENTED, BASE.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedPart lidInstance() {
return instancerManager.getInstancer(StructTypes.TRANSFORMED, LID.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}

View file

@ -59,7 +59,7 @@ public class MinecartInstance<T extends AbstractMinecart> extends AbstractEntity
contents.delete();
contents = getContents();
if (contents != null) {
relight(getWorldPosition(), contents);
relight(entity.blockPosition(), contents);
}
}
}
@ -143,16 +143,19 @@ public class MinecartInstance<T extends AbstractMinecart> extends AbstractEntity
@Override
public void updateLight() {
if (contents == null)
relight(getWorldPosition(), body);
else
relight(getWorldPosition(), body, contents);
if (contents == null) {
relight(entity.blockPosition(), body);
} else {
relight(entity.blockPosition(), body, contents);
}
}
@Override
protected void _delete() {
body.delete();
if (contents != null) contents.delete();
if (contents != null) {
contents.delete();
}
}
private TransformedPart getContents() {
@ -165,8 +168,9 @@ public class MinecartInstance<T extends AbstractMinecart> extends AbstractEntity
}
active = true;
if (shape == RenderShape.INVISIBLE)
if (shape == RenderShape.INVISIBLE) {
return null;
}
return instancerManager.getInstancer(StructTypes.TRANSFORMED, Models.block(blockState), RenderStage.AFTER_ENTITIES)
.createInstance();

View file

@ -256,11 +256,6 @@ public class ExampleEffect implements Effect {
.setSkyLight(15);
}
@Override
public BlockPos getWorldPosition() {
return blockPos;
}
@Override
protected void _delete() {
instance.delete();
@ -304,5 +299,10 @@ public class ExampleEffect implements Effect {
public boolean checkFrustum(FrustumIntersection frustum) {
return true;
}
@Override
public double distanceSquared(double x, double y, double z) {
return 0;
}
}
}