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;