From 7ca4ea5c3efd7a53ad55dfe2cd6926e415c8384b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 29 Aug 2021 14:40:46 -0700 Subject: [PATCH] Light update convergence - Move light update logic for all instances to use LightUpdater - Begin refactor of LightUpdater to account for moving listeners --- .../backend/instancing/AbstractInstance.java | 97 ++++++++++ .../backend/instancing/IInstance.java | 26 +-- .../backend/instancing/InstanceManager.java | 33 ++-- .../instancing/InstancedRenderDispatcher.java | 5 +- .../instancing/entity/EntityInstance.java | 83 ++------ .../entity/EntityInstanceManager.java | 4 +- .../instancing/tile/TileEntityInstance.java | 62 +----- .../instancing/tile/TileInstanceManager.java | 4 +- .../jozufozu/flywheel/event/ForgeEvents.java | 9 + .../flywheel/light/GridAlignedBB.java | 4 + .../flywheel/light/ILightUpdateListener.java | 21 +- .../jozufozu/flywheel/light/LightUpdater.java | 179 ++++++------------ .../flywheel/light/ListenerStatus.java | 16 ++ .../com/jozufozu/flywheel/light/Volume.java | 35 ++++ .../light/WeakContainmentMultiMap.java | 56 ++++++ .../mixin/light/LightUpdateMixin.java | 19 -- .../mixin/light/NetworkLightUpdateMixin.java | 16 -- .../jozufozu/flywheel/util/WeakHashSet.java | 6 +- .../flywheel/vanilla/MinecartInstance.java | 23 ++- 19 files changed, 358 insertions(+), 340 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java create mode 100644 src/main/java/com/jozufozu/flywheel/light/ListenerStatus.java create mode 100644 src/main/java/com/jozufozu/flywheel/light/Volume.java create mode 100644 src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java new file mode 100644 index 000000000..8fbfdc2ef --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/AbstractInstance.java @@ -0,0 +1,97 @@ +package com.jozufozu.flywheel.backend.instancing; + +import java.util.Arrays; +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.LightUpdater; +import com.jozufozu.flywheel.light.ListenerStatus; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockDisplayReader; +import net.minecraft.world.LightType; +import net.minecraft.world.World; + +/** + * A general interface providing information about any type of thing that could use Flywheel's instanced rendering. + * Right now, that's only {@link TileInstanceManager}, but there could be an entity equivalent in the future. + */ +public abstract class AbstractInstance implements IInstance, ILightUpdateListener { + + protected final MaterialManager materialManager; + protected final World world; + + public AbstractInstance(MaterialManager materialManager, World world) { + this.materialManager = materialManager; + this.world = world; + } + + /** + * Free any acquired resources. + */ + public abstract void remove(); + + /** + * Update instance data here. Good for when data doesn't change very often and when animations are GPU based. + * Don't query lighting data here, that's handled separately in {@link #updateLight()}. + * + *

If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}. + */ + public void update() { + } + + /** + * Called after construction and when a light update occurs in the world. + * + *
If your model needs it, update light here. + */ + public void updateLight() { + } + + /** + * When an instance is reset, the instance is deleted and re-created. + * + *

+ * Just before {@link #update()} would be called, shouldReset() is checked. + * If this function returns true, then this instance will be {@link #remove 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. + *

+ * + * @return true if this instance should be discarded and refreshed. + */ + public boolean shouldReset() { + return false; + } + + @Override + public ListenerStatus status() { + return ListenerStatus.OKAY; + } + + @Override + public void onLightUpdate(IBlockDisplayReader world, LightType type, GridAlignedBB changed) { + updateLight(); + } + + protected void relight(BlockPos pos, IFlatLight... models) { + relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models); + } + + protected > void relight(BlockPos pos, Stream models) { + relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models); + } + + protected void relight(int block, int sky, IFlatLight... models) { + relight(block, sky, Arrays.stream(models)); + } + + protected > void relight(int block, int sky, Stream models) { + models.forEach(model -> model.setBlockLight(block) + .setSkyLight(sky)); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/IInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/IInstance.java index e3b969f7a..ceeb2d6dc 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/IInstance.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/IInstance.java @@ -1,31 +1,7 @@ package com.jozufozu.flywheel.backend.instancing; -import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager; - import net.minecraft.util.math.BlockPos; -/** - * A general interface providing information about any type of thing that could use Flywheel's instanced rendering. - * Right now, that's only {@link TileInstanceManager}, but there could be an entity equivalent in the future. - */ public interface IInstance { - - BlockPos getWorldPosition(); - - void updateLight(); - - void remove(); - - /** - * When an instance is reset, the instance is deleted and re-created. - * - *

- * This is used to handle things like block state changes. - *

- * - * @return true if this instance should be reset - */ - boolean shouldReset(); - - void update(); + BlockPos getWorldPosition(); } 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 113a4d83e..69f87bf1c 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java @@ -25,7 +25,7 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh private final Set queuedAdditions; private final Set queuedUpdates; - protected final Map instances; + protected final Map instances; protected final Object2ObjectOpenHashMap tickableInstances; protected final Object2ObjectOpenHashMap dynamicInstances; @@ -63,7 +63,7 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh protected abstract boolean canCreateInstance(T obj); @Nullable - protected abstract IInstance createRaw(T obj); + protected abstract AbstractInstance createRaw(T obj); /** * Ticks the InstanceManager. @@ -169,7 +169,7 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh .canUseInstancing()) return; if (canInstance(obj)) { - IInstance instance = getInstance(obj, false); + AbstractInstance instance = getInstance(obj, false); if (instance != null) { @@ -186,40 +186,29 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh } } - public void onLightUpdate(T obj) { - if (!Backend.getInstance() - .canUseInstancing()) return; - - if (canInstance(obj)) { - IInstance instance = getInstance(obj, false); - - if (instance != null) instance.updateLight(); - } - } - public void remove(T obj) { if (!Backend.getInstance() .canUseInstancing()) return; if (canInstance(obj)) { - IInstance instance = getInstance(obj, false); + AbstractInstance instance = getInstance(obj, false); if (instance != null) removeInternal(obj, instance); } } public void invalidate() { - instances.values().forEach(IInstance::remove); + instances.values().forEach(AbstractInstance::remove); instances.clear(); dynamicInstances.clear(); tickableInstances.clear(); } @Nullable - protected IInstance getInstance(I obj, boolean create) { + protected AbstractInstance getInstance(I obj, boolean create) { if (!Backend.getInstance() .canUseInstancing()) return null; - IInstance instance = instances.get(obj); + AbstractInstance instance = instances.get(obj); if (instance != null) { return instance; @@ -283,18 +272,20 @@ public abstract class InstanceManager implements MaterialManagerImpl.OriginSh getInstance(tile, true); } - protected void removeInternal(T obj, IInstance instance) { + protected void removeInternal(T obj, AbstractInstance instance) { instance.remove(); instances.remove(obj); dynamicInstances.remove(obj); tickableInstances.remove(obj); } - protected IInstance createInternal(T obj) { - IInstance renderer = createRaw(obj); + @Nullable + protected AbstractInstance createInternal(T obj) { + AbstractInstance renderer = createRaw(obj); if (renderer != null) { renderer.updateLight(); + renderer.startListening(); instances.put(obj, renderer); if (renderer instanceof IDynamicInstance) dynamicInstances.put(obj, (IDynamicInstance) renderer); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java index 708582ae5..32810d03d 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstancedRenderDispatcher.java @@ -3,7 +3,6 @@ package com.jozufozu.flywheel.backend.instancing; import javax.annotation.Nonnull; import com.jozufozu.flywheel.backend.Backend; -import com.jozufozu.flywheel.backend.state.RenderLayer; import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.event.RenderLayerEvent; @@ -28,7 +27,7 @@ public class InstancedRenderDispatcher { private static final WorldAttached instanceWorlds = new WorldAttached<>($ -> new InstanceWorld()); /** - * Call this when you want to manually run {@link IInstance#update()}. + * Call this when you want to manually run {@link AbstractInstance#update()}. * @param te The tile whose instance you want to update. */ public static void enqueueUpdate(TileEntity te) { @@ -36,7 +35,7 @@ public class InstancedRenderDispatcher { } /** - * Call this when you want to manually run {@link IInstance#update()}. + * Call this when you want to manually run {@link AbstractInstance#update()}. * @param entity The entity whose instance you want to update. */ public static void enqueueUpdate(Entity entity) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java index dd19d80b7..5fbdcd6bd 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstance.java @@ -4,15 +4,13 @@ import java.util.Arrays; import java.util.stream.Stream; import com.jozufozu.flywheel.backend.instancing.IDynamicInstance; -import com.jozufozu.flywheel.backend.instancing.IInstance; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; import com.jozufozu.flywheel.backend.instancing.ITickableInstance; import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager; -import com.jozufozu.flywheel.backend.material.InstanceMaterial; import com.jozufozu.flywheel.backend.material.MaterialManager; -import com.jozufozu.flywheel.core.Materials; -import com.jozufozu.flywheel.core.materials.IFlatLight; -import com.jozufozu.flywheel.core.materials.ModelData; -import com.jozufozu.flywheel.core.materials.OrientedData; +import com.jozufozu.flywheel.light.ILightUpdateListener; +import com.jozufozu.flywheel.light.ListenerStatus; +import com.jozufozu.flywheel.light.Volume; import net.minecraft.entity.Entity; import net.minecraft.tileentity.TileEntity; @@ -21,8 +19,6 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.util.math.vector.Vector3f; import net.minecraft.util.math.vector.Vector3i; -import net.minecraft.world.LightType; -import net.minecraft.world.World; /** * The layer between a {@link TileEntity} and the Flywheel backend. @@ -39,50 +35,25 @@ import net.minecraft.world.World; * * @param The type of {@link Entity} your class is an instance of. */ -public abstract class EntityInstance implements IInstance { +public abstract class EntityInstance extends AbstractInstance implements ILightUpdateListener { - protected final MaterialManager materialManager; protected final E entity; - protected final World world; public EntityInstance(MaterialManager materialManager, E entity) { - this.materialManager = materialManager; + super(materialManager, entity.level); this.entity = entity; - this.world = entity.level; + + startListening(); } - /** - * Free any acquired resources. - */ - public abstract void remove(); - - /** - * Update instance data here. Good for when data doesn't change very often and when animations are GPU based. - * Don't query lighting data here, that's handled separately in {@link #updateLight()}. - * - *

If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}. - */ - public void update() { + @Override + public Volume.Box getVolume() { + return Volume.box(entity.getBoundingBox()); } - /** - * Called after construction and when a light update occurs in the world. - * - *
If your model needs it, update light here. - */ - public void updateLight() { - } - - /** - * Just before {@link #update()} would be called, shouldReset() is checked. - * If this function returns true, then this instance will be {@link #remove 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. - * - * @return true if this instance should be discarded and refreshed. - */ - public boolean shouldReset() { - return false; + @Override + public ListenerStatus status() { + return ListenerStatus.UPDATE; } /** @@ -119,30 +90,4 @@ public abstract class EntityInstance implements IInstance { public BlockPos getWorldPosition() { return entity.blockPosition(); } - - protected void relight(BlockPos pos, IFlatLight... models) { - relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models); - } - - protected > void relight(BlockPos pos, Stream models) { - relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models); - } - - protected void relight(int block, int sky, IFlatLight... models) { - relight(block, sky, Arrays.stream(models)); - } - - protected > void relight(int block, int sky, Stream models) { - models.forEach(model -> model.setBlockLight(block) - .setSkyLight(sky)); - } - - protected InstanceMaterial getTransformMaterial() { - return materialManager.defaultSolid().material(Materials.TRANSFORMED); - } - - protected InstanceMaterial getOrientedMaterial() { - return materialManager.defaultSolid().material(Materials.ORIENTED); - } - } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java index d72100e62..a34c7f0a7 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java @@ -1,7 +1,7 @@ package com.jozufozu.flywheel.backend.instancing.entity; import com.jozufozu.flywheel.backend.Backend; -import com.jozufozu.flywheel.backend.instancing.IInstance; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; import com.jozufozu.flywheel.backend.material.MaterialManagerImpl; @@ -23,7 +23,7 @@ public class EntityInstanceManager extends InstanceManager { } @Override - protected IInstance createRaw(Entity obj) { + protected AbstractInstance createRaw(Entity obj) { return InstancedRenderRegistry.getInstance() .create(materialManager, obj); } 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 9e94753ab..876035a34 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 @@ -1,24 +1,19 @@ package com.jozufozu.flywheel.backend.instancing.tile; -import java.util.Arrays; -import java.util.stream.Stream; - import com.jozufozu.flywheel.backend.instancing.IDynamicInstance; -import com.jozufozu.flywheel.backend.instancing.IInstance; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; import com.jozufozu.flywheel.backend.instancing.ITickableInstance; import com.jozufozu.flywheel.backend.material.InstanceMaterial; import com.jozufozu.flywheel.backend.material.MaterialManager; -import com.jozufozu.flywheel.backend.material.MaterialManagerImpl; import com.jozufozu.flywheel.core.Materials; -import com.jozufozu.flywheel.core.materials.IFlatLight; 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.Volume; import net.minecraft.block.BlockState; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; -import net.minecraft.world.LightType; -import net.minecraft.world.World; /** * The layer between a {@link TileEntity} and the Flywheel backend. @@ -37,46 +32,21 @@ import net.minecraft.world.World; * * @param The type of {@link TileEntity} your class is an instance of. */ -public abstract class TileEntityInstance implements IInstance { +public abstract class TileEntityInstance extends AbstractInstance { - protected final MaterialManager materialManager; protected final T tile; - protected final World world; protected final BlockPos pos; protected final BlockPos instancePos; protected final BlockState blockState; public TileEntityInstance(MaterialManager materialManager, T tile) { - this.materialManager = materialManager; + super(materialManager, tile.getLevel()); this.tile = tile; - this.world = tile.getLevel(); this.pos = tile.getBlockPos(); this.blockState = tile.getBlockState(); this.instancePos = pos.subtract(materialManager.getOriginCoordinate()); } - /** - * Update instance data here. Good for when data doesn't change very often and when animations are GPU based. - * Don't query lighting data here, that's handled separately in {@link #updateLight()}. - * - *

If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}. - */ - public void update() { - } - - /** - * Called after construction and when a light update occurs in the world. - * - *
If your model needs it, update light here. - */ - public void updateLight() { - } - - /** - * Free any acquired resources. - */ - public abstract void remove(); - /** * Just before {@link #update()} would be called, shouldReset() is checked. * If this function returns true, then this instance will be {@link #remove removed}, @@ -106,23 +76,6 @@ public abstract class TileEntityInstance implements IInsta return pos; } - protected void relight(BlockPos pos, IFlatLight... models) { - relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models); - } - - protected > void relight(BlockPos pos, Stream models) { - relight(world.getBrightness(LightType.BLOCK, pos), world.getBrightness(LightType.SKY, pos), models); - } - - protected void relight(int block, int sky, IFlatLight... models) { - relight(block, sky, Arrays.stream(models)); - } - - protected > void relight(int block, int sky, Stream models) { - models.forEach(model -> model.setBlockLight(block) - .setSkyLight(sky)); - } - protected InstanceMaterial getTransformMaterial() { return materialManager.defaultCutout().material(Materials.TRANSFORMED); } @@ -130,4 +83,9 @@ public abstract class TileEntityInstance implements IInsta protected InstanceMaterial getOrientedMaterial() { return materialManager.defaultCutout().material(Materials.ORIENTED); } + + @Override + public Volume.Block getVolume() { + return Volume.block(pos); + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileInstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileInstanceManager.java index 2e4080ad4..4b2b05275 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileInstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/tile/TileInstanceManager.java @@ -1,7 +1,7 @@ package com.jozufozu.flywheel.backend.instancing.tile; import com.jozufozu.flywheel.backend.Backend; -import com.jozufozu.flywheel.backend.instancing.IInstance; +import com.jozufozu.flywheel.backend.instancing.AbstractInstance; import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; import com.jozufozu.flywheel.backend.material.MaterialManagerImpl; @@ -23,7 +23,7 @@ public class TileInstanceManager extends InstanceManager { } @Override - protected IInstance createRaw(TileEntity obj) { + protected AbstractInstance createRaw(TileEntity obj) { return InstancedRenderRegistry.getInstance() .create(materialManager, obj); } diff --git a/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java b/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java index 0f6e9da62..13a8ad429 100644 --- a/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java +++ b/src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java @@ -4,12 +4,15 @@ import java.util.ArrayList; import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; +import com.jozufozu.flywheel.light.LightUpdater; import net.minecraft.client.Minecraft; import net.minecraft.client.world.ClientWorld; import net.minecraft.world.IWorld; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @@ -45,4 +48,10 @@ public class ForgeEvents { } } + @SubscribeEvent + public static void rwle(TickEvent.ClientTickEvent e) { + if (e.phase == TickEvent.Phase.END && Backend.isGameActive()) + LightUpdater.getInstance().tick(); + } + } diff --git a/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java b/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java index 030ea78f6..cdecc44af 100644 --- a/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java +++ b/src/main/java/com/jozufozu/flywheel/light/GridAlignedBB.java @@ -53,6 +53,10 @@ public class GridAlignedBB { return new GridAlignedBB(start.getX(), start.getY(), start.getZ(), end.getX() + 1, end.getY() + 1, end.getZ() + 1); } + public static GridAlignedBB from(BlockPos pos) { + return new GridAlignedBB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1); + } + public static GridAlignedBB from(int sectionX, int sectionZ) { int startX = sectionX << 4; int startZ = sectionZ << 4; diff --git a/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java b/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java index 1ee9503a0..13f890afc 100644 --- a/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java +++ b/src/main/java/com/jozufozu/flywheel/light/ILightUpdateListener.java @@ -4,28 +4,33 @@ import net.minecraft.world.IBlockDisplayReader; import net.minecraft.world.LightType; /** - * Anything can implement this, implementors should call {@link LightUpdater#startListening} + * Anything can implement this, implementors should call {@link #startListening} * appropriately to make sure they get the updates they want. */ public interface ILightUpdateListener { + Volume getVolume(); + + ListenerStatus status(); + + default void startListening() { + LightUpdater.getInstance().addListener(this); + } + /** * Called when a light updates in a chunk the implementor cares about. - * - * @return true if this object is no longer valid and should not receive any more updates. */ - boolean onLightUpdate(IBlockDisplayReader world, LightType type, GridAlignedBB changed); + void onLightUpdate(IBlockDisplayReader world, LightType type, GridAlignedBB changed); /** * Called when the server sends light data to the client. * - * @return true if this object is no longer valid and should not receive any more updates. */ - default boolean onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) { + default void onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) { GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ); - if (onLightUpdate(world, LightType.BLOCK, changedVolume)) return true; + onLightUpdate(world, LightType.BLOCK, changedVolume); - return onLightUpdate(world, LightType.SKY, changedVolume); + onLightUpdate(world, LightType.SKY, changedVolume); } } diff --git a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java index df8956e4a..e1ff6c989 100644 --- a/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/light/LightUpdater.java @@ -1,27 +1,16 @@ package com.jozufozu.flywheel.light; -import java.util.WeakHashMap; -import java.util.function.LongConsumer; +import java.util.Set; import com.jozufozu.flywheel.util.WeakHashSet; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongRBTreeSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.client.Minecraft; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.SectionPos; import net.minecraft.world.IBlockDisplayReader; import net.minecraft.world.LightType; -/** - * By using WeakReferences we can automatically remove listeners when they are garbage collected. - * This allows us to easily be more clever about how we store the listeners. Each listener is associated - * with 2 sets of longs indicating what chunks and sections each listener is in. Additionally, a reverse - * mapping is created to allow for fast lookups when light updates. The reverse mapping is more interesting, - * but {@link #listenersToSections}, and {@link #listenersToChunks} are used to know what sections and - * chunks we need to remove the listeners from if they re-subscribe. Otherwise, listeners could get updates - * they no longer care about. This is done in {@link #clearSections} and {@link #clearChunks} - */ public class LightUpdater { private static LightUpdater instance; @@ -32,72 +21,69 @@ public class LightUpdater { return instance; } - private final Long2ObjectMap> sections; - private final WeakHashMap listenersToSections; - - private final Long2ObjectMap> chunks; - private final WeakHashMap listenersToChunks; + private final WeakHashSet allListeners; + private final WeakContainmentMultiMap sections; + private final WeakContainmentMultiMap chunks; public LightUpdater() { - sections = new Long2ObjectOpenHashMap<>(); - listenersToSections = new WeakHashMap<>(); + allListeners = new WeakHashSet<>(); + sections = new WeakContainmentMultiMap<>(); + chunks = new WeakContainmentMultiMap<>(); + } - chunks = new Long2ObjectOpenHashMap<>(); - listenersToChunks = new WeakHashMap<>(); + public void tick() { + for (ILightUpdateListener listener : allListeners) { + if (listener.status() == ListenerStatus.UPDATE) { + addListener(listener); + + listener.onLightUpdate(Minecraft.getInstance().level, LightType.BLOCK, null); + } + } } /** - * Add a listener associated with the given {@link BlockPos}. - *

- * When a light update occurs in the chunk the position is contained in, - * {@link ILightUpdateListener#onLightUpdate} will be called. - * - * @param pos The position in the world that the listener cares about. + * Add a listener. + * @param listener The object that wants to receive light update notifications. */ - public void startListening(BlockPos pos, ILightUpdateListener listener) { - LongRBTreeSet sections = clearSections(listener); - LongRBTreeSet chunks = clearChunks(listener); + public void addListener(ILightUpdateListener listener) { + allListeners.add(listener); - long sectionPos = worldToSection(pos); - addToSection(sectionPos, listener); - sections.add(sectionPos); + Volume volume = listener.getVolume(); - long chunkPos = sectionToChunk(sectionPos); - addToChunk(chunkPos, listener); - chunks.add(chunkPos); - } + LongSet sections = this.sections.getAndResetContainment(listener); + LongSet chunks = this.chunks.getAndResetContainment(listener); - /** - * Add a listener associated with the given {@link GridAlignedBB}. - *

- * When a light update occurs in any chunk spanning the given volume, - * {@link ILightUpdateListener#onLightUpdate} will be called. - * - * @param volume The volume in the world that the listener cares about. - * @param listener The object that wants to receive light update notifications. - */ - public void startListening(GridAlignedBB volume, ILightUpdateListener listener) { - LongRBTreeSet sections = clearSections(listener); - LongRBTreeSet chunks = clearSections(listener); + if (volume instanceof Volume.Block) { + BlockPos pos = ((Volume.Block) volume).pos; + long sectionPos = blockToSection(pos); + this.sections.put(sectionPos, listener); + sections.add(sectionPos); - int minX = SectionPos.blockToSectionCoord(volume.minX); - int minY = SectionPos.blockToSectionCoord(volume.minY); - int minZ = SectionPos.blockToSectionCoord(volume.minZ); - int maxX = SectionPos.blockToSectionCoord(volume.maxX); - int maxY = SectionPos.blockToSectionCoord(volume.maxY); - int maxZ = SectionPos.blockToSectionCoord(volume.maxZ); + long chunkPos = sectionToChunk(sectionPos); + this.chunks.put(chunkPos, listener); + chunks.add(chunkPos); + } else if (volume instanceof Volume.Box) { + GridAlignedBB box = ((Volume.Box) volume).box; - for (int x = minX; x <= maxX; x++) { - for (int z = minZ; z <= maxZ; z++) { - for (int y = minY; y <= maxY; y++) { - long sectionPos = SectionPos.asLong(x, y, z); - addToSection(sectionPos, listener); - sections.add(sectionPos); + int minX = SectionPos.blockToSectionCoord(box.minX); + int minY = SectionPos.blockToSectionCoord(box.minY); + int minZ = SectionPos.blockToSectionCoord(box.minZ); + int maxX = SectionPos.blockToSectionCoord(box.maxX); + int maxY = SectionPos.blockToSectionCoord(box.maxY); + int maxZ = SectionPos.blockToSectionCoord(box.maxZ); + + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + for (int y = minY; y <= maxY; y++) { + long sectionPos = SectionPos.asLong(x, y, z); + this.sections.put(sectionPos, listener); + sections.add(sectionPos); + } + long chunkPos = SectionPos.asLong(x, 0, z); + this.chunks.put(chunkPos, listener); + chunks.add(chunkPos); } - long chunkPos = SectionPos.asLong(x, 0, z); - addToChunk(chunkPos, listener); - chunks.add(chunkPos); } } } @@ -110,13 +96,17 @@ public class LightUpdater { * @param sectionPos A long representing the section position where light changed. */ public void onLightUpdate(IBlockDisplayReader world, LightType type, long sectionPos) { - WeakHashSet set = sections.get(sectionPos); + Set set = sections.get(sectionPos); if (set == null || set.isEmpty()) return; + set.removeIf(l -> l.status().shouldRemove()); + GridAlignedBB chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos)); - set.removeIf(listener -> listener.onLightUpdate(world, type, chunkBox.copy())); + for (ILightUpdateListener listener : set) { + listener.onLightUpdate(world, type, chunkBox.copy()); + } } /** @@ -126,63 +116,20 @@ public class LightUpdater { * @param world The world in which light was updated. */ public void onLightPacket(IBlockDisplayReader world, int chunkX, int chunkZ) { - long chunkPos = SectionPos.asLong(chunkX, 0, chunkZ); - WeakHashSet set = chunks.get(chunkPos); + Set set = chunks.get(chunkPos); if (set == null || set.isEmpty()) return; - set.removeIf(listener -> listener.onLightPacket(world, chunkX, chunkZ)); - } + set.removeIf(l -> l.status().shouldRemove()); - private LongRBTreeSet clearChunks(ILightUpdateListener listener) { - return clear(listener, listenersToChunks, chunks); - } - - private LongRBTreeSet clearSections(ILightUpdateListener listener) { - return clear(listener, listenersToSections, sections); - } - - private LongRBTreeSet clear(ILightUpdateListener listener, WeakHashMap listeners, Long2ObjectMap> lookup) { - LongRBTreeSet set = listeners.get(listener); - - if (set == null) { - set = new LongRBTreeSet(); - listeners.put(listener, set); - } else { - set.forEach((LongConsumer) l -> { - WeakHashSet listeningSections = lookup.get(l); - - if (listeningSections != null) listeningSections.remove(listener); - }); - - set.clear(); + for (ILightUpdateListener listener : set) { + listener.onLightPacket(world, chunkX, chunkZ); } - - return set; } - private void addToSection(long sectionPos, ILightUpdateListener listener) { - getOrCreate(sections, sectionPos).add(listener); - } - - private void addToChunk(long chunkPos, ILightUpdateListener listener) { - getOrCreate(chunks, chunkPos).add(listener); - } - - private WeakHashSet getOrCreate(Long2ObjectMap> sections, long chunkPos) { - WeakHashSet set = sections.get(chunkPos); - - if (set == null) { - set = new WeakHashSet<>(); - sections.put(chunkPos, set); - } - - return set; - } - - public static long worldToSection(BlockPos pos) { + public static long blockToSection(BlockPos pos) { return SectionPos.asLong(pos.getX(), pos.getY(), pos.getZ()); } diff --git a/src/main/java/com/jozufozu/flywheel/light/ListenerStatus.java b/src/main/java/com/jozufozu/flywheel/light/ListenerStatus.java new file mode 100644 index 000000000..e8ca36d30 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/light/ListenerStatus.java @@ -0,0 +1,16 @@ +package com.jozufozu.flywheel.light; + +public enum ListenerStatus { + OKAY, + REMOVE, + UPDATE, + ; + + public boolean isOk() { + return this == OKAY; + } + + public boolean shouldRemove() { + return this == REMOVE; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/light/Volume.java b/src/main/java/com/jozufozu/flywheel/light/Volume.java new file mode 100644 index 000000000..b812bba71 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/light/Volume.java @@ -0,0 +1,35 @@ +package com.jozufozu.flywheel.light; + +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; + +public class Volume { + + public static Volume.Block block(BlockPos pos) { + return new Block(pos); + } + + public static Volume.Box box(GridAlignedBB box) { + return new Box(box); + } + + public static Volume.Box box(AxisAlignedBB box) { + return new Box(GridAlignedBB.from(box)); + } + + public static class Block extends Volume { + public final BlockPos pos; + + public Block(BlockPos pos) { + this.pos = pos; + } + } + + public static class Box extends Volume { + public final GridAlignedBB box; + + public Box(GridAlignedBB box) { + this.box = box; + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java b/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java new file mode 100644 index 000000000..944e75a75 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/light/WeakContainmentMultiMap.java @@ -0,0 +1,56 @@ +package com.jozufozu.flywheel.light; + +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.LongConsumer; + +import com.jozufozu.flywheel.util.WeakHashSet; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongRBTreeSet; +import it.unimi.dsi.fastutil.longs.LongSet; + +public class WeakContainmentMultiMap { + + private final Long2ObjectMap> forward; + private final WeakHashMap reverse; + + public WeakContainmentMultiMap() { + forward = new Long2ObjectOpenHashMap<>(); + reverse = new WeakHashMap<>(); + } + + /** + * This is a confusing function, but it maintains the internal state of the chunk/section maps. + * + *

+ * First, uses the reverse lookup map to remove listener from all sets in the lookup map.
+ * Then, clears the listeners containment set. + *

+ * + * @param listener The listener to clean up. + * @return An empty set that should be populated with the chunks/sections the listener is contained in. + */ + public LongSet getAndResetContainment(T listener) { + LongSet containmentSet = reverse.computeIfAbsent(listener, $ -> new LongRBTreeSet()); + + containmentSet.forEach((LongConsumer) l -> { + WeakHashSet listeners = forward.get(l); + + if (listeners != null) listeners.remove(listener); + }); + + containmentSet.clear(); + + return containmentSet; + } + + public Set get(long l) { + return forward.get(l); + } + + public void put(long sectionPos, T listener) { + forward.computeIfAbsent(sectionPos, $ -> new WeakHashSet<>()).add(listener); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/mixin/light/LightUpdateMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/light/LightUpdateMixin.java index 0c40a62ab..6a6ab7f73 100644 --- a/src/main/java/com/jozufozu/flywheel/mixin/light/LightUpdateMixin.java +++ b/src/main/java/com/jozufozu/flywheel/mixin/light/LightUpdateMixin.java @@ -38,25 +38,6 @@ public abstract class LightUpdateMixin extends AbstractChunkProvider { ClientChunkProvider thi = ((ClientChunkProvider) (Object) this); ClientWorld world = (ClientWorld) thi.getLevel(); - Chunk chunk = thi.getChunk(pos.x(), pos.z(), false); - - int sectionY = pos.y(); - - if (ChunkUtil.isValidSection(chunk, sectionY)) { - InstanceManager tiles = InstancedRenderDispatcher.getTiles(world); - InstanceManager entities = InstancedRenderDispatcher.getEntities(world); - - chunk.getBlockEntities() - .entrySet() - .stream() - .filter(entry -> SectionPos.blockToSectionCoord(entry.getKey() - .getY()) == sectionY) - .map(Map.Entry::getValue) - .forEach(tiles::onLightUpdate); - - chunk.getEntitySections()[sectionY].forEach(entities::onLightUpdate); - } - LightUpdater.getInstance() .onLightUpdate(world, type, pos.asLong()); } diff --git a/src/main/java/com/jozufozu/flywheel/mixin/light/NetworkLightUpdateMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/light/NetworkLightUpdateMixin.java index cc9596070..ed396c31d 100644 --- a/src/main/java/com/jozufozu/flywheel/mixin/light/NetworkLightUpdateMixin.java +++ b/src/main/java/com/jozufozu/flywheel/mixin/light/NetworkLightUpdateMixin.java @@ -34,22 +34,6 @@ public class NetworkLightUpdateMixin { int chunkX = packet.getX(); int chunkZ = packet.getZ(); - Chunk chunk = world.getChunkSource() - .getChunk(chunkX, chunkZ, false); - - if (chunk != null) { - InstanceManager tiles = InstancedRenderDispatcher.getTiles(world); - InstanceManager entities = InstancedRenderDispatcher.getEntities(world); - - chunk.getBlockEntities() - .values() - .forEach(tiles::onLightUpdate); - - Arrays.stream(chunk.getEntitySections()) - .flatMap(ClassInheritanceMultiMap::stream) - .forEach(entities::onLightUpdate); - } - LightUpdater.getInstance() .onLightPacket(world, chunkX, chunkZ); }); diff --git a/src/main/java/com/jozufozu/flywheel/util/WeakHashSet.java b/src/main/java/com/jozufozu/flywheel/util/WeakHashSet.java index b3683526a..281aaf418 100644 --- a/src/main/java/com/jozufozu/flywheel/util/WeakHashSet.java +++ b/src/main/java/com/jozufozu/flywheel/util/WeakHashSet.java @@ -98,7 +98,11 @@ public class WeakHashSet extends AbstractSet { @Override public boolean addAll(Collection c) { - return false; + boolean out = false; + for (T t : c) { + out |= add(t); + } + return out; } @Override diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/MinecartInstance.java b/src/main/java/com/jozufozu/flywheel/vanilla/MinecartInstance.java index 28d922fcd..3568b4b09 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/MinecartInstance.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/MinecartInstance.java @@ -1,13 +1,12 @@ package com.jozufozu.flywheel.vanilla; import com.jozufozu.flywheel.backend.instancing.IDynamicInstance; +import com.jozufozu.flywheel.backend.instancing.ITickableInstance; import com.jozufozu.flywheel.backend.instancing.entity.EntityInstance; import com.jozufozu.flywheel.backend.material.MaterialManager; -import com.jozufozu.flywheel.backend.model.ModelPool; import com.jozufozu.flywheel.backend.state.TextureRenderState; import com.jozufozu.flywheel.core.Materials; import com.jozufozu.flywheel.core.materials.ModelData; -import com.jozufozu.flywheel.core.model.BlockModel; import com.jozufozu.flywheel.core.model.IModel; import com.jozufozu.flywheel.core.model.ModelPart; import com.jozufozu.flywheel.util.AnimationTickHolder; @@ -21,22 +20,36 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.util.math.vector.Vector3f; -public class MinecartInstance extends EntityInstance implements IDynamicInstance { +public class MinecartInstance extends EntityInstance implements IDynamicInstance, ITickableInstance { private static final ResourceLocation MINECART_LOCATION = new ResourceLocation("textures/entity/minecart.png"); MatrixTransformStack stack = new MatrixTransformStack(); private final ModelData body; - private final ModelData contents; + private ModelData contents; + private BlockState blockstate; public MinecartInstance(MaterialManager materialManager, T entity) { super(materialManager, entity); + blockstate = entity.getDisplayBlockState(); contents = getContents(); body = getBody(); } + @Override + public void tick() { + BlockState displayBlockState = entity.getDisplayBlockState(); + + if (displayBlockState != blockstate) { + blockstate = displayBlockState; + contents.delete(); + contents = getContents(); + updateLight(); + } + } + @Override public void beginFrame() { stack.setIdentity(); @@ -124,8 +137,6 @@ public class MinecartInstance extends EntityIn } private ModelData getContents() { - BlockState blockstate = entity.getDisplayBlockState(); - if (blockstate.getRenderShape() == BlockRenderType.INVISIBLE) return null;