0
0
Fork 0
mirror of https://github.com/Jozufozu/Flywheel.git synced 2025-01-18 17:07:52 +01:00

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 org.gradle.daemon = false
# mod version info # mod version info
mod_version = 0.6.3 mod_version = 0.6.4
mc_update_version = 1.18 mc_update_version = 1.18
minecraft_version = 1.18.2 minecraft_version = 1.18.2
forge_version = 40.0.15 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.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.core.materials.FlatLit; import com.jozufozu.flywheel.core.materials.FlatLit;
import com.jozufozu.flywheel.light.LightListener; 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 com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -26,6 +24,7 @@ public abstract class AbstractInstance implements Instance, LightListener {
protected final MaterialManager materialManager; protected final MaterialManager materialManager;
public final Level world; public final Level world;
protected boolean removed = false;
public AbstractInstance(MaterialManager materialManager, Level world) { public AbstractInstance(MaterialManager materialManager, Level world) {
this.materialManager = materialManager; 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. * 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. * 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 @Override
public ListenerStatus status() { public boolean isListenerInvalid() {
return ListenerStatus.OKAY; return removed;
} }
@Override @Override
public void onLightUpdate(LightProvider world, LightLayer type, ImmutableBox changed) { public void onLightUpdate(LightLayer type, ImmutableBox changed) {
updateLight(); updateLight();
} }
@ -103,4 +111,5 @@ public abstract class AbstractInstance implements Instance, LightListener {
models.forEach(model -> model.setBlockLight(block) models.forEach(model -> model.setBlockLight(block)
.setSkyLight(sky)); .setSkyLight(sky));
} }
} }

View file

