mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-03 19:06:27 +01:00
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:
parent
f9e5d33296
commit
91738e38a8
18 changed files with 209 additions and 227 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -10,9 +10,9 @@
|
|||
"EntityTypeMixin",
|
||||
"FogUpdateMixin",
|
||||
"GlStateManagerMixin",
|
||||
"LayerLightSectionStorageMixin",
|
||||
"LevelMixin",
|
||||
"LevelRendererMixin",
|
||||
"LightUpdateMixin",
|
||||
"MinecraftMixin",
|
||||
"PoseStackMixin",
|
||||
"VertexFormatMixin",
|
||||
|
|
Loading…
Reference in a new issue