From 55e38a52f4b80f3e40ffdee4d89b06c2aefeb7a2 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 25 Jan 2024 21:17:36 -0800 Subject: [PATCH] Lit beyond bounds - Add callback to LitVisual to allow LitVisuals to change sections - Move visual managers' tick/frame plan creation back into VisualManagerImpl. - Some small reorganization in LitVisualStorage. --- .../flywheel/api/visual/LitVisual.java | 25 +++++- .../VisualizationManagerImpl.java | 10 +-- .../manager/VisualManagerImpl.java | 13 +++ .../storage/LitVisualStorage.java | 85 +++++++++++++++---- .../impl/visualization/storage/Storage.java | 2 +- .../lib/visual/AbstractBlockEntityVisual.java | 8 ++ 6 files changed, 116 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/jozufozu/flywheel/api/visual/LitVisual.java b/src/main/java/com/jozufozu/flywheel/api/visual/LitVisual.java index 697f324d0..12c4d7212 100644 --- a/src/main/java/com/jozufozu/flywheel/api/visual/LitVisual.java +++ b/src/main/java/com/jozufozu/flywheel/api/visual/LitVisual.java @@ -26,10 +26,33 @@ public interface LitVisual extends Visual { /** * Collect the sections that this visual is contained in. *
- * This method is only called upon visual creation. + * This method is called upon visual creation, and the frame after {@link Notifier#notifySectionsChanged} is called. * * @param consumer The consumer to provide the sections to. * @see SectionPos#asLong */ void collectLightSections(LongConsumer consumer); + + /** + * Set the notifier object. + *
+ * This method is only called once, upon visual creation, + * after {@link #init} and before {@link #collectLightSections}. + * + * @param notifier The notifier. + */ + void initLightSectionNotifier(Notifier notifier); + + /** + * A notifier object that can be used to indicate to the impl + * that the sections a visual is contained in have changed. + */ + interface Notifier { + /** + * Invoke this to indicate to the impl that your visual has moved to a different set of sections. + *
+ * The next frame, the impl will call {@link LitVisual#collectLightSections} again. + */ + void notifySectionsChanged(); + } } 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 432ee9670..65bb12404 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -98,20 +98,14 @@ public class VisualizationManagerImpl implements VisualizationManager { entities = new VisualManagerImpl<>(entitiesStorage); effects = new VisualManagerImpl<>(effectsStorage); - tickPlan = NestedPlan.of(SimplePlan.of(context -> blockEntities.processQueue(0)) - .then(blockEntitiesStorage.getTickPlan()), SimplePlan.of(context -> entities.processQueue(0)) - .then(entitiesStorage.getTickPlan()), SimplePlan.of(context -> effects.processQueue(0)) - .then(effectsStorage.getTickPlan())) + tickPlan = NestedPlan.of(blockEntities.tickPlan(), entities.tickPlan(), effects.tickPlan()) .then(RaisePlan.raise(tickFlag)) .simplify(); var recreate = SimplePlan.of(context -> blockEntitiesStorage.recreateAll(context.partialTick()), context -> entitiesStorage.recreateAll(context.partialTick()), context -> effectsStorage.recreateAll(context.partialTick())); var update = MapContextPlan.map(this::createVisualFrameContext) - .to(NestedPlan.of(SimplePlan.of(context -> blockEntities.processQueue(0)) - .then(blockEntitiesStorage.getFramePlan()), SimplePlan.of(context -> entities.processQueue(0)) - .then(entitiesStorage.getFramePlan()), SimplePlan.of(context -> effects.processQueue(0)) - .then(effectsStorage.getFramePlan()))); + .to(NestedPlan.of(blockEntities.framePlan(), entities.framePlan(), effects.framePlan())); framePlan = IfElsePlan.on((RenderContext ctx) -> engine.updateRenderOrigin(ctx.camera())) .ifTrue(recreate) 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 42f122ba7..d18b81f5d 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 @@ -3,9 +3,13 @@ package com.jozufozu.flywheel.impl.visualization.manager; 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.impl.visualization.storage.Storage; import com.jozufozu.flywheel.impl.visualization.storage.Transaction; +import com.jozufozu.flywheel.lib.task.SimplePlan; public class VisualManagerImpl> implements VisualManager { private final Queue> queue = new ConcurrentLinkedQueue<>(); @@ -60,4 +64,13 @@ public class VisualManagerImpl> implements VisualManager } } + public Plan framePlan() { + return SimplePlan.of(context -> processQueue(context.partialTick())) + .then(storage.getFramePlan()); + } + + public Plan tickPlan() { + return SimplePlan.of(context -> processQueue(0)) + .then(storage.getTickPlan()); + } } diff --git a/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/LitVisualStorage.java b/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/LitVisualStorage.java index f69227a50..1900a2532 100644 --- a/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/LitVisualStorage.java +++ b/src/main/java/com/jozufozu/flywheel/impl/visualization/storage/LitVisualStorage.java @@ -2,9 +2,13 @@ package com.jozufozu.flywheel.impl.visualization.storage; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; +import org.jetbrains.annotations.NotNull; + import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; import com.jozufozu.flywheel.api.visual.LitVisual; @@ -30,25 +34,28 @@ public class LitVisualStorage { private final Map visuals2Sections = new WeakHashMap<>(); private final Long2ObjectMap> sections2Visuals = new Long2ObjectOpenHashMap<>(); - private final LongSet sectionsQueue = new LongOpenHashSet(); + private final Queue movedVisuals = new ConcurrentLinkedQueue<>(); + private final LongSet sectionsUpdatedThisFrame = new LongOpenHashSet(); private long updateId = INITIAL_UPDATE_ID; public Plan plan() { return (SimplyComposedPlan) (TaskExecutor taskExecutor, VisualFrameContext context, Runnable onCompletion) -> { - if (sectionsQueue.isEmpty()) { + processMoved(); + + if (sectionsUpdatedThisFrame.isEmpty()) { onCompletion.run(); return; } - var sync = new Synchronizer(sectionsQueue.size(), () -> { - sectionsQueue.clear(); + var sync = new Synchronizer(sectionsUpdatedThisFrame.size(), () -> { + sectionsUpdatedThisFrame.clear(); onCompletion.run(); }); long updateId = getNextUpdateId(); - for (long section : sectionsQueue) { + for (long section : sectionsUpdatedThisFrame) { var visuals = sections2Visuals.get(section); if (visuals != null && !visuals.isEmpty()) { taskExecutor.execute(() -> PlanUtil.distribute(taskExecutor, updateId, sync, visuals, Updater::updateLight)); @@ -59,6 +66,16 @@ public class LitVisualStorage { }; } + private void processMoved() { + LitVisual visual; + while ((visual = movedVisuals.poll()) != null) { + // If the visual isn't there when we try to remove it that means it was deleted before we got to it. + if (remove(visual)) { + add(visual); + } + } + } + private long getNextUpdateId() { long out = this.updateId; @@ -75,37 +92,47 @@ public class LitVisualStorage { return visuals2Sections.isEmpty(); } - public void add(LitVisual visual) { + public void addAndInitNotifier(LitVisual visual) { + visual.initLightSectionNotifier(new LitVisualNotifierImpl(visual)); + add(visual); + } + + private void add(LitVisual visual) { LongSet sections = new LongArraySet(); visual.collectLightSections(sections::add); - Updater updater; + // Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals + visuals2Sections.put(visual, sections); + + // Don't bother creating an updater if the visual isn't in any sections. if (sections.isEmpty()) { return; - } else if (sections.size() == 1) { - updater = new Updater.Simple(visual); - } else { - updater = new Updater.Synced(visual, new AtomicLong(NEVER_UPDATED)); } + var updater = createUpdater(visual, sections.size()); + for (long section : sections) { sections2Visuals.computeIfAbsent(section, $ -> new ObjectArrayList<>()) .add(updater); } - - visuals2Sections.put(visual, sections); } public void enqueueLightUpdateSections(LongSet sections) { - sectionsQueue.addAll(sections); + sectionsUpdatedThisFrame.addAll(sections); } - public void remove(LitVisual visual) { + /** + * Remove the visual from this storage. + * + * @param visual The visual to remove. + * @return {@code true} if the visual was removed, {@code false} otherwise. + */ + public boolean remove(LitVisual visual) { var sections = visuals2Sections.remove(visual); if (sections == null) { - return; + return false; } for (long section : sections) { @@ -114,12 +141,14 @@ public class LitVisualStorage { listeners.remove(indexOfUpdater(listeners, visual)); } } + + return true; } public void clear() { visuals2Sections.clear(); sections2Visuals.clear(); - sectionsQueue.clear(); + sectionsUpdatedThisFrame.clear(); } private static int indexOfUpdater(List listeners, LitVisual visual) { @@ -132,6 +161,15 @@ public class LitVisualStorage { return -1; } + @NotNull + private static Updater createUpdater(LitVisual visual, int sectionCount) { + if (sectionCount == 1) { + return new Updater.Simple(visual); + } else { + return new Updater.Synced(visual, new AtomicLong(NEVER_UPDATED)); + } + } + // Breaking this into 2 separate cases allows us to avoid the sync overhead in the common case. // TODO: is it faster to only use the synced variant to avoid virtual dispatches? sealed interface Updater { @@ -160,4 +198,17 @@ public class LitVisualStorage { } } } + + private final class LitVisualNotifierImpl implements LitVisual.Notifier { + private final LitVisual litVisual; + + private LitVisualNotifierImpl(LitVisual litVisual) { + this.litVisual = litVisual; + } + + @Override + public void notifySectionsChanged() { + movedVisuals.add(litVisual); + } + } } 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 e6bc1500e..8f9875144 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 @@ -169,7 +169,7 @@ public abstract class Storage { } if (visual instanceof LitVisual lit) { - litVisuals.add(lit); + litVisuals.addAndInitNotifier(lit); } } diff --git a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java index 9d4f7128d..2d4cf59f1 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java +++ b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractBlockEntityVisual.java @@ -2,6 +2,7 @@ package com.jozufozu.flywheel.lib.visual; import java.util.function.LongConsumer; +import org.jetbrains.annotations.Nullable; import org.joml.FrustumIntersection; import com.jozufozu.flywheel.api.visual.BlockEntityVisual; @@ -40,6 +41,8 @@ public abstract class AbstractBlockEntityVisual extends A protected final BlockPos pos; protected final BlockPos visualPos; protected final BlockState blockState; + @Nullable + protected LitVisual.Notifier notifier; public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity) { super(ctx, blockEntity.getLevel()); @@ -59,6 +62,11 @@ public abstract class AbstractBlockEntityVisual extends A consumer.accept(SectionPos.asLong(pos)); } + @Override + public void initLightSectionNotifier(Notifier notifier) { + this.notifier = notifier; + } + @Override public boolean shouldReset() { return blockEntity.getBlockState() != blockState;