package com.jozufozu.flywheel.light; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Stream; import com.jozufozu.flywheel.util.WeakHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.SectionPos; import net.minecraft.world.IBlockDisplayReader; import net.minecraft.world.LightType; /** * Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated. */ public class LightUpdater { private static final Map light = new HashMap<>(); public static LightUpdater get(IBlockDisplayReader world) { return light.computeIfAbsent(world, LightUpdater::new); } private final LightProvider provider; private final WeakHashSet movingListeners = new WeakHashSet<>(); private final WeakContainmentMultiMap sections = new WeakContainmentMultiMap<>(); private final WeakContainmentMultiMap chunks = new WeakContainmentMultiMap<>(); public LightUpdater(IBlockDisplayReader world) { provider = BasicProvider.get(world); } public LightProvider getProvider() { return provider; } public void tick() { for (IMovingListener listener : movingListeners) { if (listener.update(provider)) { addListener(listener); } } } /** * Add a listener. * @param listener The object that wants to receive light update notifications. */ public void addListener(ILightUpdateListener listener) { if (listener instanceof IMovingListener) movingListeners.add(((IMovingListener) listener)); ImmutableBox box = listener.getVolume(); LongSet sections = this.sections.getAndResetContainment(listener); LongSet chunks = this.chunks.getAndResetContainment(listener); int minX = SectionPos.blockToSectionCoord(box.getMinX()); int minY = SectionPos.blockToSectionCoord(box.getMinY()); int minZ = SectionPos.blockToSectionCoord(box.getMinZ()); int maxX = SectionPos.blockToSectionCoord(box.getMaxX()); int maxY = SectionPos.blockToSectionCoord(box.getMaxY()); int maxZ = SectionPos.blockToSectionCoord(box.getMaxZ()); for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { for (int y = minY; y <= maxY; y++) { long sectionPos = SectionPos.asLong(x, y, z); this.sections.put(sectionPos, listener); sections.add(sectionPos); } long chunkPos = SectionPos.asLong(x, 0, z); this.chunks.put(chunkPos, listener); chunks.add(chunkPos); } } } public void removeListener(ILightUpdateListener listener) { this.sections.remove(listener); this.chunks.remove(listener); } /** * Dispatch light updates to all registered {@link ILightUpdateListener}s. * @param type The type of light that changed. * @param sectionPos A long representing the section position where light changed. */ public void onLightUpdate(LightType type, long sectionPos) { Set set = sections.get(sectionPos); if (set == null || set.isEmpty()) return; set.removeIf(l -> l.status().shouldRemove()); ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos)); set.parallelStream().forEach(listener -> listener.onLightUpdate(provider, type, chunkBox)); } /** * Dispatch light updates to all registered {@link ILightUpdateListener}s * when the server sends lighting data for an entire chunk. * */ public void onLightPacket(int chunkX, int chunkZ) { long chunkPos = SectionPos.asLong(chunkX, 0, chunkZ); Set set = chunks.get(chunkPos); if (set == null || set.isEmpty()) return; set.removeIf(l -> l.status().shouldRemove()); set.parallelStream().forEach(listener -> listener.onLightPacket(provider, chunkX, chunkZ)); } public static long blockToSection(BlockPos pos) { return SectionPos.asLong(pos.getX(), pos.getY(), pos.getZ()); } public static long sectionToChunk(long sectionPos) { return sectionPos & 0xFFFFFFFFFFF_00000L; } public Stream getAllBoxes() { return chunks.stream().map(ILightUpdateListener::getVolume); } public boolean isEmpty() { return chunks.isEmpty(); } }