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.
This commit is contained in:
Jozufozu 2024-01-25 21:17:36 -08:00
parent e7c27109e5
commit 4698d7b394
6 changed files with 116 additions and 27 deletions

View File

@ -26,10 +26,33 @@ public interface LitVisual extends Visual {
/** /**
* Collect the sections that this visual is contained in. * Collect the sections that this visual is contained in.
* <br> * <br>
* 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. * @param consumer The consumer to provide the sections to.
* @see SectionPos#asLong * @see SectionPos#asLong
*/ */
void collectLightSections(LongConsumer consumer); void collectLightSections(LongConsumer consumer);
/**
* Set the notifier object.
* <br>
* 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.
* <br>
* The next frame, the impl will call {@link LitVisual#collectLightSections} again.
*/
void notifySectionsChanged();
}
} }

View File

@ -98,20 +98,14 @@ public class VisualizationManagerImpl implements VisualizationManager {
entities = new VisualManagerImpl<>(entitiesStorage); entities = new VisualManagerImpl<>(entitiesStorage);
effects = new VisualManagerImpl<>(effectsStorage); effects = new VisualManagerImpl<>(effectsStorage);
tickPlan = NestedPlan.of(SimplePlan.<VisualTickContext>of(context -> blockEntities.processQueue(0)) tickPlan = NestedPlan.of(blockEntities.tickPlan(), entities.tickPlan(), effects.tickPlan())
.then(blockEntitiesStorage.getTickPlan()), SimplePlan.<VisualTickContext>of(context -> entities.processQueue(0))
.then(entitiesStorage.getTickPlan()), SimplePlan.<VisualTickContext>of(context -> effects.processQueue(0))
.then(effectsStorage.getTickPlan()))
.then(RaisePlan.raise(tickFlag)) .then(RaisePlan.raise(tickFlag))
.simplify(); .simplify();
var recreate = SimplePlan.<RenderContext>of(context -> blockEntitiesStorage.recreateAll(context.partialTick()), context -> entitiesStorage.recreateAll(context.partialTick()), context -> effectsStorage.recreateAll(context.partialTick())); var recreate = SimplePlan.<RenderContext>of(context -> blockEntitiesStorage.recreateAll(context.partialTick()), context -> entitiesStorage.recreateAll(context.partialTick()), context -> effectsStorage.recreateAll(context.partialTick()));
var update = MapContextPlan.map(this::createVisualFrameContext) var update = MapContextPlan.map(this::createVisualFrameContext)
.to(NestedPlan.of(SimplePlan.<VisualFrameContext>of(context -> blockEntities.processQueue(0)) .to(NestedPlan.of(blockEntities.framePlan(), entities.framePlan(), effects.framePlan()));
.then(blockEntitiesStorage.getFramePlan()), SimplePlan.<VisualFrameContext>of(context -> entities.processQueue(0))
.then(entitiesStorage.getFramePlan()), SimplePlan.<VisualFrameContext>of(context -> effects.processQueue(0))
.then(effectsStorage.getFramePlan())));
framePlan = IfElsePlan.on((RenderContext ctx) -> engine.updateRenderOrigin(ctx.camera())) framePlan = IfElsePlan.on((RenderContext ctx) -> engine.updateRenderOrigin(ctx.camera()))
.ifTrue(recreate) .ifTrue(recreate)

View File

@ -3,9 +3,13 @@ package com.jozufozu.flywheel.impl.visualization.manager;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; 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.api.visualization.VisualManager;
import com.jozufozu.flywheel.impl.visualization.storage.Storage; import com.jozufozu.flywheel.impl.visualization.storage.Storage;
import com.jozufozu.flywheel.impl.visualization.storage.Transaction; import com.jozufozu.flywheel.impl.visualization.storage.Transaction;
import com.jozufozu.flywheel.lib.task.SimplePlan;
public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager<T> { public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager<T> {
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>(); private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
@ -60,4 +64,13 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
} }
} }
public Plan<VisualFrameContext> framePlan() {
return SimplePlan.<VisualFrameContext>of(context -> processQueue(context.partialTick()))
.then(storage.getFramePlan());
}
public Plan<VisualTickContext> tickPlan() {
return SimplePlan.<VisualTickContext>of(context -> processQueue(0))
.then(storage.getTickPlan());
}
} }

View File