@ -260,7 +260,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
} }
public void invalidate() { public void invalidate() {
instances.values().forEach(AbstractInstance::remove); instances.values().forEach(AbstractInstance::removeAndMark);
instances.clear(); instances.clear();
dynamicInstances.clear(); dynamicInstances.clear();
tickableInstances.clear(); tickableInstances.clear();
@ -314,7 +314,7 @@ public abstract class InstanceManager<T> implements InstancingEngine.OriginShift
} }
protected void removeInternal(T obj, AbstractInstance instance) { protected void removeInternal(T obj, AbstractInstance instance) {
instance.remove(); instance.removeAndMark();
instances.remove(obj); instances.remove(obj);
dynamicInstances.remove(obj); dynamicInstances.remove(obj);
tickableInstances.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.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.light.LightListener; import com.jozufozu.flywheel.light.LightListener;
import com.jozufozu.flywheel.light.LightProvider; import com.jozufozu.flywheel.light.TickingLightListener;
import com.jozufozu.flywheel.light.MovingListener;
import com.jozufozu.flywheel.util.box.GridAlignedBB; import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.mojang.math.Vector3f; 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. * @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 E entity;
protected final GridAlignedBB bounds; protected final GridAlignedBB bounds;
@ -51,7 +50,7 @@ public abstract class EntityInstance<E extends Entity> extends AbstractInstance
} }
@Override @Override
public boolean update(LightProvider provider) { public boolean tickLightListener() {
AABB boundsNow = entity.getBoundingBox(); AABB boundsNow = entity.getBoundingBox();
if (bounds.sameAs(boundsNow)) return false; 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.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox; import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.LightLayer;
public class GPULightVolume extends LightVolume { public class GPULightVolume extends LightVolume {
@ -36,8 +37,8 @@ public class GPULightVolume extends LightVolume {
private final GlTextureUnit textureUnit = GlTextureUnit.T4; private final GlTextureUnit textureUnit = GlTextureUnit.T4;
protected boolean bufferDirty; protected boolean bufferDirty;
public GPULightVolume(ImmutableBox sampleVolume) { public GPULightVolume(BlockAndTintGetter level, ImmutableBox sampleVolume) {
super(sampleVolume); super(level, sampleVolume);
this.sampleVolume.assign(sampleVolume); this.sampleVolume.assign(sampleVolume);
glTexture = new GlTexture(GL_TEXTURE_3D); glTexture = new GlTexture(GL_TEXTURE_3D);
@ -110,38 +111,24 @@ public class GPULightVolume extends LightVolume {
glTexture.delete(); glTexture.delete();
} }
public void move(LightProvider world, ImmutableBox newSampleVolume) { public void move(BlockAndTintGetter level, ImmutableBox newSampleVolume) {
if (lightData == null) return; if (lightData == null) return;
if (box.contains(newSampleVolume)) { if (box.contains(newSampleVolume)) {
sampleVolume.assign(newSampleVolume); sampleVolume.assign(newSampleVolume);
initialize(world); initialize();
} else { } 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 @Override
public ImmutableBox getVolume() { public ImmutableBox getVolume() {
return sampleVolume; 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; 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 { public interface LightListener {
ImmutableBox getVolume(); 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. * 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. * 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); 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> sections = new WeakContainmentMultiMap<>();
private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>(); private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>();
public LightUpdater(LevelAccessor world) { public LightUpdater(LevelAccessor level) {
taskEngine = Backend.getTaskEngine(); taskEngine = Backend.getTaskEngine();
provider = new BasicProvider(world); this.level = level;
}
public LightProvider getProvider() {
return provider;
} }
public void tick() { public void tick() {
@ -60,9 +56,9 @@ public class LightUpdater {
} }
private void tickSerial() { private void tickSerial() {
for (MovingListener movingListener : movingListeners) { for (TickingLightListener tickingLightListener : tickingLightListeners) {
if (movingListener.update(provider)) { if (tickingLightListener.tickLightListener()) {
addListener(movingListener); addListener(tickingLightListener);
} }
} }
} }
@ -71,8 +67,8 @@ public class LightUpdater {
Queue<LightListener> listeners = new ConcurrentLinkedQueue<>(); Queue<LightListener> listeners = new ConcurrentLinkedQueue<>();
taskEngine.group("LightUpdater") taskEngine.group("LightUpdater")
.addTasks(movingListeners.stream(), listener -> { .addTasks(tickingLightListeners.stream(), listener -> {
if (listener.update(provider)) { if (listener.tickLightListener()) {
listeners.add(listener); listeners.add(listener);
} }
}) })
@ -86,8 +82,8 @@ public class LightUpdater {
* @param listener The object that wants to receive light update notifications. * @param listener The object that wants to receive light update notifications.
*/ */
public void addListener(LightListener listener) { public void addListener(LightListener listener) {
if (listener instanceof MovingListener) if (listener instanceof TickingLightListener)
movingListeners.add(((MovingListener) listener)); tickingLightListeners.add(((TickingLightListener) listener));
ImmutableBox box = listener.getVolume(); ImmutableBox box = listener.getVolume();
@ -130,12 +126,12 @@ public class LightUpdater {
if (set == null || set.isEmpty()) return; if (set == null || set.isEmpty()) return;
set.removeIf(l -> l.status().shouldRemove()); set.removeIf(LightListener::isListenerInvalid);
ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos)); ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
for (LightListener listener : set) { 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; if (set == null || set.isEmpty()) return;
set.removeIf(l -> l.status().shouldRemove()); set.removeIf(LightListener::isListenerInvalid);
for (LightListener listener : set) { 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 com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.LightLayer;
public class LightVolume implements ImmutableBox, LightListener { public class LightVolume implements ImmutableBox, LightListener {
protected final BlockAndTintGetter level;
protected final GridAlignedBB box = new GridAlignedBB(); protected final GridAlignedBB box = new GridAlignedBB();
protected ByteBuffer lightData; protected ByteBuffer lightData;
public LightVolume(ImmutableBox sampleVolume) { public LightVolume(BlockAndTintGetter level, ImmutableBox sampleVolume) {
this.level = level;
this.setBox(sampleVolume); this.setBox(sampleVolume);
this.lightData = MemoryUtil.memAlloc(this.box.volume() * 2); this.lightData = MemoryUtil.memAlloc(this.box.volume() * 2);
@ -57,7 +60,7 @@ public class LightVolume implements ImmutableBox, LightListener {
return box.getMaxZ(); return box.getMaxZ();
} }
public void move(LightProvider world, ImmutableBox newSampleVolume) { public void move(BlockAndTintGetter level, ImmutableBox newSampleVolume) {
if (lightData == null) return; if (lightData == null) return;
setBox(newSampleVolume); setBox(newSampleVolume);
@ -65,51 +68,43 @@ public class LightVolume implements ImmutableBox, LightListener {
if (neededCapacity > lightData.capacity()) { if (neededCapacity > lightData.capacity()) {
lightData = MemoryUtil.memRealloc(lightData, neededCapacity); lightData = MemoryUtil.memRealloc(lightData, neededCapacity);
} }
initialize(world); initialize();
} }
@Override @Override
public void onLightUpdate(LightProvider world, LightLayer type, ImmutableBox changedVolume) { public void onLightUpdate(LightLayer type, ImmutableBox changedVolume) {
if (lightData == null) return; if (lightData == null) return;
GridAlignedBB vol = changedVolume.copy(); GridAlignedBB vol = changedVolume.copy();
if (!vol.intersects(getVolume())) return; if (!vol.intersects(getVolume())) return;
vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data. vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
if (type == LightLayer.BLOCK) copyBlock(world, vol); if (type == LightLayer.BLOCK) copyBlock(vol);
else if (type == LightLayer.SKY) copySky(world, vol); else if (type == LightLayer.SKY) copySky(vol);
markDirty();
} }
@Override @Override
public void onLightPacket(LightProvider world, int chunkX, int chunkZ) { public void onLightPacket(int chunkX, int chunkZ) {
if (lightData == null) return; if (lightData == null) return;
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ); GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
if (!changedVolume.intersects(getVolume())) return; if (!changedVolume.intersects(getVolume())) return;
changedVolume.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data. 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. * Completely (re)populate this volume with block and sky lighting data.
* This is expensive and should be avoided. * This is expensive and should be avoided.
*/ */
public void initialize(LightProvider world) { public void initialize() {
if (lightData == null) return; if (lightData == null) return;
// the volume is indexed based on the greater bounding box copyLight(getVolume());
int shiftX = box.getMinX(); markDirty();
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);
});
} }
/** /**
@ -117,13 +112,15 @@ public class LightVolume implements ImmutableBox, LightListener {
* *
* @param worldVolume the region in the world to copy data from. * @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 xShift = box.getMinX();
int yShift = box.getMinY(); int yShift = box.getMinY();
int zShift = box.getMinZ(); int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> { 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); 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. * @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 xShift = box.getMinX();
int yShift = box.getMinY(); int yShift = box.getMinY();
int zShift = box.getMinZ(); int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> { 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); 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. * @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(); BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
int xShift = box.getMinX(); int xShift = box.getMinX();
@ -161,8 +160,8 @@ public class LightVolume implements ImmutableBox, LightListener {
worldVolume.forEachContained((x, y, z) -> { worldVolume.forEachContained((x, y, z) -> {
pos.set(x, y, z); pos.set(x, y, z);
int block = world.getLight(LightLayer.BLOCK, x, y, z); int block = this.level.getBrightness(LightLayer.BLOCK, pos);
int sky = world.getLight(LightLayer.SKY, x, y, z); int sky = this.level.getBrightness(LightLayer.SKY, pos);
writeLight(x - xShift, y - yShift, z - zShift, block, sky); writeLight(x - xShift, y - yShift, z - zShift, block, sky);
}); });
@ -173,6 +172,10 @@ public class LightVolume implements ImmutableBox, LightListener {
lightData = null; lightData = null;
} }
protected void markDirty() {
// noop
}
protected 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 b = (byte) ((block & 0xF) << 4);
byte s = (byte) ((sky & 0xF) << 4); byte s = (byte) ((sky & 0xF) << 4);
@ -211,8 +214,8 @@ public class LightVolume implements ImmutableBox, LightListener {
} }
@Override @Override
public ListenerStatus status() { public boolean isListenerInvalid() {
return ListenerStatus.OKAY; 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;