mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-11-10 12:34:11 +01:00
Light refactoring
- Run light updates in parallel to the visual frame plans - Add null check in AbstractVisual#relight - Add AbstractVisual#relight taking an iterable. - Begin frame is after light updates are complete. I tried dividing some work to run before, but it doesn't really make a difference. - Remove LightUpdatedLevel. - Remove FrameContext. - LightUpdater no longer runs ticks. - LightUpdater no longer stores things weakly. - Fix some docs.
This commit is contained in:
parent
77fd9dab1e
commit
bc9f156cfd
@ -14,20 +14,18 @@ public interface Visual {
|
||||
|
||||
/**
|
||||
* Update instances here. Good for when instances don't change very often and when animations are GPU based.
|
||||
*
|
||||
* <br><br> If your animations are complex or more CPU driven, see {@link DynamicVisual} or {@link TickableVisual}.
|
||||
* <br>
|
||||
* <br> If your animations are complex or more CPU driven, see {@link DynamicVisual} or {@link TickableVisual}.
|
||||
*/
|
||||
void update(float partialTick);
|
||||
|
||||
/**
|
||||
* When a visual is reset, the visual is deleted and re-created.
|
||||
*
|
||||
* <p>
|
||||
* Just before {@link #update()} would be called, {@code shouldReset()} is checked.
|
||||
* If this function returns {@code true}, then this visual will be {@link #delete deleted},
|
||||
* and another visual will be constructed to replace it. This allows for more sane resource
|
||||
* acquisition compared to trying to update everything within the lifetime of a visual.
|
||||
* </p>
|
||||
* <br>
|
||||
* Just before {@link #update)} would be called, {@code shouldReset} is checked.
|
||||
* If this function returns {@code true}, then this visual will be {@link #delete deleted},
|
||||
* and another visual will be constructed to replace it. This allows for more sane resource
|
||||
* acquisition compared to trying to update everything within the lifetime of a visual.
|
||||
*
|
||||
* @return {@code true} if this visual should be discarded and refreshed.
|
||||
*/
|
||||
|
@ -1,24 +0,0 @@
|
||||
package com.jozufozu.flywheel.impl.visualization;
|
||||
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import com.jozufozu.flywheel.api.event.RenderContext;
|
||||
|
||||
import net.minecraft.core.Vec3i;
|
||||
|
||||
public record FrameContext(double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum, float partialTick) {
|
||||
public static FrameContext create(RenderContext context, Vec3i renderOrigin) {
|
||||
var cameraPos = context.camera()
|
||||
.getPosition();
|
||||
double cameraX = cameraPos.x;
|
||||
double cameraY = cameraPos.y;
|
||||
double cameraZ = cameraPos.z;
|
||||
|
||||
Matrix4f viewProjection = new Matrix4f(context.viewProjection());
|
||||
viewProjection.translate((float) (renderOrigin.getX() - cameraX), (float) (renderOrigin.getY() - cameraY), (float) (renderOrigin.getZ() - cameraZ));
|
||||
FrustumIntersection frustum = new FrustumIntersection(viewProjection);
|
||||
|
||||
return new FrameContext(cameraX, cameraY, cameraZ, frustum, context.partialTick());
|
||||
}
|
||||
}
|
@ -6,6 +6,8 @@ import java.util.SortedSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.FrustumIntersection;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import com.jozufozu.flywheel.api.backend.BackendManager;
|
||||
import com.jozufozu.flywheel.api.backend.Engine;
|
||||
@ -100,21 +102,25 @@ public class VisualizationManagerImpl implements VisualizationManager, Supplier<
|
||||
.then(RaisePlan.raise(tickFlag))
|
||||
.simplify();
|
||||
|
||||
var lightUpdatePlan = lightUpdater.plan();
|
||||
|
||||
var recreate = SimplePlan.<RenderContext>of(context -> blockEntities.getStorage()
|
||||
.recreateAll(context.partialTick()), context -> entities.getStorage()
|
||||
.recreateAll(context.partialTick()), context -> effects.getStorage()
|
||||
.recreateAll(context.partialTick()))
|
||||
.then(lightUpdatePlan);
|
||||
|
||||
var update = SimplePlan.<RenderContext>of(context -> blockEntities.processQueue(context.partialTick()), context -> entities.processQueue(context.partialTick()), context -> effects.processQueue(context.partialTick()))
|
||||
.then(lightUpdatePlan.and(MapContextPlan.map(this::createVisualContext)
|
||||
.to(NestedPlan.of(blockEntities.getStorage()
|
||||
.getFramePlan(), entities.getStorage()
|
||||
.getFramePlan(), effects.getStorage()
|
||||
.getFramePlan()))));
|
||||
|
||||
framePlan = IfElsePlan.on((RenderContext ctx) -> engine.updateRenderOrigin(ctx.camera()))
|
||||
.ifTrue(MapContextPlan.map(RenderContext::partialTick)
|
||||
.to(blockEntities.createRecreationPlan()
|
||||
.and(entities.createRecreationPlan())
|
||||
.and(effects.createRecreationPlan())))
|
||||
.ifFalse(MapContextPlan.map((RenderContext ctx) -> createVisualContext(FrameContext.create(ctx, engine.renderOrigin())))
|
||||
.to(NestedPlan.of(SimplePlan.<VisualFrameContext>of(context -> blockEntities.processQueue(context.partialTick()))
|
||||
.then(blockEntities.getStorage()
|
||||
.getFramePlan()), SimplePlan.<VisualFrameContext>of(context -> entities.processQueue(context.partialTick()))
|
||||
.then(entities.getStorage()
|
||||
.getFramePlan()), SimplePlan.<VisualFrameContext>of(context -> effects.processQueue(context.partialTick()))
|
||||
.then(effects.getStorage()
|
||||
.getFramePlan()))))
|
||||
.ifTrue(recreate)
|
||||
.ifFalse(update)
|
||||
.plan()
|
||||
.then(lightUpdater.plan())
|
||||
.then(RaisePlan.raise(frameVisualsFlag))
|
||||
.then(engine.createFramePlan())
|
||||
.then(RaisePlan.raise(frameFlag))
|
||||
@ -126,8 +132,19 @@ public class VisualizationManagerImpl implements VisualizationManager, Supplier<
|
||||
}
|
||||
}
|
||||
|
||||
private VisualFrameContext createVisualContext(FrameContext ctx) {
|
||||
return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), ctx.partialTick(), frameLimiter);
|
||||
private VisualFrameContext createVisualContext(RenderContext ctx) {
|
||||
Vec3i renderOrigin = engine.renderOrigin();
|
||||
var cameraPos = ctx.camera()
|
||||
.getPosition();
|
||||
double cameraX = cameraPos.x;
|
||||
double cameraY = cameraPos.y;
|
||||
double cameraZ = cameraPos.z;
|
||||
|
||||
Matrix4f viewProjection = new Matrix4f(ctx.viewProjection());
|
||||
viewProjection.translate((float) (renderOrigin.getX() - cameraX), (float) (renderOrigin.getY() - cameraY), (float) (renderOrigin.getZ() - cameraZ));
|
||||
FrustumIntersection frustum = new FrustumIntersection(viewProjection);
|
||||
|
||||
return new VisualFrameContext(cameraX, cameraY, cameraZ, frustum, ctx.partialTick(), frameLimiter);
|
||||
}
|
||||
|
||||
private VisualTickContext createVisualTickContext(TickContext ctx) {
|
||||
@ -235,8 +252,6 @@ public class VisualizationManagerImpl implements VisualizationManager, Supplier<
|
||||
|
||||
tickLimiter.tick();
|
||||
|
||||
lightUpdater.tick();
|
||||
|
||||
tickPlan.execute(taskExecutor, new TickContext(cameraX, cameraY, cameraZ));
|
||||
}
|
||||
|
||||
@ -257,6 +272,7 @@ public class VisualizationManagerImpl implements VisualizationManager, Supplier<
|
||||
frameFlag.lower();
|
||||
|
||||
frameLimiter.tick();
|
||||
|
||||
framePlan.execute(taskExecutor, context);
|
||||
}
|
||||
|
||||
|
@ -3,16 +3,14 @@ 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.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<T, S extends Storage<T>> implements VisualManager<T> {
|
||||
private final Queue<Transaction<T>> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
protected final S storage;
|
||||
private final S storage;
|
||||
|
||||
public VisualManagerImpl(S storage) {
|
||||
this.storage = storage;
|
||||
@ -50,10 +48,6 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
|
||||
queue.add(Transaction.update(obj));
|
||||
}
|
||||
|
||||
public Plan<Float> createRecreationPlan() {
|
||||
return SimplePlan.of(getStorage()::recreateAll);
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
getStorage().invalidate();
|
||||
}
|
||||
|
@ -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.<p>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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<LightListener, LongSet> listenersAndTheirSections = new WeakHashMap<>();
|
||||
private final Set<TickingLightListener> tickingListeners = FlwUtil.createWeakHashSet();
|
||||
private final Map<LightListener, LongSet> listenersAndTheirSections = new WeakHashMap<>();
|
||||
private final Long2ObjectMap<List<LightListener>> listenersBySection = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
private final Queue<LightListener> additionQueue = new ConcurrentLinkedQueue<>();
|
||||
private final LongSet sectionsQueue = new LongOpenHashSet();
|
||||
@ -49,30 +49,32 @@ public class LightUpdaterImpl implements LightUpdater {
|
||||
}
|
||||
|
||||
public Plan<RenderContext> 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.<RenderContext>of(this::processQueue)
|
||||
.then(IfElsePlan.<RenderContext>on(() -> !sectionsQueue.isEmpty())
|
||||
.ifTrue(ForEachPlan.of(() -> listenersAndTheirSections.entrySet()
|
||||
.stream()
|
||||
.toList(), this::updateOneEntry))
|
||||
.plan())
|
||||
.then(SimplePlan.of(() -> sectionsQueue.clear()));
|
||||
}
|
||||
return (SimplyComposedPlan<RenderContext>) (TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) -> {
|
||||
processQueue();
|
||||
|
||||
private void updateOneEntry(Map.Entry<LightListener, LongSet> 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<LightListener> 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<Box> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.jozufozu.flywheel.lib.light;
|
||||
|
||||
// TODO: remove
|
||||
public interface TickingLightListener extends LightListener {
|
||||
/**
|
||||
* Called every tick for active listeners.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user