Light updated

- Promote LightUpdater to the API.
- Include LightUpdater in VisualizationContext.
- Explicitly launch a plan to run light updates.
- Misc tweaks:
  - The tick/frame limiters are now shared between visual managers.
  - The VisualizationManager assembles the frame/tick plans itself to
    avoid duplicate context mapping and to allow for reorganization
    in later commits.
This commit is contained in:
Jozufozu 2024-01-20 23:43:21 -08:00
parent f9e5d33296
commit 91738e38a8
18 changed files with 209 additions and 227 deletions

View file

@ -21,7 +21,6 @@ import com.jozufozu.flywheel.impl.RegistryImpl;
import com.jozufozu.flywheel.impl.visualization.VisualizationEventHandler;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import com.jozufozu.flywheel.lib.material.CutoutShaders;
import com.jozufozu.flywheel.lib.material.FogShaders;
import com.jozufozu.flywheel.lib.material.StandardMaterialShaders;
@ -97,7 +96,6 @@ public class Flywheel {
forgeEventBus.addListener(Uniforms::onReloadLevelRenderer);
forgeEventBus.addListener(LightUpdater::onClientTick);
forgeEventBus.addListener((LevelEvent.Unload e) -> LevelAttached.onUnloadLevel(e));
// forgeEventBus.addListener(ExampleEffect::tick);

View file

@ -0,0 +1,9 @@
package com.jozufozu.flywheel.api.visualization;
import com.jozufozu.flywheel.lib.light.LightListener;
public interface LightUpdater {
void addListener(LightListener listener);
void removeListener(LightListener listener);
}

View file

@ -11,5 +11,5 @@ import net.minecraft.core.Vec3i;
* @param renderOrigin The origin of the renderer as a world position.
* All models render as if this position is (0, 0, 0).
*/
public record VisualizationContext(InstancerProvider instancerProvider, Vec3i renderOrigin) {
public record VisualizationContext(InstancerProvider instancerProvider, LightUpdater lightUpdater, Vec3i renderOrigin) {
}

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.impl.visualization;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
@ -16,20 +17,30 @@ import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.Effect;
import com.jozufozu.flywheel.api.visual.TickableVisual;
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.VisualizationContext;
import com.jozufozu.flywheel.api.visualization.VisualizationLevel;
import com.jozufozu.flywheel.api.visualization.VisualizationManager;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.extension.LevelExtension;
import com.jozufozu.flywheel.impl.task.FlwTaskExecutor;
import com.jozufozu.flywheel.impl.visualization.manager.BlockEntityStorage;
import com.jozufozu.flywheel.impl.visualization.manager.EffectStorage;
import com.jozufozu.flywheel.impl.visualization.manager.EntityStorage;
import com.jozufozu.flywheel.impl.visualization.manager.VisualManagerImpl;
import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiterImpl;
import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter;
import com.jozufozu.flywheel.lib.light.LightUpdaterImpl;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.IfElsePlan;
import com.jozufozu.flywheel.lib.task.MapContextPlan;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.NestedPlan;
import com.jozufozu.flywheel.lib.task.RaisePlan;
import com.jozufozu.flywheel.lib.task.SimplePlan;
import com.jozufozu.flywheel.lib.util.LevelAttached;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@ -44,11 +55,12 @@ import net.minecraft.world.level.block.entity.BlockEntity;
/**
* A manager class for a single world where visualization is supported.
*/
public class VisualizationManagerImpl implements VisualizationManager {
public class VisualizationManagerImpl implements VisualizationManager, Supplier<VisualizationContext> {
private static final LevelAttached<VisualizationManagerImpl> MANAGERS = new LevelAttached<>(VisualizationManagerImpl::new, VisualizationManagerImpl::delete);
private final Engine engine;
private final TaskExecutor taskExecutor;
private final LightUpdaterImpl lightUpdater;
private final VisualManagerImpl<BlockEntity, BlockEntityStorage> blockEntities;
private final VisualManagerImpl<Entity, EntityStorage> entities;
@ -61,18 +73,30 @@ public class VisualizationManagerImpl implements VisualizationManager {
private final Flag frameVisualsFlag = new NamedFlag("frameVisualUpdates");
private final Flag frameFlag = new NamedFlag("frameComplete");
protected DistanceUpdateLimiterImpl tickLimiter;
protected DistanceUpdateLimiterImpl frameLimiter;
private VisualizationManagerImpl(LevelAccessor level) {
tickLimiter = createUpdateLimiter();
frameLimiter = createUpdateLimiter();
engine = BackendManager.getBackend()
.createEngine(level);
taskExecutor = FlwTaskExecutor.get();
lightUpdater = new LightUpdaterImpl();
blockEntities = new VisualManagerImpl<>(new BlockEntityStorage(engine));
entities = new VisualManagerImpl<>(new EntityStorage(engine));
effects = new VisualManagerImpl<>(new EffectStorage(engine));
blockEntities = new VisualManagerImpl<>(new BlockEntityStorage(this));
entities = new VisualManagerImpl<>(new EntityStorage(this));
effects = new VisualManagerImpl<>(new EffectStorage(this));
tickPlan = blockEntities.createTickPlan()
.and(entities.createTickPlan())
.and(effects.createTickPlan())
tickPlan = MapContextPlan.map(this::createVisualTickContext)
.to(NestedPlan.of(SimplePlan.<VisualTickContext>of(context -> blockEntities.processQueue(0))
.then(blockEntities.getStorage()
.getTickPlan()), SimplePlan.<VisualTickContext>of(context -> entities.processQueue(0))
.then(entities.getStorage()
.getTickPlan()), SimplePlan.<VisualTickContext>of(context -> effects.processQueue(0))
.then(effects.getStorage()
.getTickPlan())))
.then(RaisePlan.raise(tickFlag))
.simplify();
@ -81,11 +105,16 @@ public class VisualizationManagerImpl implements VisualizationManager {
.to(blockEntities.createRecreationPlan()
.and(entities.createRecreationPlan())
.and(effects.createRecreationPlan())))
.ifFalse(MapContextPlan.map((RenderContext ctx) -> FrameContext.create(ctx, engine.renderOrigin()))
.to(blockEntities.createFramePlan()
.and(entities.createFramePlan())
.and(effects.createFramePlan())))
.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()))))
.plan()
.then(lightUpdater.plan())
.then(RaisePlan.raise(frameVisualsFlag))
.then(engine.createFramePlan())
.then(RaisePlan.raise(frameFlag))
@ -97,6 +126,23 @@ public class VisualizationManagerImpl implements VisualizationManager {
}
}
private VisualFrameContext createVisualContext(FrameContext ctx) {
return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), ctx.partialTick(), frameLimiter);
}
private VisualTickContext createVisualTickContext(TickContext ctx) {
return new VisualTickContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), frameLimiter);
}
protected DistanceUpdateLimiterImpl createUpdateLimiter() {
if (FlwConfig.get()
.limitUpdates()) {
return new BandedPrimeLimiter();
} else {
return new NonLimiter();
}
}
public static boolean supportsVisualization(@Nullable LevelAccessor level) {
if (!BackendManager.isOn()) {
return false;
@ -136,14 +182,19 @@ public class VisualizationManagerImpl implements VisualizationManager {
// TODO: Consider making these reset actions reuse the existing game objects instead of re-adding them
// potentially by keeping the same VisualizationManagerImpl and deleting the engine and visuals but not the game objects
public static void reset(LevelAccessor level) {
MANAGERS.remove(level);
}
public static void resetAll() {
MANAGERS.reset();
}
@Override
public VisualizationContext get() {
return new VisualizationContext(engine, lightUpdater, engine.renderOrigin());
}
@Override
public Vec3i getRenderOrigin() {
return engine.renderOrigin();
@ -164,6 +215,10 @@ public class VisualizationManagerImpl implements VisualizationManager {
return effects;
}
public LightUpdaterImpl getLightUpdater() {
return lightUpdater;
}
/**
* Tick the visuals after the game has ticked:
* <p>
@ -178,6 +233,10 @@ public class VisualizationManagerImpl implements VisualizationManager {
taskExecutor.syncUntil(tickFlag::isRaised);
tickFlag.lower();
tickLimiter.tick();
lightUpdater.tick();
tickPlan.execute(taskExecutor, new TickContext(cameraX, cameraY, cameraZ));
}
@ -196,6 +255,8 @@ public class VisualizationManagerImpl implements VisualizationManager {
frameVisualsFlag.lower();
frameFlag.lower();
frameLimiter.tick();
framePlan.execute(taskExecutor, context);
}

View file

@ -1,8 +1,9 @@
package com.jozufozu.flywheel.impl.visualization.manager;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.BlockEntityVisual;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
@ -19,8 +20,8 @@ import net.minecraft.world.level.block.entity.BlockEntity;
public class BlockEntityStorage extends Storage<BlockEntity> {
private final Long2ObjectMap<BlockEntityVisual<?>> posLookup = new Long2ObjectOpenHashMap<>();
public BlockEntityStorage(Engine engine) {
super(engine);
public BlockEntityStorage(Supplier<VisualizationContext> visualizationContextSupplier) {
super(visualizationContextSupplier);
}
public BlockEntityVisual<?> visualAtPos(long pos) {
@ -59,7 +60,7 @@ public class BlockEntityStorage extends Storage<BlockEntity> {
return null;
}
var visual = visualizer.createVisual(new VisualizationContext(engine, engine.renderOrigin()), obj);
var visual = visualizer.createVisual(visualizationContextSupplier.get(), obj);
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), visual);

View file

@ -1,19 +1,20 @@
package com.jozufozu.flywheel.impl.visualization.manager;
import com.jozufozu.flywheel.api.backend.Engine;
import java.util.function.Supplier;
import com.jozufozu.flywheel.api.visual.Effect;
import com.jozufozu.flywheel.api.visual.EffectVisual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
public class EffectStorage extends Storage<Effect> {
public EffectStorage(Engine engine) {
super(engine);
public EffectStorage(Supplier<VisualizationContext> visualizationContextSupplier) {
super(visualizationContextSupplier);
}
@Override
protected EffectVisual<?> createRaw(Effect obj) {
return obj.visualize(new VisualizationContext(engine, engine.renderOrigin()));
return obj.visualize(visualizationContextSupplier.get());
}
@Override

View file

@ -1,8 +1,9 @@
package com.jozufozu.flywheel.impl.visualization.manager;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.impl.visualization.VisualizationHelper;
@ -12,8 +13,8 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
public class EntityStorage extends Storage<Entity> {
public EntityStorage(Engine engine) {
super(engine);
public EntityStorage(Supplier<VisualizationContext> visualizationContextSupplier) {
super(visualizationContextSupplier);
}
@Override
@ -24,7 +25,7 @@ public class EntityStorage extends Storage<Entity> {
return null;
}
return visualizer.createVisual(new VisualizationContext(engine, engine.renderOrigin()), obj);
return visualizer.createVisual(visualizationContextSupplier.get(), obj);
}
@Override

View file

@ -4,31 +4,17 @@ 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.config.FlwConfig;
import com.jozufozu.flywheel.impl.visualization.FrameContext;
import com.jozufozu.flywheel.impl.visualization.TickContext;
import com.jozufozu.flywheel.impl.visualization.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.impl.visualization.ratelimit.DistanceUpdateLimiterImpl;
import com.jozufozu.flywheel.impl.visualization.ratelimit.NonLimiter;
import com.jozufozu.flywheel.impl.visualization.storage.Storage;
import com.jozufozu.flywheel.impl.visualization.storage.Transaction;
import com.jozufozu.flywheel.lib.task.MapContextPlan;
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 DistanceUpdateLimiterImpl tickLimiter;
protected DistanceUpdateLimiterImpl frameLimiter;
protected final S storage;
public VisualManagerImpl(S storage) {
tickLimiter = createUpdateLimiter();
frameLimiter = createUpdateLimiter();
this.storage = storage;
}
@ -36,15 +22,6 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
return storage;
}
protected DistanceUpdateLimiterImpl createUpdateLimiter() {
if (FlwConfig.get()
.limitUpdates()) {
return new BandedPrimeLimiter();
} else {
return new NonLimiter();
}
}
@Override
public int getVisualCount() {
return getStorage().getAllVisuals().size();
@ -81,7 +58,7 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
getStorage().invalidate();
}
protected void processQueue(float partialTick) {
public void processQueue(float partialTick) {
var storage = getStorage();
Transaction<T> transaction;
while ((transaction = queue.poll()) != null) {
@ -89,29 +66,4 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
}
}
public Plan<TickContext> createTickPlan() {
return SimplePlan.<TickContext>of(() -> {
tickLimiter.tick();
processQueue(0);
})
.then(MapContextPlan.map(this::createVisualTickContext)
.to(getStorage().getTickPlan()));
}
public Plan<FrameContext> createFramePlan() {
return SimplePlan.<FrameContext>of(context -> {
frameLimiter.tick();
processQueue(context.partialTick());
})
.then(MapContextPlan.map(this::createVisualContext)
.to(getStorage().getFramePlan()));
}
private VisualFrameContext createVisualContext(FrameContext ctx) {
return new VisualFrameContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), ctx.frustum(), ctx.partialTick(), frameLimiter);
}
private VisualTickContext createVisualTickContext(TickContext ctx) {
return new VisualTickContext(ctx.cameraX(), ctx.cameraY(), ctx.cameraZ(), frameLimiter);
}
}

View file

@ -4,10 +4,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.visual.DynamicVisual;
import com.jozufozu.flywheel.api.visual.PlannedVisual;
@ -15,12 +15,13 @@ import com.jozufozu.flywheel.api.visual.TickableVisual;
import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visual.VisualFrameContext;
import com.jozufozu.flywheel.api.visual.VisualTickContext;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.task.ForEachPlan;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
public abstract class Storage<T> {
protected final Engine engine;
protected final Supplier<VisualizationContext> visualizationContextSupplier;
protected final List<TickableVisual> tickableVisuals = new ArrayList<>();
protected final List<DynamicVisual> dynamicVisuals = new ArrayList<>();
protected final List<PlannedVisual> plannedVisuals = new ArrayList<>();
@ -33,8 +34,8 @@ public abstract class Storage<T> {
private final Map<T, Visual> visuals = new Reference2ObjectOpenHashMap<>();
public Storage(Engine engine) {
this.engine = engine;
public Storage(Supplier<VisualizationContext> visualizationContextSupplier) {
this.visualizationContextSupplier = visualizationContextSupplier;
}
public Collection<Visual> getAllVisuals() {

View file

@ -1,45 +0,0 @@
package com.jozufozu.flywheel.lib.light;
import java.util.stream.Stream;
import com.jozufozu.flywheel.lib.box.Box;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer;
public final class DummyLightUpdater extends LightUpdater {
public static final DummyLightUpdater INSTANCE = new DummyLightUpdater();
private DummyLightUpdater() {
}
@Override
public void tick() {
// noop
}
@Override
public void addListener(LightListener listener) {
// noop
}
@Override
public void removeListener(LightListener listener) {
// noop
}
@Override
public void onLightUpdate(LightLayer type, SectionPos pos) {
// noop
}
@Override
public Stream<Box> getAllBoxes() {
return Stream.empty();
}
@Override
public boolean isEmpty() {
return true;
}
}

View file

@ -7,7 +7,7 @@ import net.minecraft.world.level.LightLayer;
/**
* Implementors of this interface may choose to subscribe to light updates by calling
* {@link LightUpdater#addListener(LightListener)}.<p>
* {@link LightUpdaterImpl#addListener(LightListener)}.<p>
*
* It is the responsibility of the implementor to keep a reference to the level an object is contained in.
*/

View file

@ -1,22 +1,26 @@
package com.jozufozu.flywheel.lib.light;
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 org.jetbrains.annotations.ApiStatus;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.task.Plan;
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.util.LevelAttached;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.Minecraft;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LightLayer;
import net.minecraftforge.event.TickEvent;
/**
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
@ -24,35 +28,12 @@ import net.minecraftforge.event.TickEvent;
* @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement
* {@link LightUpdatedLevel} for LightUpdater to work with them.
*/
public class LightUpdater {
private static final LevelAttached<LightUpdater> UPDATERS = new LevelAttached<>(level -> new LightUpdater());
private final WeakContainmentMultiMap<LightListener> listenersBySection = new WeakContainmentMultiMap<>();
public class LightUpdaterImpl implements LightUpdater {
private final WeakHashMap<LightListener, LongSet> listenersAndTheirSections = new WeakHashMap<>();
private final Set<TickingLightListener> tickingListeners = FlwUtil.createWeakHashSet();
private final Queue<LightListener> additionQueue = new ConcurrentLinkedQueue<>();
public static boolean supports(LevelAccessor level) {
// The client level is guaranteed to receive updates.
if (Minecraft.getInstance().level == level) {
return true;
}
// Custom/fake levels need to indicate that LightUpdater has meaning.
if (level instanceof LightUpdatedLevel c) {
return c.receivesLightUpdates();
}
return false;
}
public static LightUpdater get(LevelAccessor level) {
if (supports(level)) {
// The level is valid, so add it to the map.
return UPDATERS.get(level);
} else {
// Fake light updater for a fake level.
return DummyLightUpdater.INSTANCE;
}
}
private final LongSet sectionsQueue = new LongOpenHashSet();
/**
* Add a listener.
@ -64,48 +45,47 @@ public class LightUpdater {
}
public void removeListener(LightListener listener) {
listenersBySection.remove(listener);
listenersAndTheirSections.remove(listener);
}
/**
* Dispatch light updates to all registered {@link LightListener}s.
*
* @param type The type of light that changed.
* @param pos The section position where light changed.
*/
public void onLightUpdate(LightLayer type, SectionPos pos) {
processQueue();
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()));
}
Set<LightListener> listeners = listenersBySection.get(pos.asLong());
private void updateOneEntry(Map.Entry<LightListener, LongSet> entry) {
if (listeners == null || listeners.isEmpty()) {
return;
}
updateOne(entry.getKey(), entry.getValue());
listeners.removeIf(LightListener::isInvalid);
}
for (LightListener listener : listeners) {
listener.onLightUpdate(type, pos);
private void updateOne(LightListener listener, LongSet containedSections) {
for (long l : containedSections.toLongArray()) {
if (sectionsQueue.contains(l)) {
listener.onLightUpdate(LightLayer.BLOCK, SectionPos.of(l));
break;
}
}
}
public Stream<Box> getAllBoxes() {
return listenersBySection.stream().map(LightListener::getVolume);
return listenersAndTheirSections.keySet()
.stream()
.map(LightListener::getVolume);
}
public boolean isEmpty() {
return listenersBySection.isEmpty();
return listenersAndTheirSections.isEmpty();
}
@ApiStatus.Internal
public static void onClientTick(TickEvent.ClientTickEvent event) {
if (event.phase == TickEvent.Phase.END && FlwUtil.isGameActive()) {
get(Minecraft.getInstance().level)
.tick();
}
}
void tick() {
public void tick() {
processQueue();
for (TickingLightListener tickingListener : tickingListeners) {
@ -129,7 +109,7 @@ public class LightUpdater {
Box box = listener.getVolume();
LongSet sections = listenersBySection.getAndResetContainment(listener);
LongSet sections = new LongArraySet();
int minX = SectionPos.blockToSectionCoord(box.getMinX());
int minY = SectionPos.blockToSectionCoord(box.getMinY());
@ -141,11 +121,15 @@ public class LightUpdater {
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
long sectionPos = SectionPos.asLong(x, y, z);
listenersBySection.put(sectionPos, listener);
sections.add(sectionPos);
sections.add(SectionPos.asLong(x, y, z));
}
}
}
listenersAndTheirSections.put(listener, sections);
}
public void notifySectionUpdates(LongSet sections) {
sectionsQueue.addAll(sections);
}
}

View file

@ -39,7 +39,9 @@ class WeakContainmentMultiMap<T> extends AbstractCollection<T> {
containmentSet.forEach((LongConsumer) l -> {
Set<T> listeners = forward.get(l);
if (listeners != null) listeners.remove(listener);
if (listeners != null) {
listeners.remove(listener);
}
});
containmentSet.clear();
@ -63,7 +65,9 @@ class WeakContainmentMultiMap<T> extends AbstractCollection<T> {
containmentSet.forEach((LongConsumer) l -> {
Set<T> listeners = forward.get(l);
if (listeners != null) listeners.remove(o);
if (listeners != null) {
listeners.remove(o);
}
});
containmentSet.clear();

View file

@ -7,7 +7,6 @@ import com.jozufozu.flywheel.api.visual.Visual;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.instance.FlatLit;
import com.jozufozu.flywheel.lib.light.LightListener;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
@ -37,7 +36,8 @@ public abstract class AbstractVisual implements Visual, LightListener {
@Override
public void init(float partialTick) {
LightUpdater.get(level).addListener(this);
visualizationContext.lightUpdater()
.addListener(this);
updateLight();
}
@ -67,6 +67,8 @@ public abstract class AbstractVisual implements Visual, LightListener {
}
_delete();
visualizationContext.lightUpdater()
.removeListener(this);
deleted = true;
}

View file

@ -0,0 +1,41 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.impl.visualization.VisualizationManagerImpl;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.world.level.lighting.LayerLightSectionStorage;
@Mixin(LayerLightSectionStorage.class)
public abstract class LayerLightSectionStorageMixin {
@Shadow
@Final
protected LongSet sectionsAffectedByLightUpdates;
@Shadow
@Final
protected LightChunkGetter chunkSource;
@Inject(at = @At("HEAD"), method = "swapSectionMap")
private void flywheel$listenForChangedSections(CallbackInfo ci) {
if (this.sectionsAffectedByLightUpdates.isEmpty()) {
return;
}
var manager = VisualizationManagerImpl.get((LevelAccessor) this.chunkSource.getLevel());
if (manager != null) {
manager.getLightUpdater()
.notifySectionUpdates(this.sectionsAffectedByLightUpdates);
}
}
}

View file

@ -49,6 +49,11 @@ abstract class LevelRendererMixin {
@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(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) {
flywheel$renderContext = RenderContext.create((LevelRenderer) (Object) this, level, renderBuffers, poseStack, projectionMatrix, camera, partialTick);
MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(flywheel$renderContext));

View file

@ -1,33 +0,0 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.chunk.ChunkSource;
@Mixin(ClientChunkCache.class)
abstract class LightUpdateMixin extends ChunkSource {
@Shadow
@Final
ClientLevel level;
/**
* JUSTIFICATION: This method is called after lighting updates are finished processing
* per section where a lighting change occurred that frame. On the client, Minecraft
* uses this method to inform the rendering system that it needs to redraw a chunk.
*/
@Inject(method = "onLightUpdate(Lnet/minecraft/world/level/LightLayer;Lnet/minecraft/core/SectionPos;)V", at = @At("HEAD"))
private void flywheel$onLightUpdate(LightLayer type, SectionPos pos, CallbackInfo ci) {
LightUpdater.get(level).onLightUpdate(type, pos);
}
}

View file

@ -10,9 +10,9 @@
"EntityTypeMixin",
"FogUpdateMixin",
"GlStateManagerMixin",
"LayerLightSectionStorageMixin",
"LevelMixin",
"LevelRendererMixin",
"LightUpdateMixin",
"MinecraftMixin",
"PoseStackMixin",
"VertexFormatMixin",