From 7e65eaa00d81f01b70dc8619d71866e28f5bd12f Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 8 Sep 2021 15:48:49 -0700 Subject: [PATCH] Separate LightVolume and GPULightVolume - LightVolumes now can act as a light cache with configurable size - More GridAlignedBB changes - Remove ILightUpdateListeners - Simplify pulley rendering using LightVolume --- .../backend/instancing/AbstractInstance.java | 5 +- .../backend/instancing/InstanceManager.java | 5 +- .../instancing/tile/TileEntityInstance.java | 3 +- .../jozufozu/flywheel/event/ForgeEvents.java | 2 +- .../flywheel/light/GPULightVolume.java | 160 ++++++++ .../flywheel/light/GridAlignedBB.java | 286 ++++++++++---- .../flywheel/light/ILightUpdateListener.java | 4 +- .../{ReadOnlyBox.java => ImmutableBox.java} | 28 +- .../jozufozu/flywheel/light/LightUpdater.java | 17 +- .../jozufozu/flywheel/light/LightVolume.java | 354 +++++++----------- .../light/WeakContainmentMultiMap.java | 19 +- 11 files changed, 568 insertions(+), 315 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java rename src/main/java/com/jozufozu/flywheel/light/{ReadOnlyBox.java => ImmutableBox.java} (81%) diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java index 766d63c32..e25e10693 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java @@ -6,11 +6,10 @@ import java.util.stream.Stream; import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager; import com.jozufozu.flywheel.backend.material.MaterialManager; import com.jozufozu.flywheel.core.materials.IFlatLight; -import com.jozufozu.flywheel.light.GridAlignedBB; import com.jozufozu.flywheel.light.ILightUpdateListener; import com.jozufozu.flywheel.light.LightProvider; import com.jozufozu.flywheel.light.ListenerStatus; -import com.jozufozu.flywheel.light.ReadOnlyBox; +import com.jozufozu.flywheel.light.ImmutableBox; import net.minecraft.util.math.BlockPos; import net.minecraft.world.LightType; @@ -74,7 +73,7 @@ public abstract class AbstractInstance implements IInstance, ILightUpdateListene } @Override - public void onLightUpdate(LightProvider world, LightType type, ReadOnlyBox changed) { + public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changed) { updateLight(); } 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 3e8d307c1..bb600858c 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java @@ -278,6 +278,8 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh instances.remove(obj); dynamicInstances.remove(obj); tickableInstances.remove(obj); + LightUpdater.get(instance.world) + .removeListener(instance); } @Nullable @@ -286,7 +288,8 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh if (renderer != null) { renderer.updateLight(); - LightUpdater.get(renderer.world).addListener(renderer); + LightUpdater.get(renderer.world) + .addListener(renderer); instances.put(obj, renderer); if (renderer instanceof IDynamicInstance) dynamicInstances.put(obj, (IDynamicInstance) renderer); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileEntityInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileEntityInstance.java index 16fc5aede..45e308032 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileEntityInstance.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileEntityInstance.java @@ -9,6 +9,7 @@ import com.jozufozu.flywheel.core.Materials; import com.jozufozu.flywheel.core.materials.ModelData; import com.jozufozu.flywheel.core.materials.OrientedData; import com.jozufozu.flywheel.light.GridAlignedBB; +import com.jozufozu.flywheel.light.ImmutableBox; import net.minecraft.block.BlockState; import net.minecraft.tileentity.TileEntity; @@ -84,7 +85,7 @@ public abstract class TileEntityInstance extends AbstractI } @Override - public GridAlignedBB getVolume() { + public ImmutableBox getVolume() { return GridAlignedBB.from(pos); } } diff --git a/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java b/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java index 869c52da4..1b2b37961 100644 --- a/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java +++ b/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java @@ -48,7 +48,7 @@ public class ForgeEvents { } @SubscribeEvent - public static void rwle(TickEvent.ClientTickEvent e) { + public static void tickLight(TickEvent.ClientTickEvent e) { if (e.phase == TickEvent.Phase.END && Backend.isGameActive()) LightUpdater.get(Minecraft.getInstance().level).tick(); } diff --git a/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java b/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java new file mode 100644 index 000000000..880246be3 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/light/GPULightVolume.java @@ -0,0 +1,160 @@ +package com.jozufozu.flywheel.light; + +import static org.lwjgl.opengl.GL20.GL_LINEAR; +import static org.lwjgl.opengl.GL20.GL_MIRRORED_REPEAT; +import static org.lwjgl.opengl.GL20.GL_TEXTURE0; +import static org.lwjgl.opengl.GL20.GL_TEXTURE4; +import static org.lwjgl.opengl.GL20.GL_TEXTURE_3D; +import static org.lwjgl.opengl.GL20.GL_TEXTURE_MAG_FILTER; +import static org.lwjgl.opengl.GL20.GL_TEXTURE_MIN_FILTER; +import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_R; +import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_S; +import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_T; +import static org.lwjgl.opengl.GL20.GL_UNPACK_ALIGNMENT; +import static org.lwjgl.opengl.GL20.GL_UNPACK_IMAGE_HEIGHT; +import static org.lwjgl.opengl.GL20.GL_UNPACK_ROW_LENGTH; +import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_IMAGES; +import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_PIXELS; +import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_ROWS; +import static org.lwjgl.opengl.GL20.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL20.glActiveTexture; +import static org.lwjgl.opengl.GL20.glPixelStorei; +import static org.lwjgl.opengl.GL20.glTexImage3D; +import static org.lwjgl.opengl.GL20.glTexParameteri; +import static org.lwjgl.opengl.GL20.glTexSubImage3D; + + +import com.jozufozu.flywheel.backend.Backend; +import com.jozufozu.flywheel.backend.gl.GlTexture; +import com.jozufozu.flywheel.backend.gl.GlTextureUnit; +import com.jozufozu.flywheel.backend.gl.versioned.RGPixelFormat; + +import net.minecraft.world.LightType; + +public class GPULightVolume extends LightVolume { + + protected final GridAlignedBB sampleVolume = new GridAlignedBB(); + private final GlTexture glTexture; + + private final RGPixelFormat pixelFormat; + protected boolean bufferDirty; + + public GPULightVolume(ImmutableBox sampleVolume) { + super(sampleVolume); + this.sampleVolume.assign(sampleVolume); + + pixelFormat = Backend.getInstance().compat.pixelFormat; + glTexture = new GlTexture(GL_TEXTURE_3D); + + // allocate space for the texture + glActiveTexture(GL_TEXTURE4); + glTexture.bind(); + + int sizeX = box.sizeX(); + int sizeY = box.sizeY(); + int sizeZ = box.sizeZ(); + glTexImage3D(GL_TEXTURE_3D, 0, pixelFormat.internalFormat(), sizeX, sizeY, sizeZ, 0, pixelFormat.format(), GL_UNSIGNED_BYTE, 0); + + glTexture.unbind(); + glActiveTexture(GL_TEXTURE0); + } + + @Override + protected void setBox(ImmutableBox box) { + this.box.assign(box); + this.box.nextPowerOf2Centered(); + // called during super ctor + if (sampleVolume != null) this.sampleVolume.assign(box); + } + + public void bind() { + // just in case something goes wrong, or we accidentally call this before this volume is properly disposed of. + if (lightData == null) return; + + GlTextureUnit.T4.makeActive(); + glTexture.bind(); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); + + uploadTexture(); + } + + private void uploadTexture() { + if (bufferDirty) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0); + glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + int sizeX = box.sizeX(); + int sizeY = box.sizeY(); + int sizeZ = box.sizeZ(); + + glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, pixelFormat.format(), GL_UNSIGNED_BYTE, lightData); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default + bufferDirty = false; + } + } + + public void unbind() { + glTexture.unbind(); + } + + @Override + public void delete() { + super.delete(); + glTexture.delete(); + } + + public void move(LightProvider world, ImmutableBox newSampleVolume) { + if (lightData == null) return; + + if (box.contains(newSampleVolume)) { + if (newSampleVolume.intersects(sampleVolume)) { + GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume); + sampleVolume.assign(newSampleVolume); + + copyLight(world, newArea); + } else { + sampleVolume.assign(newSampleVolume); + initialize(world); + } + } else { + super.move(world, newSampleVolume); + } + } + + public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changedVolume) { + super.onLightUpdate(world, type, changedVolume); + bufferDirty = true; + } + + public void onLightPacket(LightProvider world, int chunkX, int chunkZ) { + super.onLightPacket(world, chunkX, chunkZ); + bufferDirty = true; + } + + /** + * Completely (re)populate this volume with block and sky lighting data. + * This is expensive and should be avoided. + */ + public void initialize(LightProvider world) { + super.initialize(world); + bufferDirty = true; + } + + @Override + protected int getStride() { + return Backend.getInstance().compat.pixelFormat.byteCount(); + } + + @Override + public ImmutableBox getVolume() { + return sampleVolume; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java b/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java index 5838c335f..13fbe1d41 100644 --- a/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java +++ b/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java @@ -8,7 +8,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.SectionPos; import net.minecraft.util.math.vector.Vector3i; -public class GridAlignedBB implements ReadOnlyBox { +public class GridAlignedBB implements ImmutableBox { private int minX; private int minY; private int minZ; @@ -16,6 +16,10 @@ public class GridAlignedBB implements ReadOnlyBox { private int maxY; private int maxZ; + public GridAlignedBB() { + + } + public GridAlignedBB(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { this.minX = minX; this.minY = minY; @@ -58,19 +62,19 @@ public class GridAlignedBB implements ReadOnlyBox { } public void fixMinMax() { - int minX = Math.min(this.getMinX(), this.getMaxX()); - int minY = Math.min(this.getMinY(), this.getMaxY()); - int minZ = Math.min(this.getMinZ(), this.getMaxZ()); - int maxX = Math.max(this.getMinX(), this.getMaxX()); - int maxY = Math.max(this.getMinY(), this.getMaxY()); - int maxZ = Math.max(this.getMinZ(), this.getMaxZ()); + int minX = Math.min(this.minX, this.maxX); + int minY = Math.min(this.minY, this.maxY); + int minZ = Math.min(this.minZ, this.maxZ); + int maxX = Math.max(this.minX, this.maxX); + int maxY = Math.max(this.minY, this.maxY); + int maxZ = Math.max(this.minZ, this.maxZ); - this.setMinX(minX); - this.setMinY(minY); - this.setMinZ(minZ); - this.setMaxX(maxX); - this.setMaxY(maxY); - this.setMaxZ(maxZ); + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; } public void translate(Vector3i by) { @@ -78,12 +82,12 @@ public class GridAlignedBB implements ReadOnlyBox { } public void translate(int x, int y, int z) { - setMinX(getMinX() + x); - setMaxX(getMaxX() + x); - setMinY(getMinY() + y); - setMaxY(getMaxY() + y); - setMinZ(getMinZ() + z); - setMaxZ(getMaxZ() + z); + minX = minX + x; + maxX = maxX + x; + minY = minY + y; + maxY = maxY + y; + minZ = minZ + z; + maxZ = maxZ + z; } public void mirrorAbout(Direction.Axis axis) { @@ -93,15 +97,15 @@ public class GridAlignedBB implements ReadOnlyBox { int flipY = axisVec.getY() - 1; int flipZ = axisVec.getZ() - 1; - int maxX = this.getMaxX() * flipX; - int maxY = this.getMaxY() * flipY; - int maxZ = this.getMaxZ() * flipZ; - this.setMaxX(this.getMinX() * flipX); - this.setMaxY(this.getMinY() * flipY); - this.setMaxZ(this.getMinZ() * flipZ); - this.setMinX(maxX); - this.setMinY(maxY); - this.setMinZ(maxZ); + int maxX = this.maxX * flipX; + int maxY = this.maxY * flipY; + int maxZ = this.maxZ * flipZ; + this.maxX = this.minX * flipX; + this.maxY = this.minY * flipY; + this.maxZ = this.minZ * flipZ; + this.minX = maxX; + this.minY = maxY; + this.minZ = maxZ; } /** @@ -120,12 +124,12 @@ public class GridAlignedBB implements ReadOnlyBox { int diffY = newSizeY - sizeY; int diffZ = newSizeZ - sizeZ; - setMinX(getMinX() - diffX / 2); // floor division for the minimums - setMinY(getMinY() - diffY / 2); - setMinZ(getMinZ() - diffZ / 2); - setMaxX(getMaxX() + (diffX + 1) / 2); // ceiling divison for the maximums - setMaxY(getMaxY() + (diffY + 1) / 2); - setMaxZ(getMaxZ() + (diffZ + 1) / 2); + minX = minX - diffX / 2; // floor division for the minimums + minY = minY - diffY / 2; + minZ = minZ - diffZ / 2; + maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums + maxY = maxY + (diffY + 1) / 2; + maxZ = maxZ + (diffZ + 1) / 2; } /** @@ -136,9 +140,9 @@ public class GridAlignedBB implements ReadOnlyBox { int sizeY = RenderUtil.nextPowerOf2(sizeY()); int sizeZ = RenderUtil.nextPowerOf2(sizeZ()); - this.setMaxX(this.getMinX() + sizeX); - this.setMaxY(this.getMinY() + sizeY); - this.setMaxZ(this.getMinZ() + sizeZ); + maxX = minX + sizeX; + maxY = minY + sizeY; + maxZ = minZ + sizeZ; } public void grow(int s) { @@ -146,57 +150,57 @@ public class GridAlignedBB implements ReadOnlyBox { } public void grow(int x, int y, int z) { - setMinX(getMinX() - x); - setMinY(getMinY() - y); - setMinZ(getMinZ() - z); - setMaxX(getMaxX() + x); - setMaxY(getMaxY() + y); - setMaxZ(getMaxZ() + z); + minX = minX - x; + minY = minY - y; + minZ = minZ - z; + maxX = maxX + x; + maxY = maxY + y; + maxZ = maxZ + z; } - public void intersectAssign(ReadOnlyBox other) { - this.setMinX(Math.max(this.getMinX(), other.getMinX())); - this.setMinY(Math.max(this.getMinY(), other.getMinY())); - this.setMinZ(Math.max(this.getMinZ(), other.getMinZ())); - this.setMaxX(Math.min(this.getMaxX(), other.getMaxX())); - this.setMaxY(Math.min(this.getMaxY(), other.getMaxY())); - this.setMaxZ(Math.min(this.getMaxZ(), other.getMaxZ())); + public void intersectAssign(ImmutableBox other) { + minX = Math.max(this.minX, other.getMinX()); + minY = Math.max(this.minY, other.getMinY()); + minZ = Math.max(this.minZ, other.getMinZ()); + maxX = Math.min(this.maxX, other.getMaxX()); + maxY = Math.min(this.maxY, other.getMaxY()); + maxZ = Math.min(this.maxZ, other.getMaxZ()); } - public void unionAssign(ReadOnlyBox other) { - this.setMinX(Math.min(this.getMinX(), other.getMinX())); - this.setMinY(Math.min(this.getMinY(), other.getMinY())); - this.setMinZ(Math.min(this.getMinZ(), other.getMinZ())); - this.setMaxX(Math.max(this.getMaxX(), other.getMaxX())); - this.setMaxY(Math.max(this.getMaxY(), other.getMaxY())); - this.setMaxZ(Math.max(this.getMaxZ(), other.getMaxZ())); + public void unionAssign(ImmutableBox other) { + minX = Math.min(this.minX, other.getMinX()); + minY = Math.min(this.minY, other.getMinY()); + minZ = Math.min(this.minZ, other.getMinZ()); + maxX = Math.max(this.maxX, other.getMaxX()); + maxY = Math.max(this.maxY, other.getMaxY()); + maxZ = Math.max(this.maxZ, other.getMaxZ()); } public void unionAssign(AxisAlignedBB other) { - this.setMinX(Math.min(this.getMinX(), (int) Math.floor(other.minX))); - this.setMinY(Math.min(this.getMinY(), (int) Math.floor(other.minY))); - this.setMinZ(Math.min(this.getMinZ(), (int) Math.floor(other.minZ))); - this.setMaxX(Math.max(this.getMaxX(), (int) Math.ceil(other.maxX))); - this.setMaxY(Math.max(this.getMaxY(), (int) Math.ceil(other.maxY))); - this.setMaxZ(Math.max(this.getMaxZ(), (int) Math.ceil(other.maxZ))); + minX = Math.min(this.minX, (int) Math.floor(other.minX)); + minY = Math.min(this.minY, (int) Math.floor(other.minY)); + minZ = Math.min(this.minZ, (int) Math.floor(other.minZ)); + maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX)); + maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY)); + maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ)); } public void assign(AxisAlignedBB other) { - this.setMinX((int) Math.floor(other.minX)); - this.setMinY((int) Math.floor(other.minY)); - this.setMinZ((int) Math.floor(other.minZ)); - this.setMaxX((int) Math.ceil(other.maxX)); - this.setMaxY((int) Math.ceil(other.maxY)); - this.setMaxZ((int) Math.ceil(other.maxZ)); + minX = (int) Math.floor(other.minX); + minY = (int) Math.floor(other.minY); + minZ = (int) Math.floor(other.minZ); + maxX = (int) Math.ceil(other.maxX); + maxY = (int) Math.ceil(other.maxY); + maxZ = (int) Math.ceil(other.maxZ); } - public void assign(ReadOnlyBox other) { - this.setMinX(other.getMinX()); - this.setMinY(other.getMinY()); - this.setMinZ(other.getMinZ()); - this.setMaxX(other.getMaxX()); - this.setMaxY(other.getMaxY()); - this.setMaxZ(other.getMaxZ()); + public void assign(ImmutableBox other) { + minX = other.getMinX(); + minY = other.getMinY(); + minZ = other.getMinZ(); + maxX = other.getMaxX(); + maxY = other.getMaxY(); + maxZ = other.getMaxZ(); } @Override @@ -204,7 +208,7 @@ public class GridAlignedBB implements ReadOnlyBox { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ReadOnlyBox that = (ReadOnlyBox) o; + ImmutableBox that = (ImmutableBox) o; return this.sameAs(that); } @@ -279,4 +283,132 @@ public class GridAlignedBB implements ReadOnlyBox { this.maxZ = maxZ; return this; } + + public GridAlignedBB assign(BlockPos start, BlockPos end) { + minX = start.getX(); + minY = start.getY(); + minZ = start.getZ(); + maxX = end.getX() + 1; + maxY = end.getY() + 1; + maxZ = end.getZ() + 1; + return this; + } + + public GridAlignedBB setMax(Vector3i v) { + return setMax(v.getX(), v.getY(), v.getZ()); + } + + public GridAlignedBB setMin(Vector3i v) { + return setMin(v.getX(), v.getY(), v.getZ()); + } + + public GridAlignedBB setMax(int x, int y, int z) { + maxX = x; + maxY = y; + maxZ = z; + return this; + } + + public GridAlignedBB setMin(int x, int y, int z) { + minX = x; + minY = y; + minZ = z; + return this; + } + + @Override + public int sizeX() { + return maxX - minX; + } + + @Override + public int sizeY() { + return maxY - minY; + } + + @Override + public int sizeZ() { + return maxZ - minZ; + } + + @Override + public boolean empty() { + // if any dimension has side length 0 this box contains no volume + return minX == maxX || minY == maxY || minZ == maxZ; + } + + @Override + public boolean sameAs(ImmutableBox other) { + return minX == other.getMinX() && minY == other.getMinY() && minZ == other.getMinZ() && maxX == other.getMaxX() && maxY == other.getMaxY() && maxZ == other.getMaxZ(); + } + + @Override + public boolean sameAs(AxisAlignedBB other) { + return minX == Math.floor(other.minX) + && minY == Math.floor(other.minY) + && minZ == Math.floor(other.minZ) + && maxX == Math.ceil(other.maxX) + && maxY == Math.ceil(other.maxY) + && maxZ == Math.ceil(other.maxZ); + } + + @Override + public GridAlignedBB intersect(ImmutableBox other) { + int minX = Math.max(this.minX, other.getMinX()); + int minY = Math.max(this.minY, other.getMinY()); + int minZ = Math.max(this.minZ, other.getMinZ()); + int maxX = Math.min(this.maxX, other.getMaxX()); + int maxY = Math.min(this.maxY, other.getMaxY()); + int maxZ = Math.min(this.maxZ, other.getMaxZ()); + return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + } + + @Override + public ImmutableBox union(ImmutableBox other) { + int minX = Math.min(this.minX, other.getMinX()); + int minY = Math.min(this.minY, other.getMinY()); + int minZ = Math.min(this.minZ, other.getMinZ()); + int maxX = Math.max(this.maxX, other.getMaxX()); + int maxY = Math.max(this.maxY, other.getMaxY()); + int maxZ = Math.max(this.maxZ, other.getMaxZ()); + return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + } + + @Override + public boolean contains(ImmutableBox other) { + return other.getMinX() >= this.minX && other.getMaxX() <= this.maxX && other.getMinY() >= this.minY && other.getMaxY() <= this.maxY && other.getMinZ() >= this.minZ && other.getMaxZ() <= this.maxZ; + } + + @Override + public boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ; + } + + @Override + public void forEachContained(ICoordinateConsumer func) { + if (empty()) return; + + for (int x = minX; x < maxX; x++) { + for (int y = Math.max(minY, 0); y < Math.min(maxY, 255); y++) { // clamp to world height limits + for (int z = minZ; z < maxZ; z++) { + func.consume(x, y, z); + } + } + } + } + + @Override + public AxisAlignedBB toAABB() { + return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + } + + @Override + public GridAlignedBB copy() { + return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + } + + @Override + public String toString() { + return "(" + minX + ", " + minY + ", " + minZ + ")->(" + maxX + ", " + maxY + ", " + maxZ + ')'; + } } diff --git a/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java b/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java index 659d9e819..ac0f8d3f1 100644 --- a/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java +++ b/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java @@ -4,14 +4,14 @@ import net.minecraft.world.LightType; public interface ILightUpdateListener { - ReadOnlyBox getVolume(); + ImmutableBox getVolume(); ListenerStatus status(); /** * Called when a light updates in a chunk the implementor cares about. */ - void onLightUpdate(LightProvider world, LightType type, ReadOnlyBox changed); + void onLightUpdate(LightProvider world, LightType type, ImmutableBox changed); /** * Called when the server sends light data to the client. diff --git a/src/main/java/com/jozufozu/flywheel/light/ReadOnlyBox.java b/src/main/java/com/jozufozu/flywheel/light/ImmutableBox.java similarity index 81% rename from src/main/java/com/jozufozu/flywheel/light/ReadOnlyBox.java rename to src/main/java/com/jozufozu/flywheel/light/ImmutableBox.java index 5b53f02d6..7ae6ac5e2 100644 --- a/src/main/java/com/jozufozu/flywheel/light/ReadOnlyBox.java +++ b/src/main/java/com/jozufozu/flywheel/light/ImmutableBox.java @@ -4,7 +4,7 @@ import static com.jozufozu.flywheel.util.RenderUtil.isPowerOf2; import net.minecraft.util.math.AxisAlignedBB; -public interface ReadOnlyBox { +public interface ImmutableBox { int getMinX(); int getMinY(); @@ -38,7 +38,7 @@ public interface ReadOnlyBox { return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ(); } - default boolean sameAs(ReadOnlyBox other) { + default boolean sameAs(ImmutableBox other) { return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ(); } @@ -56,7 +56,7 @@ public interface ReadOnlyBox { return isPowerOf2(volume()); } - default GridAlignedBB intersect(ReadOnlyBox other) { + default GridAlignedBB intersect(ImmutableBox other) { int minX = Math.max(this.getMinX(), other.getMinX()); int minY = Math.max(this.getMinY(), other.getMinY()); int minZ = Math.max(this.getMinZ(), other.getMinZ()); @@ -66,7 +66,7 @@ public interface ReadOnlyBox { return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); } - default ReadOnlyBox union(ReadOnlyBox other) { + default ImmutableBox union(ImmutableBox other) { int minX = Math.min(this.getMinX(), other.getMinX()); int minY = Math.min(this.getMinY(), other.getMinY()); int minZ = Math.min(this.getMinZ(), other.getMinZ()); @@ -77,12 +77,26 @@ public interface ReadOnlyBox { } - default boolean intersects(ReadOnlyBox other) { + default boolean intersects(ImmutableBox other) { return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ()); } - default boolean contains(ReadOnlyBox other) { - return other.getMinX() >= this.getMinX() && other.getMaxX() <= this.getMaxX() && other.getMinY() >= this.getMinY() && other.getMaxY() <= this.getMaxY() && other.getMinZ() >= this.getMinZ() && other.getMaxZ() <= this.getMaxZ(); + default boolean contains(int x, int y, int z) { + return x >= getMinX() + && x <= getMaxX() + && y >= getMinY() + && y <= getMaxY() + && z >= getMinZ() + && z <= getMaxZ(); + } + + default boolean contains(ImmutableBox other) { + return other.getMinX() >= this.getMinX() + && other.getMaxX() <= this.getMaxX() + && other.getMinY() >= this.getMinY() + && other.getMaxY() <= this.getMaxY() + && other.getMinZ() >= this.getMinZ() + && other.getMaxZ() <= this.getMaxZ(); } default boolean isContainedBy(GridAlignedBB other) { diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java index 5563d6cd6..7307c21f4 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java @@ -14,7 +14,7 @@ import net.minecraft.world.IBlockDisplayReader; import net.minecraft.world.LightType; /** - * Keeps track of what chunks/sections each listener is in so we can update exactly what needs to be updated. + * Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated. */ public class LightUpdater { @@ -33,6 +33,10 @@ public class LightUpdater { provider = BasicProvider.get(world); } + public LightProvider getProvider() { + return provider; + } + public void tick() { for (IMovingListener listener : movingListeners) { if (listener.update(provider)) { @@ -50,7 +54,7 @@ public class LightUpdater { if (listener instanceof IMovingListener) movingListeners.add(((IMovingListener) listener)); - ReadOnlyBox box = listener.getVolume(); + ImmutableBox box = listener.getVolume(); LongSet sections = this.sections.getAndResetContainment(listener); LongSet chunks = this.chunks.getAndResetContainment(listener); @@ -76,6 +80,11 @@ public class LightUpdater { } } + public void removeListener(ILightUpdateListener listener) { + this.sections.remove(listener); + this.chunks.remove(listener); + } + /** * Dispatch light updates to all registered {@link ILightUpdateListener}s. * @param type The type of light that changed. @@ -88,7 +97,7 @@ public class LightUpdater { set.removeIf(l -> l.status().shouldRemove()); - ReadOnlyBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos)); + ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos)); for (ILightUpdateListener listener : set) { listener.onLightUpdate(provider, type, chunkBox); @@ -122,7 +131,7 @@ public class LightUpdater { return sectionPos & 0xFFFFFFFFFFF_00000L; } - public Stream getAllBoxes() { + public Stream getAllBoxes() { return chunks.stream().map(ILightUpdateListener::getVolume); } diff --git a/src/main/java/com/jozufozu/flywheel/light/LightVolume.java b/src/main/java/com/jozufozu/flywheel/light/LightVolume.java index f44b59c85..d434143bb 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightVolume.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightVolume.java @@ -1,165 +1,89 @@ package com.jozufozu.flywheel.light; -import static org.lwjgl.opengl.GL20.GL_LINEAR; -import static org.lwjgl.opengl.GL20.GL_MIRRORED_REPEAT; -import static org.lwjgl.opengl.GL20.GL_TEXTURE0; -import static org.lwjgl.opengl.GL20.GL_TEXTURE4; -import static org.lwjgl.opengl.GL20.GL_TEXTURE_3D; -import static org.lwjgl.opengl.GL20.GL_TEXTURE_MAG_FILTER; -import static org.lwjgl.opengl.GL20.GL_TEXTURE_MIN_FILTER; -import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_R; -import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_S; -import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_T; -import static org.lwjgl.opengl.GL20.GL_UNPACK_ALIGNMENT; -import static org.lwjgl.opengl.GL20.GL_UNPACK_IMAGE_HEIGHT; -import static org.lwjgl.opengl.GL20.GL_UNPACK_ROW_LENGTH; -import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_IMAGES; -import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_PIXELS; -import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_ROWS; -import static org.lwjgl.opengl.GL20.GL_UNSIGNED_BYTE; -import static org.lwjgl.opengl.GL20.glActiveTexture; -import static org.lwjgl.opengl.GL20.glPixelStorei; -import static org.lwjgl.opengl.GL20.glTexImage3D; -import static org.lwjgl.opengl.GL20.glTexParameteri; -import static org.lwjgl.opengl.GL20.glTexSubImage3D; - import java.nio.ByteBuffer; import org.lwjgl.system.MemoryUtil; -import com.jozufozu.flywheel.backend.Backend; -import com.jozufozu.flywheel.backend.gl.GlTexture; -import com.jozufozu.flywheel.backend.gl.GlTextureUnit; -import com.jozufozu.flywheel.backend.gl.versioned.RGPixelFormat; - import net.minecraft.util.math.BlockPos; -import net.minecraft.world.IBlockDisplayReader; import net.minecraft.world.LightType; -public class LightVolume { +public class LightVolume implements ImmutableBox, ILightUpdateListener { - private GridAlignedBB sampleVolume; - private GridAlignedBB textureVolume; - private ByteBuffer lightData; + protected final GridAlignedBB box = new GridAlignedBB(); + protected ByteBuffer lightData; - private boolean bufferDirty; - private boolean removed; + public LightVolume(ImmutableBox sampleVolume) { + this.setBox(sampleVolume); - private final GlTexture glTexture; - - private final RGPixelFormat pixelFormat; - - public LightVolume(GridAlignedBB sampleVolume) { - setSampleVolume(sampleVolume); - - pixelFormat = Backend.getInstance().compat.pixelFormat; - - this.glTexture = new GlTexture(GL_TEXTURE_3D); - this.lightData = MemoryUtil.memAlloc(this.textureVolume.volume() * pixelFormat.byteCount()); - - // allocate space for the texture - glActiveTexture(GL_TEXTURE4); - glTexture.bind(); - - int sizeX = textureVolume.sizeX(); - int sizeY = textureVolume.sizeY(); - int sizeZ = textureVolume.sizeZ(); - glTexImage3D(GL_TEXTURE_3D, 0, pixelFormat.internalFormat(), sizeX, sizeY, sizeZ, 0, pixelFormat.format(), GL_UNSIGNED_BYTE, 0); - - glTexture.unbind(); - glActiveTexture(GL_TEXTURE0); + this.lightData = MemoryUtil.memAlloc(this.box.volume() * getStride()); } - private void setSampleVolume(GridAlignedBB sampleVolume) { - this.sampleVolume = sampleVolume; - this.textureVolume = sampleVolume.copy(); - this.textureVolume.nextPowerOf2Centered(); + protected void setBox(ImmutableBox box) { + this.box.assign(box); } - public ReadOnlyBox getTextureVolume() { - return textureVolume; - } - - public ReadOnlyBox getSampleVolume() { - return sampleVolume; - } - - public int getMinX() { - return textureVolume.getMinX(); - } - - public int getMinY() { - return textureVolume.getMinY(); - } - - public int getMinZ() { - return textureVolume.getMinZ(); - } - - public int getMaxX() { - return textureVolume.getMaxX(); - } - - public int getMaxY() { - return textureVolume.getMaxY(); - } - - public int getMaxZ() { - return textureVolume.getMaxZ(); - } - - public int getSizeX() { - return textureVolume.sizeX(); - } - - public int getSizeY() { - return textureVolume.sizeY(); - } - - public int getSizeZ() { - return textureVolume.sizeZ(); - } - - public void move(IBlockDisplayReader world, GridAlignedBB newSampleVolume) { - if (removed) return; - - if (textureVolume.contains(newSampleVolume)) { - BasicProvider basicProvider = BasicProvider.get(world); - if (newSampleVolume.intersects(sampleVolume)) { - GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume); - sampleVolume = newSampleVolume; - - copyLight(basicProvider, newArea); - } else { - sampleVolume = newSampleVolume; - initialize(world); - } + public short getPackedLight(int x, int y, int z) { + if (box.contains(x, y, z)) { + return lightData.getShort(worldPosToBufferIndex(x, y, z)); } else { - setSampleVolume(newSampleVolume); - int volume = textureVolume.volume(); - if (volume * 2 > lightData.capacity()) { - lightData = MemoryUtil.memRealloc(lightData, volume * 2); - } - initialize(world); + return 0; } } - public void notifyLightUpdate(LightProvider world, LightType type, ReadOnlyBox changedVolume) { - if (removed) return; - - if (!changedVolume.intersects(sampleVolume)) return; - changedVolume = changedVolume.intersect(sampleVolume); // compute the region contained by us that has dirty lighting data. - - if (type == LightType.BLOCK) copyBlock(world, changedVolume); - else if (type == LightType.SKY) copySky(world, changedVolume); + public int getMinX() { + return box.getMinX(); } - public void notifyLightPacket(LightProvider world, int chunkX, int chunkZ) { - if (removed) return; + public int getMinY() { + return box.getMinY(); + } + + public int getMinZ() { + return box.getMinZ(); + } + + public int getMaxX() { + return box.getMaxX(); + } + + public int getMaxY() { + return box.getMaxY(); + } + + public int getMaxZ() { + return box.getMaxZ(); + } + + public void move(LightProvider world, ImmutableBox newSampleVolume) { + if (lightData == null) return; + + setBox(newSampleVolume); + int volume = box.volume(); + if (volume * 2 > lightData.capacity()) { + lightData = MemoryUtil.memRealloc(lightData, volume * 2); + } + initialize(world); + } + + @Override + public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changedVolume) { + if (lightData == null) return; + + GridAlignedBB vol = changedVolume.copy(); + if (!vol.intersects(getVolume())) return; + vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data. + + if (type == LightType.BLOCK) copyBlock(world, vol); + else if (type == LightType.SKY) copySky(world, vol); + } + + @Override + public void onLightPacket(LightProvider world, int chunkX, int chunkZ) { + if (lightData == null) return; GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ); - if (!changedVolume.intersects(sampleVolume)) return; - changedVolume.intersectAssign(sampleVolume); // compute the region contained by us that has dirty lighting data. + if (!changedVolume.intersects(getVolume())) return; + changedVolume.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data. copyLight(world, changedVolume); } @@ -168,25 +92,20 @@ public class LightVolume { * Completely (re)populate this volume with block and sky lighting data. * This is expensive and should be avoided. */ - public void initialize(IBlockDisplayReader world) { - if (removed) return; + public void initialize(LightProvider world) { + if (lightData == null) return; - BlockPos.Mutable pos = new BlockPos.Mutable(); + ImmutableBox box = getVolume(); + int shiftX = box.getMinX(); + int shiftY = box.getMinY(); + int shiftZ = box.getMinZ(); - int shiftX = textureVolume.getMinX(); - int shiftY = textureVolume.getMinY(); - int shiftZ = textureVolume.getMinZ(); - - sampleVolume.forEachContained((x, y, z) -> { - pos.set(x, y, z); - - int blockLight = world.getBrightness(LightType.BLOCK, pos); - int skyLight = world.getBrightness(LightType.SKY, pos); + box.forEachContained((x, y, z) -> { + int blockLight = world.getLight(LightType.BLOCK, x, y, z); + int skyLight = world.getLight(LightType.SKY, x, y, z); writeLight(x - shiftX, y - shiftY, z - shiftZ, blockLight, skyLight); }); - - bufferDirty = true; } /** @@ -194,18 +113,16 @@ public class LightVolume { * * @param worldVolume the region in the world to copy data from. */ - public void copyBlock(LightProvider world, ReadOnlyBox worldVolume) { - int xShift = textureVolume.getMinX(); - int yShift = textureVolume.getMinY(); - int zShift = textureVolume.getMinZ(); + public void copyBlock(LightProvider world, ImmutableBox worldVolume) { + int xShift = box.getMinX(); + int yShift = box.getMinY(); + int zShift = box.getMinZ(); worldVolume.forEachContained((x, y, z) -> { int light = world.getLight(LightType.BLOCK, x, y, z); writeBlock(x - xShift, y - yShift, z - zShift, light); }); - - bufferDirty = true; } /** @@ -213,18 +130,16 @@ public class LightVolume { * * @param worldVolume the region in the world to copy data from. */ - public void copySky(LightProvider world, ReadOnlyBox worldVolume) { - int xShift = textureVolume.getMinX(); - int yShift = textureVolume.getMinY(); - int zShift = textureVolume.getMinZ(); + public void copySky(LightProvider world, ImmutableBox worldVolume) { + int xShift = box.getMinX(); + int yShift = box.getMinY(); + int zShift = box.getMinZ(); worldVolume.forEachContained((x, y, z) -> { int light = world.getLight(LightType.SKY, x, y, z); writeSky(x - xShift, y - yShift, z - zShift, light); }); - - bufferDirty = true; } /** @@ -232,12 +147,12 @@ public class LightVolume { * * @param worldVolume the region in the world to copy data from. */ - public void copyLight(LightProvider world, ReadOnlyBox worldVolume) { + public void copyLight(LightProvider world, ImmutableBox worldVolume) { BlockPos.Mutable pos = new BlockPos.Mutable(); - int xShift = textureVolume.getMinX(); - int yShift = textureVolume.getMinY(); - int zShift = textureVolume.getMinZ(); + int xShift = box.getMinX(); + int yShift = box.getMinY(); + int zShift = box.getMinZ(); worldVolume.forEachContained((x, y, z) -> { pos.set(x, y, z); @@ -247,77 +162,80 @@ public class LightVolume { writeLight(x - xShift, y - yShift, z - zShift, block, sky); }); - - bufferDirty = true; - } - - public void bind() { - // just in case something goes wrong or we accidentally call this before this volume is properly disposed of. - if (lightData == null || removed) return; - - GlTextureUnit.T4.makeActive(); - glTexture.bind(); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); - - uploadTexture(); - } - - private void uploadTexture() { - if (bufferDirty) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); - glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0); - glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); - glPixelStorei(GL_UNPACK_ALIGNMENT, 2); - int sizeX = textureVolume.sizeX(); - int sizeY = textureVolume.sizeY(); - int sizeZ = textureVolume.sizeZ(); - - glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, pixelFormat.format(), GL_UNSIGNED_BYTE, lightData); - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default - bufferDirty = false; - } - } - - public void unbind() { - glTexture.unbind(); } public void delete() { - removed = true; - glTexture.delete(); MemoryUtil.memFree(lightData); lightData = null; } - private void writeLight(int x, int y, int z, int block, int sky) { + protected void writeLight(int x, int y, int z, int block, int sky) { byte b = (byte) ((block & 0xF) << 4); byte s = (byte) ((sky & 0xF) << 4); - int i = posToIndex(x, y, z); + int i = boxPosToBufferIndex(x, y, z); lightData.put(i, b); lightData.put(i + 1, s); } - private void writeBlock(int x, int y, int z, int block) { + protected void writeBlock(int x, int y, int z, int block) { byte b = (byte) ((block & 0xF) << 4); - lightData.put(posToIndex(x, y, z), b); + lightData.put(boxPosToBufferIndex(x, y, z), b); } - private void writeSky(int x, int y, int z, int sky) { + protected void writeSky(int x, int y, int z, int sky) { byte b = (byte) ((sky & 0xF) << 4); - lightData.put(posToIndex(x, y, z) + 1, b); + lightData.put(boxPosToBufferIndex(x, y, z) + 1, b); } - private int posToIndex(int x, int y, int z) { - return (x + textureVolume.sizeX() * (y + z * textureVolume.sizeY())) * pixelFormat.byteCount(); + protected int worldPosToBufferIndex(int x, int y, int z) { + x -= box.getMinX(); + y -= box.getMinY(); + z -= box.getMinZ(); + return boxPosToBufferIndex(x, y, z); } + + protected int boxPosToBufferIndex(int x, int y, int z) { + return (x + box.sizeX() * (y + z * box.sizeY())) * getStride(); + } + + /** + * @return The stride of the texels, in bytes. + */ + protected int getStride() { + return 2; + } + + @Override + public ImmutableBox getVolume() { + return box; + } + + @Override + public ListenerStatus status() { + return ListenerStatus.OKAY; + } + + public static int unpackBlock(short packed) { + return (packed >> 4) & 0xF; + } + + public static int unpackSky(short packed) { + return (packed >> 12) & 0xF; + } + + public static byte packLight(byte block, byte sky) { + return (byte) (block | (sky << 4)); + } + + public static int unpackBlock(byte packed) { + return packed & 0xF; + } + + public static int unpackSky(byte packed) { + return (packed >> 4) & 0xF; + } + } diff --git a/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java b/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java index e884ec706..ae6c5ea61 100644 --- a/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java +++ b/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java @@ -1,7 +1,6 @@ package com.jozufozu.flywheel.light; import java.util.AbstractCollection; -import java.util.Collection; import java.util.Iterator; import java.util.Set; import java.util.WeakHashMap; @@ -57,6 +56,24 @@ public class WeakContainmentMultiMap extends AbstractCollection { forward.computeIfAbsent(sectionPos, $ -> new WeakHashSet<>()).add(listener); } + @Override + public boolean remove(Object o) { + LongSet containmentSet = reverse.remove(o); + + if (containmentSet != null) { + containmentSet.forEach((LongConsumer) l -> { + WeakHashSet listeners = forward.get(l); + + if (listeners != null) listeners.remove(o); + }); + + containmentSet.clear(); + + return true; + } + return false; + } + @Override public Iterator iterator() { return reverse.keySet().iterator();