To delete or not to delete

- Fix AtomicBitset#nextClearBit
- Add test for AtomicBitset
- Remove unnecessary gl state stuff from IndirectEngine#renderCrumbling.
- Fix instance removal on IndirectEngine.
  - Deleting an instance not at the end of the instancers list would
    cause an exception to be thrown during upload caused by
    out-of-bounds indexing.
  - Fixed by unsetting changed for the tail of the instancers list.
This commit is contained in:
Jozufozu 2023-12-20 11:21:18 -08:00
parent 7c0959be9a
commit 948750f296
5 changed files with 67 additions and 34 deletions

View file

@ -40,11 +40,6 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
return instances.size(); return instances.size();
} }
protected boolean moreThanTwoThirdsChanged() {
return (changed.cardinality() * 3) > (instances.size() * 2);
}
public void notifyDirty(int index) { public void notifyDirty(int index) {
if (index < 0 || index >= getInstanceCount()) { if (index < 0 || index >= getInstanceCount()) {
return; return;
@ -68,21 +63,34 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
final int oldSize = this.instances.size(); final int oldSize = this.instances.size();
int removeCount = deleted.cardinality(); int removeCount = deleted.cardinality();
if (oldSize == removeCount) {
clear();
return;
}
final int newSize = oldSize - removeCount; final int newSize = oldSize - removeCount;
// shift surviving elements left over the spaces left by removed elements // Punch out the deleted instances, shifting over surviving instances to fill their place.
for (int i = 0, j = 0; (i < oldSize) && (j < newSize); i++, j++) { for (int scanPos = 0, writePos = 0; (scanPos < oldSize) && (writePos < newSize); scanPos++, writePos++) {
i = deleted.nextClearBit(i); // Find next non-deleted element.
scanPos = deleted.nextClearBit(scanPos);
if (i != j) { if (scanPos != writePos) {
var handle = handles.get(i); // Grab the old instance/handle from scanPos...
I instance = instances.get(i); var handle = handles.get(scanPos);
I instance = instances.get(scanPos);
handles.set(j, handle); // ... and move it to writePos.
instances.set(j, instance); handles.set(writePos, handle);
instances.set(writePos, instance);
handle.index = j; // Make sure the handle knows it's been moved...
changed.set(j); handle.index = writePos;
// ...and set it changed to force an upload.
changed.set(writePos);
// Clear the old slot. There's nothing meaningful there that can be considered "changed".
changed.clear(scanPos);
} }
} }

View file

@ -19,6 +19,8 @@ public class InstanceHandleImpl implements InstanceHandle {
@Override @Override
public void setDeleted() { public void setDeleted() {
instancer.notifyRemoval(index); instancer.notifyRemoval(index);
// invalidate ourselves
clear();
} }
public void clear() { public void clear() {

View file

@ -76,26 +76,9 @@ public class IndirectEngine extends AbstractEngine {
executor.syncUntil(flushFlag::isRaised); executor.syncUntil(flushFlag::isRaised);
try (var restoreState = GlStateTracker.getRestoreState()) { try (var restoreState = GlStateTracker.getRestoreState()) {
int prevActiveTexture = GlStateManager._getActiveTexture();
Minecraft.getInstance().gameRenderer.overlayTexture()
.setupOverlayColor();
Minecraft.getInstance().gameRenderer.lightTexture()
.turnOnLightLayer();
GlTextureUnit.T1.makeActive();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(1));
GlTextureUnit.T2.makeActive();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(2));
drawManager.renderCrumbling(crumblingBlocks); drawManager.renderCrumbling(crumblingBlocks);
MaterialRenderState.reset(); MaterialRenderState.reset();
Minecraft.getInstance().gameRenderer.overlayTexture()
.teardownOverlayColor();
Minecraft.getInstance().gameRenderer.lightTexture()
.turnOffLightLayer();
GlStateManager._activeTexture(prevActiveTexture);
} }
} }

View file

@ -168,13 +168,13 @@ public class AtomicBitset {
if (++longPosition > segmentMask) { if (++longPosition > segmentMask) {
segmentPosition++; segmentPosition++;
if (segmentPosition >= segments.numSegments()) { if (segmentPosition >= segments.numSegments()) {
return segments.numSegments() << log2SegmentSize; return segments.numSegments() << log2SegmentSize + (longPosition << 6);
} }
segment = segments.getSegment(segmentPosition); segment = segments.getSegment(segmentPosition);
longPosition = 0; longPosition = 0;
} }
word = segment.get(longPosition); word = ~segment.get(longPosition);
} }
} }

View file

@ -0,0 +1,40 @@
package com.jozufozu.flywheel.lib.util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TestAtomicBitset {
@Test
void testNextClearBit() {
var segmentLength = 1 << AtomicBitset.DEFAULT_LOG2_SEGMENT_SIZE_IN_BITS;
var bitLength = 2 << AtomicBitset.DEFAULT_LOG2_SEGMENT_SIZE_IN_BITS;
var bs = new AtomicBitset(AtomicBitset.DEFAULT_LOG2_SEGMENT_SIZE_IN_BITS, bitLength);
Assertions.assertEquals(0, bs.nextClearBit(0));
Assertions.assertEquals(1, bs.nextClearBit(1));
Assertions.assertEquals(5000, bs.nextClearBit(5000));
bs.set(16);
Assertions.assertEquals(0, bs.nextClearBit(0));
Assertions.assertEquals(17, bs.nextClearBit(16));
bs.set(segmentLength + 1);
Assertions.assertEquals(0, bs.nextClearBit(0));
Assertions.assertEquals(segmentLength + 2, bs.nextClearBit(segmentLength + 1));
bs.set(bitLength);
Assertions.assertEquals(0, bs.nextClearBit(0));
Assertions.assertEquals(bitLength + 1, bs.nextClearBit(bitLength));
for (int i = 0; i < bitLength; i++) {
bs.set(i);
}
Assertions.assertEquals(bitLength + 1, bs.nextClearBit(0));
}
}