FastAsyncWorldedit/core/src/main/java/com/boydti/fawe/example/NMSRelighter.java

569 lines
23 KiB
Java

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<Long, RelightSkyEntry> skyToRelight;
private final Object present = new Object();
private final Map<Long, Integer> chunksToSend;
private final ConcurrentLinkedQueue<RelightSkyEntry> queuedSkyToRelight = new ConcurrentLinkedQueue<>();
private final Map<Long, long[][][] /* z y x */ > lightQueue;
private final AtomicBoolean lightLock = new AtomicBoolean(false);
private final ConcurrentHashMap<Long, long[][][]> 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<Long, RelightSkyEntry> 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<Map.Entry<Long, RelightSkyEntry>> iter = getSkyMap().entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> 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<Long, long[][][]> map) {
int size = map.size();
if (size == 0) {
return;
}
Queue<IntegerTrio> lightPropagationQueue = new ArrayDeque<>();
Queue<Object[]> lightRemovalQueue = new ArrayDeque<>();
Map<IntegerTrio, Object> visited = new HashMap<>();
Map<IntegerTrio, Object> removalVisited = new HashMap<>();
Iterator<Map.Entry<Long, long[][][]>> iter = map.entrySet().iterator();
while (iter.hasNext() && size-- > 0) {
Map.Entry<Long, long[][][]> 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<Object[]> queue, Queue<IntegerTrio> spreadQueue, Map<IntegerTrio, Object> visited,
Map<IntegerTrio, Object> 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<IntegerTrio> queue, Map<IntegerTrio, Object> 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<Long, RelightSkyEntry> map = getSkyMap();
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> 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<Object> runnable = new RunnableVal<Object>() {
@Override
public void run(Object value) {
Iterator<Map.Entry<Long, Integer>> iter = chunksToSend.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, Integer> 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<Long, RelightSkyEntry> map = getSkyMap();
ArrayList<RelightSkyEntry> chunksList = new ArrayList<>(map.size());
Iterator<Map.Entry<Long, RelightSkyEntry>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Long, RelightSkyEntry> 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<RelightSkyEntry> 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<RelightSkyEntry> 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;
}
}
}