package com.boydti.fawe.example; import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.IntegerTrio; import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.TaskManager; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class NMSRelighter implements Relighter { private final NMSMappedFaweQueue queue; private final Map skyToRelight; private final Object present = new Object(); private final Map chunksToSend; private final ConcurrentLinkedQueue queuedSkyToRelight = new ConcurrentLinkedQueue<>(); private final Map lightQueue; private final AtomicBoolean lightLock = new AtomicBoolean(false); private final ConcurrentHashMap concurrentLightQueue; private final int maxY; private volatile boolean relighting = false; public final IntegerTrio mutableBlockPos = new IntegerTrio(); private static final int DISPATCH_SIZE = 64; private boolean removeFirst; public NMSRelighter(NMSMappedFaweQueue queue) { this.queue = queue; this.skyToRelight = new Long2ObjectOpenHashMap<>(); this.lightQueue = new Long2ObjectOpenHashMap<>(); this.chunksToSend = new Long2ObjectOpenHashMap<>(); this.concurrentLightQueue = new ConcurrentHashMap<>(); this.maxY = queue.getMaxY(); } @Override public boolean isEmpty() { return skyToRelight.isEmpty() && lightQueue.isEmpty() && queuedSkyToRelight.isEmpty() && concurrentLightQueue.isEmpty(); } @Override public synchronized void removeAndRelight(boolean sky) { removeFirst = true; fixLightingSafe(true); removeFirst = false; } private void set(int x, int y, int z, long[][][] map) { long[][] m1 = map[z]; if (m1 == null) { m1 = map[z] = new long[16][]; } long[] m2 = m1[x]; if (m2 == null) { m2 = m1[x] = new long[4]; } long value = m2[y >> 6] |= 1l << y; } public void addLightUpdate(int x, int y, int z) { long index = MathMan.pairInt(x >> 4, z >> 4); if (lightLock.compareAndSet(false, true)) { synchronized (lightQueue) { try { long[][][] currentMap = lightQueue.get(index); if (currentMap == null) { currentMap = new long[16][][]; this.lightQueue.put(index, currentMap); } set(x & 15, y, z & 15, currentMap); } finally { lightLock.set(false); } } } else { long[][][] currentMap = concurrentLightQueue.get(index); if (currentMap == null) { currentMap = new long[16][][]; this.concurrentLightQueue.put(index, currentMap); } set(x & 15, y, z & 15, currentMap); } } public boolean addChunk(int cx, int cz, byte[] fix, int bitmask) { RelightSkyEntry toPut = new RelightSkyEntry(cx, cz, fix, bitmask); queuedSkyToRelight.add(toPut); return true; } private synchronized Map getSkyMap() { RelightSkyEntry entry; while ((entry = queuedSkyToRelight.poll()) != null) { long pair = MathMan.pairInt(entry.x, entry.z); RelightSkyEntry existing = skyToRelight.put(pair, entry); if (existing != null) { entry.bitmask |= existing.bitmask; if (entry.fix != null) { for (int i = 0; i < entry.fix.length; i++) { entry.fix[i] &= existing.fix[i]; } } } } return skyToRelight; } public synchronized void removeLighting() { Iterator> iter = getSkyMap().entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); RelightSkyEntry chunk = entry.getValue(); long pair = entry.getKey(); Integer existing = chunksToSend.get(pair); chunksToSend.put(pair, chunk.bitmask | (existing != null ? existing : 0)); queue.ensureChunkLoaded(chunk.x, chunk.z); Object sections = queue.getCachedSections(queue.getWorld(), chunk.x, chunk.z); queue.removeLighting(sections, FaweQueue.RelightMode.ALL, queue.hasSky()); iter.remove(); } } public void updateBlockLight(Map map) { int size = map.size(); if (size == 0) { return; } Queue lightPropagationQueue = new ArrayDeque<>(); Queue lightRemovalQueue = new ArrayDeque<>(); Map visited = new HashMap<>(); Map removalVisited = new HashMap<>(); Iterator> iter = map.entrySet().iterator(); while (iter.hasNext() && size-- > 0) { Map.Entry entry = iter.next(); long index = entry.getKey(); long[][][] blocks = entry.getValue(); int chunkX = MathMan.unpairIntX(index); int chunkZ = MathMan.unpairIntY(index); int bx = chunkX << 4; int bz = chunkZ << 4; for (int lz = 0; lz < blocks.length; lz++) { long[][] m1 = blocks[lz]; if (m1 == null) continue; for (int lx = 0; lx < m1.length; lx++) { long[] m2 = m1[lx]; if (m2 == null) continue; for (int i = 0; i < m2.length; i++) { int yStart = i << 6; long value = m2[i]; if (value != 0) { for (int j = 0; j < 64; j++) { if (((value >> j) & 1) == 1) { int x = lx + bx; int y = yStart + j; int z = lz + bz; int oldLevel = queue.getEmmittedLight(x, y, z); int newLevel = queue.getBrightness(x, y, z); if (oldLevel != newLevel) { queue.setBlockLight(x, y, z, newLevel); IntegerTrio node = new IntegerTrio(x, y, z); if (newLevel < oldLevel) { removalVisited.put(node, present); lightRemovalQueue.add(new Object[]{node, oldLevel}); } else { visited.put(node, present); lightPropagationQueue.add(node); } } } } } } } } iter.remove(); } while (!lightRemovalQueue.isEmpty()) { Object[] val = lightRemovalQueue.poll(); IntegerTrio node = (IntegerTrio) val[0]; int lightLevel = (int) val[1]; this.computeRemoveBlockLight(node.x - 1, node.y, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(node.x + 1, node.y, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); if (node.y > 0) { this.computeRemoveBlockLight(node.x, node.y - 1, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); } if (node.y < 255) { this.computeRemoveBlockLight(node.x, node.y + 1, node.z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); } this.computeRemoveBlockLight(node.x, node.y, node.z - 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); this.computeRemoveBlockLight(node.x, node.y, node.z + 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); } while (!lightPropagationQueue.isEmpty()) { IntegerTrio node = lightPropagationQueue.poll(); int lightLevel = queue.getEmmittedLight(node.x, node.y, node.z); if (lightLevel > 1) { this.computeSpreadBlockLight(node.x - 1, node.y, node.z, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(node.x + 1, node.y, node.z, lightLevel, lightPropagationQueue, visited); if (node.y > 0) { this.computeSpreadBlockLight(node.x, node.y - 1, node.z, lightLevel, lightPropagationQueue, visited); } if (node.y < 255) { this.computeSpreadBlockLight(node.x, node.y + 1, node.z, lightLevel, lightPropagationQueue, visited); } this.computeSpreadBlockLight(node.x, node.y, node.z - 1, lightLevel, lightPropagationQueue, visited); this.computeSpreadBlockLight(node.x, node.y, node.z + 1, lightLevel, lightPropagationQueue, visited); } } } private void computeRemoveBlockLight(int x, int y, int z, int currentLight, Queue queue, Queue spreadQueue, Map visited, Map spreadVisited) { int current = this.queue.getEmmittedLight(x, y, z); if (current != 0 && current < currentLight) { this.queue.setBlockLight(x, y, z, 0); if (current > 1) { if (!visited.containsKey(mutableBlockPos)) { IntegerTrio index = new IntegerTrio(x, y, z); visited.put(index, present); queue.add(new Object[]{index, current}); } } } else if (current >= currentLight) { mutableBlockPos.set(x, y, z); if (!spreadVisited.containsKey(mutableBlockPos)) { IntegerTrio index = new IntegerTrio(x, y, z); spreadVisited.put(index, present); spreadQueue.add(index); } } } private void computeSpreadBlockLight(int x, int y, int z, int currentLight, Queue queue, Map visited) { currentLight = currentLight - Math.max(1, this.queue.getOpacity(x, y, z)); if (currentLight > 0) { int current = this.queue.getEmmittedLight(x, y, z); if (current < currentLight) { this.queue.setBlockLight(x, y, z, currentLight); mutableBlockPos.set(x, y, z); if (!visited.containsKey(mutableBlockPos)) { visited.put(new IntegerTrio(x, y, z), present); if (currentLight > 1) { queue.add(new IntegerTrio(x, y, z)); } } } } } public void fixLightingSafe(boolean sky) { if (isEmpty()) return; try { if (sky) { fixSkyLighting(); } else { synchronized (this) { Map map = getSkyMap(); Iterator> iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); chunksToSend.put(entry.getKey(), entry.getValue().bitmask); iter.remove(); } } } fixBlockLighting(); sendChunks(); } catch (Throwable e) { e.printStackTrace(); } } public void fixBlockLighting() { synchronized (lightQueue) { while (!lightLock.compareAndSet(false, true)); try { updateBlockLight(this.lightQueue); } finally { lightLock.set(false); } } } public synchronized void sendChunks() { RunnableVal runnable = new RunnableVal() { @Override public void run(Object value) { Iterator> iter = chunksToSend.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); long pair = entry.getKey(); int bitMask = entry.getValue(); int x = MathMan.unpairIntX(pair); int z = MathMan.unpairIntY(pair); queue.sendChunk(x, z, bitMask); iter.remove(); } } }; if (Settings.IMP.LIGHTING.ASYNC) { runnable.run(); } else { TaskManager.IMP.sync(runnable); } } private boolean isTransparent(int x, int y, int z) { return queue.getOpacity(x, y, z) < 15; } public synchronized void fixSkyLighting() { // Order chunks Map map = getSkyMap(); ArrayList chunksList = new ArrayList<>(map.size()); Iterator> iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); chunksToSend.put(entry.getKey(), entry.getValue().bitmask); chunksList.add(entry.getValue()); iter.remove(); } Collections.sort(chunksList); int size = chunksList.size(); if (size > DISPATCH_SIZE) { int amount = (size + DISPATCH_SIZE - 1) / DISPATCH_SIZE; for (int i = 0; i < amount; i++) { int start = i * DISPATCH_SIZE; int end = Math.min(size, start + DISPATCH_SIZE); List sub = chunksList.subList(start, end); fixSkyLighting(sub); } } else { fixSkyLighting(chunksList); } } public void fill(byte[] mask, int chunkX, int y, int chunkZ, byte reason) { if (y >= FaweChunk.HEIGHT) { Arrays.fill(mask, (byte) 15); return; } switch (reason) { case SkipReason.SOLID: { Arrays.fill(mask, (byte) 0); return; } case SkipReason.AIR: { int bx = chunkX << 4; int bz = chunkZ << 4; int index = 0; for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++) { mask[index++] = (byte) queue.getSkyLight(bx + x, y, bz + z); } } } } } private void fixSkyLighting(List sorted) { RelightSkyEntry[] chunks = sorted.toArray(new RelightSkyEntry[sorted.size()]); byte[] cacheX = FaweCache.CACHE_X[0]; byte[] cacheZ = FaweCache.CACHE_Z[0]; for (int y = FaweChunk.HEIGHT - 1; y > 0; y--) { for (RelightSkyEntry chunk : chunks) { // Propogate skylight int layer = y >> 4; byte[] mask = chunk.mask; if (chunk.fix[layer] != SkipReason.NONE) { if ((y & 15) == 0 && layer != 0 && chunk.fix[layer - 1] == SkipReason.NONE) { fill(mask, chunk.x, y, chunk.z, chunk.fix[layer]); } continue; } int bx = chunk.x << 4; int bz = chunk.z << 4; Object chunkObj = queue.ensureChunkLoaded(chunk.x, chunk.z); Object sections = queue.getCachedSections(queue.getWorld(), chunk.x, chunk.z); if (sections == null) continue; Object section = queue.getCachedSection(sections, layer); if (section == null) continue; chunk.smooth = false; if (removeFirst && (y & 15) == 15) { queue.removeSectionLighting(sections, y >> 4, true); } for (int j = 0; j <= maxY; j++) { int x = cacheX[j]; int z = cacheZ[j]; byte value = mask[j]; byte pair = (byte) queue.getOpacityBrightnessPair(section, x, y, z); int opacity = MathMan.unpair16x(pair); int brightness = MathMan.unpair16y(pair); if (brightness > 1 && (brightness != 15 || opacity != 15)) { addLightUpdate(bx + x, y, bz + z); } switch (value) { case 0: if (opacity > 1) { queue.setSkyLight(section, x, y, z, 0); continue; } break; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: if (opacity >= value) { mask[j] = 0; queue.setSkyLight(section, x, y, z, 0); continue; } if (opacity <= 1) { mask[j] = --value; } else { mask[j] = value = (byte) Math.max(0, value - opacity); } break; case 15: if (opacity > 1) { value -= opacity; mask[j] = value; } queue.setSkyLight(section, x, y, z, value); continue; } chunk.smooth = true; queue.setSkyLight(section, x, y, z, value); } queue.saveChunk(chunkObj); } for (RelightSkyEntry chunk : chunks) { // Smooth forwards if (chunk.smooth) { smoothSkyLight(chunk, y, true); } } for (int i = chunks.length - 1; i >= 0; i--) { // Smooth backwards RelightSkyEntry chunk = chunks[i]; if (chunk.smooth) { smoothSkyLight(chunk, y, false); } } } } public void smoothSkyLight(RelightSkyEntry chunk, int y, boolean direction) { byte[] mask = chunk.mask; int bx = chunk.x << 4; int bz = chunk.z << 4; queue.ensureChunkLoaded(chunk.x, chunk.z); Object sections = queue.getCachedSections(queue.getWorld(), chunk.x, chunk.z); if (sections == null) return; Object section = queue.getCachedSection(sections, y >> 4); if (section == null) return; if (direction) { for (int j = 0; j < 256; j++) { int x = j & 15; int z = j >> 4; if (mask[j] >= 14 || (mask[j] == 0 && queue.getOpacity(section, x, y, z) > 1)) { continue; } byte value = mask[j]; if ((value = (byte) Math.max(queue.getSkyLight(bx + x - 1, y, bz + z) - 1, value)) >= 14) ; else if ((value = (byte) Math.max(queue.getSkyLight(bx + x, y, bz + z - 1) - 1, value)) >= 14) ; if (value > mask[j]) queue.setSkyLight(section, x, y, z, mask[j] = value); } } else { for (int j = 255; j >= 0; j--) { int x = j & 15; int z = j >> 4; if (mask[j] >= 14 || (mask[j] == 0 && queue.getOpacity(section, x, y, z) > 1)) { continue; } byte value = mask[j]; if ((value = (byte) Math.max(queue.getSkyLight(bx + x + 1, y, bz + z) - 1, value)) >= 14) ; else if ((value = (byte) Math.max(queue.getSkyLight(bx + x, y, bz + z + 1) - 1, value)) >= 14) ; if (value > mask[j]) queue.setSkyLight(section, x, y, z, mask[j] = value); } } } public boolean isUnlit(byte[] array) { for (byte val : array) { if (val != 0) { return false; } } return true; } private class RelightSkyEntry implements Comparable { public final int x; public final int z; public final byte[] mask; public final byte[] fix; public int bitmask; public boolean smooth; public RelightSkyEntry(int x, int z, byte[] fix, int bitmask) { this.x = x; this.z = z; byte[] array = new byte[256]; Arrays.fill(array, (byte) 15); this.mask = array; this.bitmask = bitmask; if (fix == null) { this.fix = new byte[(maxY + 1) >> 4]; Arrays.fill(this.fix, SkipReason.NONE); } else { this.fix = fix; } } @Override public String toString() { return x + "," + z; } @Override public int compareTo(Object o) { RelightSkyEntry other = (RelightSkyEntry) o; if (other.x < x) { return 1; } if (other.x > x) { return -1; } if (other.z < z) { return 1; } if (other.z > z) { return -1; } return 0; } } }