createRecreationPlan() {
- return SimplePlan.of(getStorage()::recreateAll);
- }
-
public void invalidate() {
getStorage().invalidate();
}
diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdatedLevel.java b/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdatedLevel.java
deleted file mode 100644
index d2b8d952a..000000000
--- a/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdatedLevel.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.jozufozu.flywheel.lib.light;
-
-import net.minecraft.world.level.LevelAccessor;
-
-/**
- * Marker interface for custom/fake levels to indicate that LightUpdater should interact with it.
- *
- * Implement this if your custom level has light updates at all. If so, be sure to call
- * {@link com.jozufozu.flywheel.lib.util.LevelAttached#invalidateLevel} when your level is unloaded.
- */
-public interface LightUpdatedLevel extends LevelAccessor {
- /**
- * @return {@code true} if this level is passing light updates into LightUpdater.
- */
- default boolean receivesLightUpdates() {
- return true;
- }
-}
diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java b/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java
index 5813fd45a..a866a38cc 100644
--- a/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java
+++ b/src/main/java/com/jozufozu/flywheel/lib/light/LightUpdaterImpl.java
@@ -1,21 +1,24 @@
package com.jozufozu.flywheel.lib.light;
+import java.util.ArrayList;
+import java.util.List;
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 com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.task.Plan;
+import com.jozufozu.flywheel.api.task.TaskExecutor;
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.task.PlanUtil;
+import com.jozufozu.flywheel.lib.task.SimplyComposedPlan;
+import com.jozufozu.flywheel.lib.task.Synchronizer;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
@@ -24,13 +27,10 @@ import net.minecraft.world.level.LightLayer;
/**
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
- *
- * @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement
- * {@link LightUpdatedLevel} for LightUpdater to work with them.
*/
public class LightUpdaterImpl implements LightUpdater {
- private final WeakHashMap listenersAndTheirSections = new WeakHashMap<>();
- private final Set tickingListeners = FlwUtil.createWeakHashSet();
+ private final Map listenersAndTheirSections = new WeakHashMap<>();
+ private final Long2ObjectMap> listenersBySection = new Long2ObjectOpenHashMap<>();
private final Queue additionQueue = new ConcurrentLinkedQueue<>();
private final LongSet sectionsQueue = new LongOpenHashSet();
@@ -49,30 +49,32 @@ public class LightUpdaterImpl implements LightUpdater {
}
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()));
- }
+ return (SimplyComposedPlan) (TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) -> {
+ processQueue();
- private void updateOneEntry(Map.Entry entry) {
-
- updateOne(entry.getKey(), entry.getValue());
-
- }
-
- private void updateOne(LightListener listener, LongSet containedSections) {
- for (long l : containedSections.toLongArray()) {
- if (sectionsQueue.contains(l)) {
- listener.onLightUpdate(LightLayer.BLOCK, SectionPos.of(l));
- break;
+ if (sectionsQueue.isEmpty()) {
+ onCompletion.run();
+ return;
}
- }
+
+ var sync = new Synchronizer(sectionsQueue.size(), () -> {
+ sectionsQueue.clear();
+ onCompletion.run();
+ });
+
+ sectionsQueue.forEach((long section) -> {
+ List listeners = listenersBySection.get(section);
+ if (listeners != null && !listeners.isEmpty()) {
+ taskExecutor.execute(() -> {
+ PlanUtil.distribute(taskExecutor, SectionPos.of(section), sync, listeners, (listener, pos) -> {
+ listener.onLightUpdate(LightLayer.BLOCK, pos);
+ });
+ });
+ } else {
+ sync.decrementAndEventuallyRun();
+ }
+ });
+ };
}
public Stream getAllBoxes() {
@@ -85,16 +87,6 @@ public class LightUpdaterImpl implements LightUpdater {
return listenersAndTheirSections.isEmpty();
}
- public void tick() {
- processQueue();
-
- for (TickingLightListener tickingListener : tickingListeners) {
- if (tickingListener.tickLightListener()) {
- addListener(tickingListener);
- }
- }
- }
-
private synchronized void processQueue() {
LightListener listener;
while ((listener = additionQueue.poll()) != null) {
@@ -103,10 +95,6 @@ public class LightUpdaterImpl implements LightUpdater {
}
private void doAdd(LightListener listener) {
- if (listener instanceof TickingLightListener ticking) {
- tickingListeners.add(ticking);
- }
-
Box box = listener.getVolume();
LongSet sections = new LongArraySet();
@@ -121,7 +109,10 @@ public class LightUpdaterImpl implements LightUpdater {
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
- sections.add(SectionPos.asLong(x, y, z));
+ var longPos = SectionPos.asLong(x, y, z);
+ sections.add(longPos);
+ listenersBySection.computeIfAbsent(longPos, $ -> new ArrayList<>())
+ .add(listener);
}
}
}
diff --git a/src/main/java/com/jozufozu/flywheel/lib/light/TickingLightListener.java b/src/main/java/com/jozufozu/flywheel/lib/light/TickingLightListener.java
index 64fdf3b13..28530f1a9 100644
--- a/src/main/java/com/jozufozu/flywheel/lib/light/TickingLightListener.java
+++ b/src/main/java/com/jozufozu/flywheel/lib/light/TickingLightListener.java
@@ -1,5 +1,6 @@
package com.jozufozu.flywheel.lib.light;
+// TODO: remove
public interface TickingLightListener extends LightListener {
/**
* Called every tick for active listeners.
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 f54cb2d02..8dc4c8f2f 100644
--- a/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractVisual.java
+++ b/src/main/java/com/jozufozu/flywheel/lib/visual/AbstractVisual.java
@@ -1,5 +1,6 @@
package com.jozufozu.flywheel.lib.visual;
+import java.util.Objects;
import java.util.stream.Stream;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
@@ -88,6 +89,10 @@ public abstract class AbstractVisual implements Visual, LightListener {
protected void relight(int block, int sky, FlatLit... instances) {
for (FlatLit instance : instances) {
+ if (instance == null) {
+ continue;
+ }
+
instance.setLight(block, sky);
instance.handle()
.setChanged();
@@ -99,8 +104,24 @@ public abstract class AbstractVisual implements Visual, LightListener {
}
protected void relight(int block, int sky, Stream extends FlatLit> instances) {
- instances.forEach(model -> model.setLight(block, sky)
+ instances.filter(Objects::nonNull)
+ .forEach(instance -> instance.setLight(block, sky)
.handle()
.setChanged());
}
+
+ protected void relight(BlockPos pos, Iterable extends FlatLit> instances) {
+ relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), instances);
+ }
+
+ protected void relight(int block, int sky, Iterable extends FlatLit> instances) {
+ for (FlatLit instance : instances) {
+ if (instance == null) {
+ continue;
+ }
+ instance.setLight(block, sky)
+ .handle()
+ .setChanged();
+ }
+ }
}
diff --git a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java
index 2be9760fe..367d224db 100644
--- a/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java
+++ b/src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java
@@ -47,13 +47,9 @@ abstract class LevelRendererMixin {
@Unique
private RenderContext flywheel$renderContext;
- @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("HEAD"))
@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) {
+ private void flywheel$beginRender(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));