From 91738e38a875ae0b4f16e0763f95821e9f9e6f23 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 20 Jan 2024 23:43:21 -0800 Subject: [PATCH] Light updated - Promote LightUpdater to the API. - Include LightUpdater in VisualizationContext. - Explicitly launch a plan to run light updates. - Misc tweaks: - The tick/frame limiters are now shared between visual managers. - The VisualizationManager assembles the frame/tick plans itself to avoid duplicate context mapping and to allow for reorganization in later commits. --- .../java/com/jozufozu/flywheel/Flywheel.java | 2 - .../api/visualization/LightUpdater.java | 9 ++ .../visualization/VisualizationContext.java | 2 +- .../VisualizationManagerImpl.java | 85 ++++++++++++-- .../manager/BlockEntityStorage.java | 9 +- .../visualization/manager/EffectStorage.java | 9 +- .../visualization/manager/EntityStorage.java | 9 +- .../manager/VisualManagerImpl.java | 50 +------- .../impl/visualization/storage/Storage.java | 9 +- .../flywheel/lib/light/DummyLightUpdater.java | 45 ------- .../flywheel/lib/light/LightListener.java | 2 +- ...ightUpdater.java => LightUpdaterImpl.java} | 110 ++++++++---------- .../lib/light/WeakContainmentMultiMap.java | 8 +- .../flywheel/lib/visual/AbstractVisual.java | 6 +- .../mixin/LayerLightSectionStorageMixin.java | 41 +++++++ .../flywheel/mixin/LevelRendererMixin.java | 5 + .../flywheel/mixin/LightUpdateMixin.java | 33 ------ src/main/resources/flywheel.mixins.json | 2 +- 18 files changed, 209 insertions(+), 227 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/api/visualization/LightUpdater.java delete mode 100644 src/main/java/com/jozufozu/flywheel/lib/light/DummyLightUpdater.java rename src/main/java/com/jozufozu/flywheel/lib/light/{LightUpdater.java => LightUpdaterImpl.java} (50%) create mode 100644 src/main/java/com/jozufozu/flywheel/mixin/LayerLightSectionStorageMixin.java delete mode 100644 src/main/java/com/jozufozu/flywheel/mixin/LightUpdateMixin.java diff --git a/src/main/java/com/jozufozu/flywheel/Flywheel.java b/src/main/java/com/jozufozu/flywheel/Flywheel.java index 09b8fceb0..38a0ea375 100644 --- a/src/main/java/com/jozufozu/flywheel/Flywheel.java +++ b/src/main/java/com/jozufozu/flywheel/Flywheel.java @@ -21,7 +21,6 @@ import com.jozufozu.flywheel.impl.RegistryImpl; import com.jozufozu.flywheel.impl.visualization.VisualizationEventHandler; import com.jozufozu.flywheel.lib.context.Contexts; import com.jozufozu.flywheel.lib.instance.InstanceTypes; -import com.jozufozu.flywheel.lib.light.LightUpdater; import com.jozufozu.flywheel.lib.material.CutoutShaders; import com.jozufozu.flywheel.lib.material.FogShaders; import com.jozufozu.flywheel.lib.material.StandardMaterialShaders; @@ -97,7 +96,6 @@ public class Flywheel { forgeEventBus.addListener(Uniforms::onReloadLevelRenderer); - forgeEventBus.addListener(LightUpdater::onClientTick); forgeEventBus.addListener((LevelEvent.Unload e) -> LevelAttached.onUnloadLevel(e)); // forgeEventBus.addListener(ExampleEffect::tick); diff --git a/src/main/java/com/jozufozu/flywheel/api/visualization/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/api/visualization/LightUpdater.java new file mode 100644 index 000000000..10929619d --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/api/visualization/LightUpdater.java @@ -0,0 +1,9 @@ +package com.jozufozu.flywheel.api.visualization; + +import com.jozufozu.flywheel.lib.light.LightListener; + +public interface LightUpdater { + void addListener(LightListener listener); + + void removeListener(LightListener listener); +} diff --git a/src/main/java/com/jozufozu/flywheel/api/visualization/VisualizationContext.java b/src/main/java/com/jozufozu/flywheel/api/visualization/VisualizationContext.java index cc2c30d53..99205c7a2 100644 --- a/src/main/java/com/jozufozu/flywheel/api/visualization/VisualizationContext.java +++ b/src/main/java/com/jozufozu/flywheel/api/visualization/VisualizationContext.java @@ -11,5 +11,5 @@ import net.minecraft.core.Vec3i; * @param renderOrigin The origin of the renderer as a world position. * All models render as if this position is (0, 0, 0). */ -public record VisualizationContext(InstancerProvider instancerProvider, Vec3i renderOrigin) { +public record VisualizationContext(InstancerProvider instancerProvider, LightUpdater lightUpdater, Vec3i renderOrigin) { } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java index 5c9a6fdd5..9aa56c67a 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -3,6 +3,7 @@ package com.jozufozu.flywheel.impl.visualization; import java.util.ArrayList; import java.util.List; import java.util.SortedSet; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; @@ -16,20 +17,30 @@ import com.jozufozu.flywheel.api.task.TaskExecutor; import com.jozufozu.flywheel.api.visual.DynamicVisual; import com.jozufozu.flywheel.api.visual.Effect; import com.jozufozu.flywheel.api.visual.TickableVisual; +import com.jozufozu.flywheel.api.visual.VisualFrameContext; +import com.jozufozu.flywheel.api.visual.VisualTickContext; import com.jozufozu.flywheel.api.visualization.VisualManager; +import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.api.visualization.VisualizationLevel; import com.jozufozu.flywheel.api.visualization.VisualizationManager; +import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.extension.LevelExtension; import com.jozufozu.flywheel.impl.task.FlwTaskExecutor; import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityStorage; import com.jozufozu.flywheel.impl.visualization.manager.EffectStorage; import com.jozufozu.flywheel.impl.visualization.manager.EntityStorage; import com.jozufozu.flywheel.impl.visualization.manager.VisualManagerImpl; +import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter; +import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiterImpl; +import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter; +import com.jozufozu.flywheel.lib.light.LightUpdaterImpl; import com.jozufozu.flywheel.lib.task.Flag; import com.jozufozu.flywheel.lib.task.IfElsePlan; import com.jozufozu.flywheel.lib.task.MapContextPlan; import com.jozufozu.flywheel.lib.task.NamedFlag; +import com.jozufozu.flywheel.lib.task.NestedPlan; import com.jozufozu.flywheel.lib.task.RaisePlan; +import com.jozufozu.flywheel.lib.task.SimplePlan; import com.jozufozu.flywheel.lib.util.LevelAttached; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -44,11 +55,12 @@ import net.minecraft.world.level.block.entity.BlockEntity; /** * A manager class for a single world where visualization is supported. */ -public class VisualizationManagerImpl implements VisualizationManager { +public class VisualizationManagerImpl implements VisualizationManager, Supplier { private static final LevelAttached MANAGERS = new LevelAttached<>(VisualizationManagerImpl::new, VisualizationManagerImpl::delete); private final Engine engine; private final TaskExecutor taskExecutor; + private final LightUpdaterImpl lightUpdater; private final VisualManagerImpl blockEntities; private final VisualManagerImpl entities; @@ -61,18 +73,30 @@ public class VisualizationManagerImpl implements VisualizationManager { private final Flag frameVisualsFlag = new NamedFlag("frameVisualUpdates"); private final Flag frameFlag = new NamedFlag("frameComplete"); + protected DistanceUpdateLimiterImpl tickLimiter; + protected DistanceUpdateLimiterImpl frameLimiter; + private VisualizationManagerImpl(LevelAccessor level) { + tickLimiter = createUpdateLimiter(); + frameLimiter = createUpdateLimiter(); + engine = BackendManager.getBackend() .createEngine(level); taskExecutor = FlwTaskExecutor.get(); + lightUpdater = new LightUpdaterImpl(); - blockEntities = new VisualManagerImpl<>(new BlockEntityStorage(engine)); - entities = new VisualManagerImpl<>(new EntityStorage(engine)); - effects = new VisualManagerImpl<>(new EffectStorage(engine)); + blockEntities = new VisualManagerImpl<>(new BlockEntityStorage(this)); + entities = new VisualManagerImpl<>(new EntityStorage(this)); + effects = new VisualManagerImpl<>(new EffectStorage(this)); - tickPlan = blockEntities.createTickPlan() - .and(entities.createTickPlan()) - .and(effects.createTickPlan()) + tickPlan = MapContextPlan.map(this::createVisualTickContext) + .to(NestedPlan.of(SimplePlan.of(context -> blockEntities.processQueue(0)) + .then(blockEntities.getStorage() + .getTickPlan()), SimplePlan.of(context -> entities.processQueue(0)) + .then(entities.getStorage() + .getTickPlan()), SimplePlan.of(context -> effects.processQueue(0)) + .then(effects.getStorage() + .getTickPlan()))) .then(RaisePlan.raise(tickFlag)) .simplify(); @@ -81,11 +105,16 @@ public class VisualizationManagerImpl implements VisualizationManager { .to(blockEntities.createRecreationPlan() .and(entities.createRecreationPlan()) .and(effects.createRecreationPlan()))) - .ifFalse(MapContextPlan.map((RenderContext ctx) -> FrameContext.create(ctx, engine.renderOrigin())) - .to(blockEntities.createFramePlan() - .and(entities.createFramePlan()) - .and(effects.createFramePlan()))) + .ifFalse(MapContextPlan.map((RenderContext ctx) -> createVisualContext(FrameContext.create(ctx, engine.renderOrigin()))) + .to(NestedPlan.of(SimplePlan.of(context -> blockEntities.processQueue(context.partialTick())) + .then(blockEntities.getStorage() + .getFramePlan()), SimplePlan.of(context -> entities.processQueue(context.partialTick())) + .then(entities.getStorage() + .getFramePlan()), SimplePlan.of(context -> effects.processQueue(context.partialTick())) + .then(effects.getStorage() + .getFramePlan())))) .plan() + .then(lightUpdater.plan()) .then(RaisePlan.raise(frameVisualsFlag)) .then(engine.createFramePlan()) .then(RaisePlan.raise(frameFlag)) @@ -97,6 +126,23 @@ public class VisualizationManagerImpl implements VisualizationManager { } } + private VisualFrameContext createVisualContext(FrameContext ctx) { + return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), ctx.partialTick(), frameLimiter); + } + + private VisualTickContext createVisualTickContext(TickContext ctx) { + return new VisualTickContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), frameLimiter); + } + + protected DistanceUpdateLimiterImpl createUpdateLimiter() { + if (FlwConfig.get() + .limitUpdates()) { + return new BandedPrimeLimiter(); + } else { + return new NonLimiter(); + } + } + public static boolean supportsVisualization(@Nullable LevelAccessor level) { if (!BackendManager.isOn()) { return false; @@ -136,14 +182,19 @@ public class VisualizationManagerImpl implements VisualizationManager { // TODO: Consider making these reset actions reuse the existing game objects instead of re-adding them // potentially by keeping the same VisualizationManagerImpl and deleting the engine and visuals but not the game objects + public static void reset(LevelAccessor level) { MANAGERS.remove(level); } - public static void resetAll() { MANAGERS.reset(); } + @Override + public VisualizationContext get() { + return new VisualizationContext(engine, lightUpdater, engine.renderOrigin()); + } + @Override public Vec3i getRenderOrigin() { return engine.renderOrigin(); @@ -164,6 +215,10 @@ public class VisualizationManagerImpl implements VisualizationManager { return effects; } + public LightUpdaterImpl getLightUpdater() { + return lightUpdater; + } + /** * Tick the visuals after the game has ticked: *

@@ -178,6 +233,10 @@ public class VisualizationManagerImpl implements VisualizationManager { taskExecutor.syncUntil(tickFlag::isRaised); tickFlag.lower(); + tickLimiter.tick(); + + lightUpdater.tick(); + tickPlan.execute(taskExecutor, new TickContext(cameraX, cameraY, cameraZ)); } @@ -196,6 +255,8 @@ public class VisualizationManagerImpl implements VisualizationManager { frameVisualsFlag.lower(); frameFlag.lower(); + + frameLimiter.tick(); framePlan.execute(taskExecutor, context); } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/BlockEntityStorage.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/BlockEntityStorage.java index 4b1350158..64a772cbc 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/BlockEntityStorage.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/BlockEntityStorage.java @@ -1,8 +1,9 @@ package com.jozufozu.flywheel.impl.visualization.manager; +import java.util.function.Supplier; + import org.jetbrains.annotations.Nullable; -import com.jozufozu.flywheel.api.backend.Engine; import com.jozufozu.flywheel.api.visual.BlockEntityVisual; import com.jozufozu.flywheel.api.visual.Visual; import com.jozufozu.flywheel.api.visualization.VisualizationContext; @@ -19,8 +20,8 @@ import net.minecraft.world.level.block.entity.BlockEntity; public class BlockEntityStorage extends Storage { private final Long2ObjectMap> posLookup = new Long2ObjectOpenHashMap<>(); - public BlockEntityStorage(Engine engine) { - super(engine); + public BlockEntityStorage(Supplier visualizationContextSupplier) { + super(visualizationContextSupplier); } public BlockEntityVisual visualAtPos(long pos) { @@ -59,7 +60,7 @@ public class BlockEntityStorage extends Storage { return null; } - var visual = visualizer.createVisual(new VisualizationContext(engine, engine.renderOrigin()), obj); + var visual = visualizer.createVisual(visualizationContextSupplier.get(), obj); BlockPos blockPos = obj.getBlockPos(); posLookup.put(blockPos.asLong(), visual); diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EffectStorage.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EffectStorage.java index 50a91fbb9..14cee1e39 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EffectStorage.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EffectStorage.java @@ -1,19 +1,20 @@ package com.jozufozu.flywheel.impl.visualization.manager; -import com.jozufozu.flywheel.api.backend.Engine; +import java.util.function.Supplier; + import com.jozufozu.flywheel.api.visual.Effect; import com.jozufozu.flywheel.api.visual.EffectVisual; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.impl.visualization.storage.Storage; public class EffectStorage extends Storage { - public EffectStorage(Engine engine) { - super(engine); + public EffectStorage(Supplier visualizationContextSupplier) { + super(visualizationContextSupplier); } @Override protected EffectVisual createRaw(Effect obj) { - return obj.visualize(new VisualizationContext(engine, engine.renderOrigin())); + return obj.visualize(visualizationContextSupplier.get()); } @Override diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EntityStorage.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EntityStorage.java index a79d12ed4..2190333e7 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EntityStorage.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/EntityStorage.java @@ -1,8 +1,9 @@ package com.jozufozu.flywheel.impl.visualization.manager; +import java.util.function.Supplier; + import org.jetbrains.annotations.Nullable; -import com.jozufozu.flywheel.api.backend.Engine; import com.jozufozu.flywheel.api.visual.Visual; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.impl.visualization.VisualizationHelper; @@ -12,8 +13,8 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; public class EntityStorage extends Storage { - public EntityStorage(Engine engine) { - super(engine); + public EntityStorage(Supplier visualizationContextSupplier) { + super(visualizationContextSupplier); } @Override @@ -24,7 +25,7 @@ public class EntityStorage extends Storage { return null; } - return visualizer.createVisual(new VisualizationContext(engine, engine.renderOrigin()), obj); + return visualizer.createVisual(visualizationContextSupplier.get(), obj); } @Override diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManagerImpl.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManagerImpl.java index d08b9c08c..aa9cd99f8 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManagerImpl.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/manager/VisualManagerImpl.java @@ -4,31 +4,17 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import com.jozufozu.flywheel.api.task.Plan; -import com.jozufozu.flywheel.api.visual.VisualFrameContext; -import com.jozufozu.flywheel.api.visual.VisualTickContext; import com.jozufozu.flywheel.api.visualization.VisualManager; -import com.jozufozu.flywheel.config.FlwConfig; -import com.jozufozu.flywheel.impl.visualization.FrameContext; -import com.jozufozu.flywheel.impl.visualization.TickContext; -import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter; -import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiterImpl; -import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter; import com.jozufozu.flywheel.impl.visualization.storage.Storage; import com.jozufozu.flywheel.impl.visualization.storage.Transaction; -import com.jozufozu.flywheel.lib.task.MapContextPlan; import com.jozufozu.flywheel.lib.task.SimplePlan; public class VisualManagerImpl> implements VisualManager { private final Queue> queue = new ConcurrentLinkedQueue<>(); - protected DistanceUpdateLimiterImpl tickLimiter; - protected DistanceUpdateLimiterImpl frameLimiter; - protected final S storage; public VisualManagerImpl(S storage) { - tickLimiter = createUpdateLimiter(); - frameLimiter = createUpdateLimiter(); this.storage = storage; } @@ -36,15 +22,6 @@ public class VisualManagerImpl> implements VisualManager return storage; } - protected DistanceUpdateLimiterImpl createUpdateLimiter() { - if (FlwConfig.get() - .limitUpdates()) { - return new BandedPrimeLimiter(); - } else { - return new NonLimiter(); - } - } - @Override public int getVisualCount() { return getStorage().getAllVisuals().size(); @@ -81,7 +58,7 @@ public class VisualManagerImpl> implements VisualManager getStorage().invalidate(); } - protected void processQueue(float partialTick) { + public void processQueue(float partialTick) { var storage = getStorage(); Transaction transaction; while ((transaction = queue.poll()) != null) { @@ -89,29 +66,4 @@ public class VisualManagerImpl> implements VisualManager } } - public Plan createTickPlan() { - return SimplePlan.of(() -> { - tickLimiter.tick(); - processQueue(0); - }) - .then(MapContextPlan.map(this::createVisualTickContext) - .to(getStorage().getTickPlan())); - } - - public Plan createFramePlan() { - return SimplePlan.of(context -> { - frameLimiter.tick(); - processQueue(context.partialTick()); - }) - .then(MapContextPlan.map(this::createVisualContext) - .to(getStorage().getFramePlan())); - } - - private VisualFrameContext createVisualContext(FrameContext ctx) { - return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), ctx.partialTick(), frameLimiter); - } - - private VisualTickContext createVisualTickContext(TickContext ctx) { - return new VisualTickContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), frameLimiter); - } } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/Storage.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/Storage.java index b37ada43a..c3479215f 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/Storage.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/Storage.java @@ -4,10 +4,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; -import com.jozufozu.flywheel.api.backend.Engine; import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.visual.DynamicVisual; import com.jozufozu.flywheel.api.visual.PlannedVisual; @@ -15,12 +15,13 @@ import com.jozufozu.flywheel.api.visual.TickableVisual; import com.jozufozu.flywheel.api.visual.Visual; import com.jozufozu.flywheel.api.visual.VisualFrameContext; import com.jozufozu.flywheel.api.visual.VisualTickContext; +import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.lib.task.ForEachPlan; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; public abstract class Storage { - protected final Engine engine; + protected final Supplier visualizationContextSupplier; protected final List tickableVisuals = new ArrayList<>(); protected final List dynamicVisuals = new ArrayList<>(); protected final List plannedVisuals = new ArrayList<>(); @@ -33,8 +34,8 @@ public abstract class Storage { private final Map visuals = new Reference2ObjectOpenHashMap<>(); - public Storage(Engine engine) { - this.engine = engine; + public Storage(Supplier visualizationContextSupplier) { + this.visualizationContextSupplier = visualizationContextSupplier; } public Collection getAllVisuals() { diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/DummyLightUpdater.java b/src/main/java/com/jozufozu/flywheel/lib/light/DummyLightUpdater.java deleted file mode 100644 index f41f560cf..000000000 --- a/src/main/java/com/jozufozu/flywheel/lib/light/DummyLightUpdater.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jozufozu.flywheel.lib.light; - -import java.util.stream.Stream; - -import com.jozufozu.flywheel.lib.box.Box; - -import net.minecraft.core.SectionPos; -import net.minecraft.world.level.LightLayer; - -public final class DummyLightUpdater extends LightUpdater { - public static final DummyLightUpdater INSTANCE = new DummyLightUpdater(); - - private DummyLightUpdater() { - } - - @Override - public void tick() { - // noop - } - - @Override - public void addListener(LightListener listener) { - // noop - } - - @Override - public void removeListener(LightListener listener) { - // noop - } - - @Override - public void onLightUpdate(LightLayer type, SectionPos pos) { - // noop - } - - @Override - public Stream getAllBoxes() { - return Stream.empty(); - } - - @Override - public boolean isEmpty() { - return true; - } -} diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/LightListener.java b/src/main/java/com/jozufozu/flywheel/lib/light/LightListener.java index dfb3e9559..48c99183c 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/light/LightListener.java +++ b/src/main/java/com/jozufozu/flywheel/lib/light/LightListener.java @@ -7,7 +7,7 @@ import net.minecraft.world.level.LightLayer; /** * Implementors of this interface may choose to subscribe to light updates by calling - * {@link LightUpdater#addListener(LightListener)}.

+ * {@link LightUpdaterImpl#addListener(LightListener)}.

* * It is the responsibility of the implementor to keep a reference to the level an object is contained in. */ diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdater.java b/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java similarity index 50% rename from src/main/java/com/jozufozu/flywheel/lib/light/LightUpdater.java rename to src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java index 1f447ed85..5813fd45a 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdater.java +++ b/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java @@ -1,22 +1,26 @@ package com.jozufozu.flywheel.lib.light; +import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Stream; -import org.jetbrains.annotations.ApiStatus; - +import com.jozufozu.flywheel.api.event.RenderContext; +import com.jozufozu.flywheel.api.task.Plan; +import com.jozufozu.flywheel.api.visualization.LightUpdater; import com.jozufozu.flywheel.lib.box.Box; +import com.jozufozu.flywheel.lib.task.ForEachPlan; +import com.jozufozu.flywheel.lib.task.IfElsePlan; +import com.jozufozu.flywheel.lib.task.SimplePlan; import com.jozufozu.flywheel.lib.util.FlwUtil; -import com.jozufozu.flywheel.lib.util.LevelAttached; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; -import net.minecraft.client.Minecraft; import net.minecraft.core.SectionPos; -import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LightLayer; -import net.minecraftforge.event.TickEvent; /** * Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated. @@ -24,35 +28,12 @@ import net.minecraftforge.event.TickEvent; * @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement * {@link LightUpdatedLevel} for LightUpdater to work with them. */ -public class LightUpdater { - private static final LevelAttached UPDATERS = new LevelAttached<>(level -> new LightUpdater()); - - private final WeakContainmentMultiMap listenersBySection = new WeakContainmentMultiMap<>(); +public class LightUpdaterImpl implements LightUpdater { + private final WeakHashMap listenersAndTheirSections = new WeakHashMap<>(); private final Set tickingListeners = FlwUtil.createWeakHashSet(); private final Queue additionQueue = new ConcurrentLinkedQueue<>(); - - public static boolean supports(LevelAccessor level) { - // The client level is guaranteed to receive updates. - if (Minecraft.getInstance().level == level) { - return true; - } - // Custom/fake levels need to indicate that LightUpdater has meaning. - if (level instanceof LightUpdatedLevel c) { - return c.receivesLightUpdates(); - } - return false; - } - - public static LightUpdater get(LevelAccessor level) { - if (supports(level)) { - // The level is valid, so add it to the map. - return UPDATERS.get(level); - } else { - // Fake light updater for a fake level. - return DummyLightUpdater.INSTANCE; - } - } + private final LongSet sectionsQueue = new LongOpenHashSet(); /** * Add a listener. @@ -64,48 +45,47 @@ public class LightUpdater { } public void removeListener(LightListener listener) { - listenersBySection.remove(listener); + listenersAndTheirSections.remove(listener); } - /** - * Dispatch light updates to all registered {@link LightListener}s. - * - * @param type The type of light that changed. - * @param pos The section position where light changed. - */ - public void onLightUpdate(LightLayer type, SectionPos pos) { - processQueue(); + public Plan plan() { + // Assume we'll have more listeners than sections updated + // TODO: this is slow, maybe launch a task for each changed section and distribute from there? + return SimplePlan.of(this::processQueue) + .then(IfElsePlan.on(() -> !sectionsQueue.isEmpty()) + .ifTrue(ForEachPlan.of(() -> listenersAndTheirSections.entrySet() + .stream() + .toList(), this::updateOneEntry)) + .plan()) + .then(SimplePlan.of(() -> sectionsQueue.clear())); + } - Set listeners = listenersBySection.get(pos.asLong()); + private void updateOneEntry(Map.Entry entry) { - if (listeners == null || listeners.isEmpty()) { - return; - } + updateOne(entry.getKey(), entry.getValue()); - listeners.removeIf(LightListener::isInvalid); + } - for (LightListener listener : listeners) { - listener.onLightUpdate(type, pos); + private void updateOne(LightListener listener, LongSet containedSections) { + for (long l : containedSections.toLongArray()) { + if (sectionsQueue.contains(l)) { + listener.onLightUpdate(LightLayer.BLOCK, SectionPos.of(l)); + break; + } } } public Stream getAllBoxes() { - return listenersBySection.stream().map(LightListener::getVolume); + return listenersAndTheirSections.keySet() + .stream() + .map(LightListener::getVolume); } public boolean isEmpty() { - return listenersBySection.isEmpty(); + return listenersAndTheirSections.isEmpty(); } - @ApiStatus.Internal - public static void onClientTick(TickEvent.ClientTickEvent event) { - if (event.phase == TickEvent.Phase.END && FlwUtil.isGameActive()) { - get(Minecraft.getInstance().level) - .tick(); - } - } - - void tick() { + public void tick() { processQueue(); for (TickingLightListener tickingListener : tickingListeners) { @@ -129,7 +109,7 @@ public class LightUpdater { Box box = listener.getVolume(); - LongSet sections = listenersBySection.getAndResetContainment(listener); + LongSet sections = new LongArraySet(); int minX = SectionPos.blockToSectionCoord(box.getMinX()); int minY = SectionPos.blockToSectionCoord(box.getMinY()); @@ -141,11 +121,15 @@ public class LightUpdater { for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { for (int z = minZ; z <= maxZ; z++) { - long sectionPos = SectionPos.asLong(x, y, z); - listenersBySection.put(sectionPos, listener); - sections.add(sectionPos); + sections.add(SectionPos.asLong(x, y, z)); } } } + + listenersAndTheirSections.put(listener, sections); + } + + public void notifySectionUpdates(LongSet sections) { + sectionsQueue.addAll(sections); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/WeakContainmentMultiMap.java b/src/main/java/com/jozufozu/flywheel/lib/light/WeakContainmentMultiMap.java index dfa18af76..232ae0cea 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/light/WeakContainmentMultiMap.java +++ b/src/main/java/com/jozufozu/flywheel/lib/light/WeakContainmentMultiMap.java @@ -39,7 +39,9 @@ class WeakContainmentMultiMap extends AbstractCollection { containmentSet.forEach((LongConsumer) l -> { Set listeners = forward.get(l); - if (listeners != null) listeners.remove(listener); + if (listeners != null) { + listeners.remove(listener); + } }); containmentSet.clear(); @@ -63,7 +65,9 @@ class WeakContainmentMultiMap extends AbstractCollection { containmentSet.forEach((LongConsumer) l -> { Set listeners = forward.get(l); - if (listeners != null) listeners.remove(o); + if (listeners != null) { + listeners.remove(o); + } }); containmentSet.clear(); diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractVisual.java b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractVisual.java index c113c65c3..f54cb2d02 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractVisual.java +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractVisual.java @@ -7,7 +7,6 @@ import com.jozufozu.flywheel.api.visual.Visual; import com.jozufozu.flywheel.api.visualization.VisualizationContext; import com.jozufozu.flywheel.lib.instance.FlatLit; import com.jozufozu.flywheel.lib.light.LightListener; -import com.jozufozu.flywheel.lib.light.LightUpdater; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; @@ -37,7 +36,8 @@ public abstract class AbstractVisual implements Visual, LightListener { @Override public void init(float partialTick) { - LightUpdater.get(level).addListener(this); + visualizationContext.lightUpdater() + .addListener(this); updateLight(); } @@ -67,6 +67,8 @@ public abstract class AbstractVisual implements Visual, LightListener { } _delete(); + visualizationContext.lightUpdater() + .removeListener(this); deleted = true; } diff --git a/src/main/java/com/jozufozu/flywheel/mixin/LayerLightSectionStorageMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/LayerLightSectionStorageMixin.java new file mode 100644 index 000000000..6451b1630 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/mixin/LayerLightSectionStorageMixin.java @@ -0,0 +1,41 @@ +package com.jozufozu.flywheel.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.jozufozu.flywheel.impl.visualization.VisualizationManagerImpl; + +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.chunk.LightChunkGetter; +import net.minecraft.world.level.lighting.LayerLightSectionStorage; + +@Mixin(LayerLightSectionStorage.class) +public abstract class LayerLightSectionStorageMixin { + + @Shadow + @Final + protected LongSet sectionsAffectedByLightUpdates; + + @Shadow + @Final + protected LightChunkGetter chunkSource; + + @Inject(at = @At("HEAD"), method = "swapSectionMap") + private void flywheel$listenForChangedSections(CallbackInfo ci) { + if (this.sectionsAffectedByLightUpdates.isEmpty()) { + return; + } + + var manager = VisualizationManagerImpl.get((LevelAccessor) this.chunkSource.getLevel()); + + if (manager != null) { + manager.getLightUpdater() + .notifySectionUpdates(this.sectionsAffectedByLightUpdates); + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java index a0dcdcc13..2be9760fe 100644 --- a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java +++ b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java @@ -49,6 +49,11 @@ abstract class LevelRendererMixin { @Inject(method = "renderLevel", at = @At("HEAD")) private void flywheel$beginRender(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci) { + // TODO: divide some work to here, light updates may take a while + } + + @Inject(method = "renderLevel", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I")) + private void flywheel$processLightUpdates(PoseStack poseStack, float partialTick, long finishNanoTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projectionMatrix, CallbackInfo ci) { flywheel$renderContext = RenderContext.create((LevelRenderer) (Object) this, level, renderBuffers, poseStack, projectionMatrix, camera, partialTick); MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(flywheel$renderContext)); diff --git a/src/main/java/com/jozufozu/flywheel/mixin/LightUpdateMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/LightUpdateMixin.java deleted file mode 100644 index 128048c47..000000000 --- a/src/main/java/com/jozufozu/flywheel/mixin/LightUpdateMixin.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jozufozu.flywheel.mixin; - -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import com.jozufozu.flywheel.lib.light.LightUpdater; - -import net.minecraft.client.multiplayer.ClientChunkCache; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.core.SectionPos; -import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.chunk.ChunkSource; - -@Mixin(ClientChunkCache.class) -abstract class LightUpdateMixin extends ChunkSource { - @Shadow - @Final - ClientLevel level; - - /** - * JUSTIFICATION: This method is called after lighting updates are finished processing - * per section where a lighting change occurred that frame. On the client, Minecraft - * uses this method to inform the rendering system that it needs to redraw a chunk. - */ - @Inject(method = "onLightUpdate(Lnet/minecraft/world/level/LightLayer;Lnet/minecraft/core/SectionPos;)V", at = @At("HEAD")) - private void flywheel$onLightUpdate(LightLayer type, SectionPos pos, CallbackInfo ci) { - LightUpdater.get(level).onLightUpdate(type, pos); - } -} diff --git a/src/main/resources/flywheel.mixins.json b/src/main/resources/flywheel.mixins.json index 5809e2940..e2c5fc7ba 100644 --- a/src/main/resources/flywheel.mixins.json +++ b/src/main/resources/flywheel.mixins.json @@ -10,9 +10,9 @@ "EntityTypeMixin", "FogUpdateMixin", "GlStateManagerMixin", + "LayerLightSectionStorageMixin", "LevelMixin", "LevelRendererMixin", - "LightUpdateMixin", "MinecraftMixin", "PoseStackMixin", "VertexFormatMixin",