@ -2,9 +2,13 @@ package com.jozufozu.flywheel.impl.visualization.storage;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor; import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.api.visual.LitVisual; import com.jozufozu.flywheel.api.visual.LitVisual;
@ -30,25 +34,28 @@ public class LitVisualStorage {
private final Map<LitVisual, LongSet> visuals2Sections = new WeakHashMap<>(); private final Map<LitVisual, LongSet> visuals2Sections = new WeakHashMap<>();
private final Long2ObjectMap<List<Updater>> sections2Visuals = new Long2ObjectOpenHashMap<>(); private final Long2ObjectMap<List<Updater>> sections2Visuals = new Long2ObjectOpenHashMap<>();
private final LongSet sectionsQueue = new LongOpenHashSet(); private final Queue<LitVisual> movedVisuals = new ConcurrentLinkedQueue<>();
private final LongSet sectionsUpdatedThisFrame = new LongOpenHashSet();
private long updateId = INITIAL_UPDATE_ID; private long updateId = INITIAL_UPDATE_ID;
public Plan<VisualFrameContext> plan() { public Plan<VisualFrameContext> plan() {
return (SimplyComposedPlan<VisualFrameContext>) (TaskExecutor taskExecutor, VisualFrameContext context, Runnable onCompletion) -> { return (SimplyComposedPlan<VisualFrameContext>) (TaskExecutor taskExecutor, VisualFrameContext context, Runnable onCompletion) -> {
if (sectionsQueue.isEmpty()) { processMoved();
if (sectionsUpdatedThisFrame.isEmpty()) {
onCompletion.run(); onCompletion.run();
return; return;
} }
var sync = new Synchronizer(sectionsQueue.size(), () -> { var sync = new Synchronizer(sectionsUpdatedThisFrame.size(), () -> {
sectionsQueue.clear(); sectionsUpdatedThisFrame.clear();
onCompletion.run(); onCompletion.run();
}); });
long updateId = getNextUpdateId(); long updateId = getNextUpdateId();
for (long section : sectionsQueue) { for (long section : sectionsUpdatedThisFrame) {
var visuals = sections2Visuals.get(section); var visuals = sections2Visuals.get(section);
if (visuals != null && !visuals.isEmpty()) { if (visuals != null && !visuals.isEmpty()) {
taskExecutor.execute(() -> PlanUtil.distribute(taskExecutor, updateId, sync, visuals, Updater::updateLight)); 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() { private long getNextUpdateId() {
long out = this.updateId; long out = this.updateId;
@ -75,37 +92,47 @@ public class LitVisualStorage {
return visuals2Sections.isEmpty(); 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(); LongSet sections = new LongArraySet();
visual.collectLightSections(sections::add); 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()) { if (sections.isEmpty()) {
return; 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) { for (long section : sections) {
sections2Visuals.computeIfAbsent(section, $ -> new ObjectArrayList<>()) sections2Visuals.computeIfAbsent(section, $ -> new ObjectArrayList<>())
.add(updater); .add(updater);
} }
visuals2Sections.put(visual, sections);
} }
public void enqueueLightUpdateSections(LongSet 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); var sections = visuals2Sections.remove(visual);
if (sections == null) { if (sections == null) {
return; return false;
} }
for (long section : sections) { for (long section : sections) {
@ -114,12 +141,14 @@ public class LitVisualStorage {
listeners.remove(indexOfUpdater(listeners, visual)); listeners.remove(indexOfUpdater(listeners, visual));
} }
} }
return true;
} }
public void clear() { public void clear() {
visuals2Sections.clear(); visuals2Sections.clear();
sections2Visuals.clear(); sections2Visuals.clear();
sectionsQueue.clear(); sectionsUpdatedThisFrame.clear();
} }
private static int indexOfUpdater(List<Updater> listeners, LitVisual visual) { private static int indexOfUpdater(List<Updater> listeners, LitVisual visual) {
@ -132,6 +161,15 @@ public class LitVisualStorage {
return -1; 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. // 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? // TODO: is it faster to only use the synced variant to avoid virtual dispatches?
sealed interface Updater { 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);
}
}
} }

View File

@ -169,7 +169,7 @@ public abstract class Storage<T> {
} }
if (visual instanceof LitVisual lit) { if (visual instanceof LitVisual lit) {
litVisuals.add(lit); litVisuals.addAndInitNotifier(lit);
} }
} }

View File

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.lib.visual;
import java.util.function.LongConsumer; import java.util.function.LongConsumer;
import org.jetbrains.annotations.Nullable;
import org.joml.FrustumIntersection; import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.visual.BlockEntityVisual; import com.jozufozu.flywheel.api.visual.BlockEntityVisual;
@ -40,6 +41,8 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
protected final BlockPos pos; protected final BlockPos pos;
protected final BlockPos visualPos; protected final BlockPos visualPos;
protected final BlockState blockState; protected final BlockState blockState;
@Nullable
protected LitVisual.Notifier notifier;
public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity) { public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity) {
super(ctx, blockEntity.getLevel()); super(ctx, blockEntity.getLevel());
@ -59,6 +62,11 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
consumer.accept(SectionPos.asLong(pos)); consumer.accept(SectionPos.asLong(pos));
} }
@Override
public void initLightSectionNotifier(Notifier notifier) {
this.notifier = notifier;
}
@Override @Override
public boolean shouldReset() { public boolean shouldReset() {
return blockEntity.getBlockState() != blockState; return blockEntity.getBlockState() != blockState;