Update light updates

- ... to address the nullpointer with create pulleys
 - LightListeners track their own levels
 - Remove BasicProvider and LightProvider
 - Rename MovingListener to better match functionality
 - Remove ListenerStatus in favor of a boolean
 - Instances keep track of their removal status and properly report it via LightListener#isListenerInvalid
 - Bump version - 0.6.4
This commit is contained in:
Jozufozu 2022-07-09 13:23:40 -04:00
parent f058b37b13
commit 055802160f
14 changed files with 112 additions and 147 deletions

View file

@ -2,7 +2,7 @@ org.gradle.jvmargs = -Xmx3G
org.gradle.daemon = false
# mod version info
mod_version = 0.6.3
mod_version = 0.6.4
mc_update_version = 1.18
minecraft_version = 1.18.2
forge_version = 40.0.15

View file

@ -10,8 +10,6 @@ import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.core.materials.FlatLit;
import com.jozufozu.flywheel.light.LightListener;
import com.jozufozu.flywheel.light.LightProvider;
import com.jozufozu.flywheel.light.ListenerStatus;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.core.BlockPos;
@ -26,6 +24,7 @@ public abstract class AbstractInstance implements Instance, LightListener {
protected final MaterialManager materialManager;
public final Level world;
protected boolean removed = false;
public AbstractInstance(MaterialManager materialManager, Level world) {
this.materialManager = materialManager;
@ -39,10 +38,19 @@ public abstract class AbstractInstance implements Instance, LightListener {
}
final void removeAndMark() {
if (removed) {
return;
}
remove();
removed = true;
}
/**
* Free any acquired resources.
*/
public abstract void remove();
protected abstract void remove();
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
@ -78,12 +86,12 @@ public abstract class AbstractInstance implements Instance, LightListener {
}
@Override
public ListenerStatus status() {
return ListenerStatus.OKAY;
public boolean isListenerInvalid() {
return removed;
}
@Override
public void onLightUpdate(LightProvider world, LightLayer type, ImmutableBox changed) {
public void onLightUpdate(LightLayer type, ImmutableBox changed) {
updateLight();
}
@ -103,4 +111,5 @@ public abstract class AbstractInstance implements Instance, LightListener {
models.forEach(model -> model.setBlockLight(block)
.setSkyLight(sky));
}
}

View file

@ -260,7 +260,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
public void invalidate() {
instances.values().forEach(AbstractInstance::remove);
instances.values().forEach(AbstractInstance::removeAndMark);
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
@ -314,7 +314,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
}
protected void removeInternal(T obj, AbstractInstance instance) {
instance.remove();
instance.removeAndMark();
instances.remove(obj);
dynamicInstances.remove(obj);
tickableInstances.remove(obj);

View file

@ -6,8 +6,7 @@ import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.light.LightListener;
import com.jozufozu.flywheel.light.LightProvider;
import com.jozufozu.flywheel.light.MovingListener;
import com.jozufozu.flywheel.light.TickingLightListener;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.mojang.math.Vector3f;
@ -34,7 +33,7 @@ import net.minecraft.world.phys.Vec3;
*
* @param <E> The type of {@link Entity} your class is an instance of.
*/
public abstract class EntityInstance<E extends Entity> extends AbstractInstance implements LightListener, MovingListener {
public abstract class EntityInstance<E extends Entity> extends AbstractInstance implements LightListener, TickingLightListener {
protected final E entity;
protected final GridAlignedBB bounds;
@ -51,7 +50,7 @@ public abstract class EntityInstance<E extends Entity> extends AbstractInstance
}
@Override
public boolean update(LightProvider provider) {
public boolean tickLightListener() {
AABB boundsNow = entity.getBoundingBox();
if (bounds.sameAs(boundsNow)) return false;

View file

@ -1,23 +0,0 @@
package com.jozufozu.flywheel.light;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
/**
* Wraps a world and minimally lowers the interface.
*/
public class BasicProvider implements LightProvider {
private final BlockAndTintGetter reader;
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
public BasicProvider(BlockAndTintGetter reader) {
this.reader = reader;
}
@Override
public int getLight(LightLayer type, int x, int y, int z) {
return reader.getBrightness(type, pos.set(x, y, z));
}
}

View file

@ -26,6 +26,7 @@ import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
public class GPULightVolume extends LightVolume {
@ -36,8 +37,8 @@ public class GPULightVolume extends LightVolume {
private final GlTextureUnit textureUnit = GlTextureUnit.T4;
protected boolean bufferDirty;
public GPULightVolume(ImmutableBox sampleVolume) {
super(sampleVolume);
public GPULightVolume(BlockAndTintGetter level, ImmutableBox sampleVolume) {
super(level, sampleVolume);
this.sampleVolume.assign(sampleVolume);
glTexture = new GlTexture(GL_TEXTURE_3D);
@ -110,38 +111,24 @@ public class GPULightVolume extends LightVolume {
glTexture.delete();
}
public void move(LightProvider world, ImmutableBox newSampleVolume) {
public void move(BlockAndTintGetter level, ImmutableBox newSampleVolume) {
if (lightData == null) return;
if (box.contains(newSampleVolume)) {
sampleVolume.assign(newSampleVolume);
initialize(world);
initialize();
} else {
super.move(world, newSampleVolume);
super.move(level, newSampleVolume);
}
}
public void onLightUpdate(LightProvider world, LightLayer 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
public ImmutableBox getVolume() {
return sampleVolume;
}
@Override
protected void markDirty() {
this.bufferDirty = true;
}
}

View file

@ -5,26 +5,37 @@ import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.world.level.LightLayer;
/**
* Implementors of this interface may choose to subscribe to light updates by calling
* {@link LightUpdater#addListener(LightListener)}.<p>
*
* It is the responsibility of the implementor to keep a reference to the level an object is contained in.
*/
public interface LightListener {
ImmutableBox getVolume();
ListenerStatus status();
/**
* Check the status of the light listener.
* @return {@code true} if the listener is invalid/removed/deleted,
* and should no longer receive updates.
*/
boolean isListenerInvalid();
/**
* Called when a light updates in a chunk the implementor cares about.
*/
void onLightUpdate(LightProvider world, LightLayer type, ImmutableBox changed);
void onLightUpdate(LightLayer type, ImmutableBox changed);
/**
* Called when the server sends light data to the client.
*
*/
default void onLightPacket(LightProvider world, int chunkX, int chunkZ) {
default void onLightPacket(int chunkX, int chunkZ) {
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
onLightUpdate(world, LightLayer.BLOCK, changedVolume);
onLightUpdate(LightLayer.BLOCK, changedVolume);
onLightUpdate(world, LightLayer.SKY, changedVolume);
onLightUpdate(LightLayer.SKY, changedVolume);
}
}

View file

@ -1,12 +0,0 @@
package com.jozufozu.flywheel.light;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.world.level.LightLayer;
public interface LightProvider {
int getLight(LightLayer type, int x, int y, int z);
default int getPackedLight(int x, int y, int z) {
return LightTexture.pack(getLight(LightLayer.BLOCK, x, y, z), getLight(LightLayer.SKY, x, y, z));
}
}

View file

@ -39,19 +39,15 @@ public class LightUpdater {
}
}
private final LightProvider provider;
private final LevelAccessor level;
private final WeakHashSet<MovingListener> movingListeners = new WeakHashSet<>();
private final WeakHashSet<TickingLightListener> tickingLightListeners = new WeakHashSet<>();
private final WeakContainmentMultiMap<LightListener> sections = new WeakContainmentMultiMap<>();
private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>();
public LightUpdater(LevelAccessor world) {
public LightUpdater(LevelAccessor level) {
taskEngine = Backend.getTaskEngine();
provider = new BasicProvider(world);
}
public LightProvider getProvider() {
return provider;
this.level = level;
}
public void tick() {
@ -60,9 +56,9 @@ public class LightUpdater {
}
private void tickSerial() {
for (MovingListener movingListener : movingListeners) {
if (movingListener.update(provider)) {
addListener(movingListener);
for (TickingLightListener tickingLightListener : tickingLightListeners) {
if (tickingLightListener.tickLightListener()) {
addListener(tickingLightListener);
}
}
}
@ -71,8 +67,8 @@ public class LightUpdater {
Queue<LightListener> listeners = new ConcurrentLinkedQueue<>();
taskEngine.group("LightUpdater")
.addTasks(movingListeners.stream(), listener -> {
if (listener.update(provider)) {
.addTasks(tickingLightListeners.stream(), listener -> {
if (listener.tickLightListener()) {
listeners.add(listener);
}
})
@ -86,8 +82,8 @@ public class LightUpdater {
* @param listener The object that wants to receive light update notifications.
*/
public void addListener(LightListener listener) {
if (listener instanceof MovingListener)
movingListeners.add(((MovingListener) listener));
if (listener instanceof TickingLightListener)
tickingLightListeners.add(((TickingLightListener) listener));
ImmutableBox box = listener.getVolume();
@ -130,12 +126,12 @@ public class LightUpdater {
if (set == null || set.isEmpty()) return;
set.removeIf(l -> l.status().shouldRemove());
set.removeIf(LightListener::isListenerInvalid);
ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
for (LightListener listener : set) {
listener.onLightUpdate(provider, type, chunkBox);
listener.onLightUpdate(type, chunkBox);
}
}
@ -151,10 +147,10 @@ public class LightUpdater {
if (set == null || set.isEmpty()) return;
set.removeIf(l -> l.status().shouldRemove());
set.removeIf(LightListener::isListenerInvalid);
for (LightListener listener : set) {
listener.onLightPacket(provider, chunkX, chunkZ);
listener.onLightPacket(chunkX, chunkZ);
}
}

View file

@ -8,14 +8,17 @@ import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
public class LightVolume implements ImmutableBox, LightListener {
protected final BlockAndTintGetter level;
protected final GridAlignedBB box = new GridAlignedBB();
protected ByteBuffer lightData;
public LightVolume(ImmutableBox sampleVolume) {
public LightVolume(BlockAndTintGetter level, ImmutableBox sampleVolume) {
this.level = level;
this.setBox(sampleVolume);
this.lightData = MemoryUtil.memAlloc(this.box.volume() * 2);
@ -57,7 +60,7 @@ public class LightVolume implements ImmutableBox, LightListener {
return box.getMaxZ();
}
public void move(LightProvider world, ImmutableBox newSampleVolume) {
public void move(BlockAndTintGetter level, ImmutableBox newSampleVolume) {
if (lightData == null) return;
setBox(newSampleVolume);
@ -65,51 +68,43 @@ public class LightVolume implements ImmutableBox, LightListener {
if (neededCapacity > lightData.capacity()) {
lightData = MemoryUtil.memRealloc(lightData, neededCapacity);
}
initialize(world);
initialize();
}
@Override
public void onLightUpdate(LightProvider world, LightLayer type, ImmutableBox changedVolume) {
public void onLightUpdate(LightLayer 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 == LightLayer.BLOCK) copyBlock(world, vol);
else if (type == LightLayer.SKY) copySky(world, vol);
if (type == LightLayer.BLOCK) copyBlock(vol);
else if (type == LightLayer.SKY) copySky(vol);
markDirty();
}
@Override
public void onLightPacket(LightProvider world, int chunkX, int chunkZ) {
public void onLightPacket(int chunkX, int chunkZ) {
if (lightData == null) return;
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
if (!changedVolume.intersects(getVolume())) return;
changedVolume.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
copyLight(world, changedVolume);
copyLight(changedVolume);
markDirty();
}
/**
* Completely (re)populate this volume with block and sky lighting data.
* This is expensive and should be avoided.
*/
public void initialize(LightProvider world) {
public void initialize() {
if (lightData == null) return;
// the volume is indexed based on the greater bounding box
int shiftX = box.getMinX();
int shiftY = box.getMinY();
int shiftZ = box.getMinZ();
// ... but we only iterate over the (potentially) smaller sample volume
getVolume().forEachContained((x, y, z) -> {
int blockLight = world.getLight(LightLayer.BLOCK, x, y, z);
int skyLight = world.getLight(LightLayer.SKY, x, y, z);
writeLight(x - shiftX, y - shiftY, z - shiftZ, blockLight, skyLight);
});
copyLight(getVolume());
markDirty();
}
/**
@ -117,13 +112,15 @@ public class LightVolume implements ImmutableBox, LightListener {
*
* @param worldVolume the region in the world to copy data from.
*/
public void copyBlock(LightProvider world, ImmutableBox worldVolume) {
public void copyBlock(ImmutableBox worldVolume) {
var pos = new BlockPos.MutableBlockPos();
int xShift = box.getMinX();
int yShift = box.getMinY();
int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> {
int light = world.getLight(LightLayer.BLOCK, x, y, z);
int light = this.level.getBrightness(LightLayer.BLOCK, pos.set(x, y, z));
writeBlock(x - xShift, y - yShift, z - zShift, light);
});
@ -134,13 +131,15 @@ public class LightVolume implements ImmutableBox, LightListener {
*
* @param worldVolume the region in the world to copy data from.
*/
public void copySky(LightProvider world, ImmutableBox worldVolume) {
public void copySky(ImmutableBox worldVolume) {
var pos = new BlockPos.MutableBlockPos();
int xShift = box.getMinX();
int yShift = box.getMinY();
int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> {
int light = world.getLight(LightLayer.SKY, x, y, z);
int light = this.level.getBrightness(LightLayer.SKY, pos.set(x, y, z));
writeSky(x - xShift, y - yShift, z - zShift, light);
});
@ -151,7 +150,7 @@ public class LightVolume implements ImmutableBox, LightListener {
*
* @param worldVolume the region in the world to copy data from.
*/
public void copyLight(LightProvider world, ImmutableBox worldVolume) {
public void copyLight(ImmutableBox worldVolume) {
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
int xShift = box.getMinX();
@ -161,8 +160,8 @@ public class LightVolume implements ImmutableBox, LightListener {
worldVolume.forEachContained((x, y, z) -> {
pos.set(x, y, z);
int block = world.getLight(LightLayer.BLOCK, x, y, z);
int sky = world.getLight(LightLayer.SKY, x, y, z);
int block = this.level.getBrightness(LightLayer.BLOCK, pos);
int sky = this.level.getBrightness(LightLayer.SKY, pos);
writeLight(x - xShift, y - yShift, z - zShift, block, sky);
});
@ -173,6 +172,10 @@ public class LightVolume implements ImmutableBox, LightListener {
lightData = null;
}
protected void markDirty() {
// noop
}
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);
@ -211,8 +214,8 @@ public class LightVolume implements ImmutableBox, LightListener {
}
@Override
public ListenerStatus status() {
return ListenerStatus.OKAY;
public boolean isListenerInvalid() {
return lightData == null;
}
}

View file

@ -1,16 +0,0 @@
package com.jozufozu.flywheel.light;
public enum ListenerStatus {
OKAY,
REMOVE,
UPDATE,
;
public boolean isOk() {
return this == OKAY;
}
public boolean shouldRemove() {
return this == REMOVE;
}
}

View file

@ -1,5 +0,0 @@
package com.jozufozu.flywheel.light;
public interface MovingListener extends LightListener {
boolean update(LightProvider provider);
}

View file

@ -0,0 +1,9 @@
package com.jozufozu.flywheel.light;
public interface TickingLightListener extends LightListener {
/**
* Called every tick for active listeners.
* @return {@code true} if the listener changed.
*/
boolean tickLightListener();
}

View file

@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.light;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;