408 lines
15 KiB
Java
408 lines
15 KiB
Java
package com.boydti.fawe.bukkit.v0;
|
|
|
|
import com.boydti.fawe.FaweCache;
|
|
import com.boydti.fawe.bukkit.util.BukkitReflectionUtils;
|
|
import com.boydti.fawe.config.Settings;
|
|
import com.boydti.fawe.example.NullRelighter;
|
|
import com.boydti.fawe.example.Relighter;
|
|
import com.boydti.fawe.object.FaweChunk;
|
|
import com.boydti.fawe.object.RegionWrapper;
|
|
import com.boydti.fawe.object.RunnableVal;
|
|
import com.boydti.fawe.util.MathMan;
|
|
import com.boydti.fawe.util.ReflectionUtils;
|
|
import com.boydti.fawe.util.SetQueue;
|
|
import com.boydti.fawe.util.TaskManager;
|
|
import com.google.common.collect.MapMaker;
|
|
import com.sk89q.jnbt.CompoundTag;
|
|
import com.sk89q.worldedit.blocks.BaseBlock;
|
|
import java.io.File;
|
|
import java.io.RandomAccessFile;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.util.ArrayDeque;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import org.bukkit.Chunk;
|
|
import org.bukkit.ChunkSnapshot;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.World;
|
|
import org.bukkit.block.Biome;
|
|
|
|
public class BukkitQueue_All extends BukkitQueue_0<ChunkSnapshot, ChunkSnapshot, ChunkSnapshot> {
|
|
|
|
public static int ALLOCATE;
|
|
private ConcurrentMap<Long, ChunkSnapshot> chunkCache = new MapMaker()
|
|
.weakValues()
|
|
.makeMap();
|
|
|
|
public BukkitQueue_All(com.sk89q.worldedit.world.World world) {
|
|
super(world);
|
|
if (Settings.IMP.QUEUE.EXTRA_TIME_MS != Integer.MIN_VALUE) {
|
|
ALLOCATE = Settings.IMP.QUEUE.EXTRA_TIME_MS;
|
|
Settings.IMP.QUEUE.EXTRA_TIME_MS = Integer.MIN_VALUE;
|
|
Settings.IMP.QUEUE.PARALLEL_THREADS = 1;
|
|
}
|
|
}
|
|
|
|
public BukkitQueue_All(String world) {
|
|
super(world);
|
|
if (Settings.IMP.QUEUE.EXTRA_TIME_MS != Integer.MIN_VALUE) {
|
|
ALLOCATE = Settings.IMP.QUEUE.EXTRA_TIME_MS;
|
|
Settings.IMP.QUEUE.EXTRA_TIME_MS = Integer.MIN_VALUE;
|
|
Settings.IMP.QUEUE.PARALLEL_THREADS = 1;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean queueChunkLoad(int cx, int cz, RunnableVal<ChunkSnapshot> operation) {
|
|
if (PAPER) {
|
|
try {
|
|
new PaperChunkCallback(getImpWorld(), cx, cz) {
|
|
@Override
|
|
public void onLoad(Chunk chunk) {
|
|
try {
|
|
ChunkSnapshot snapshot = chunk.getChunkSnapshot();
|
|
operation.run(snapshot);
|
|
} catch (Throwable e) {
|
|
PAPER = false;
|
|
}
|
|
}
|
|
};
|
|
return true;
|
|
} catch (Throwable ignore) {
|
|
PAPER = false;
|
|
}
|
|
}
|
|
return super.queueChunkLoad(cx, cz);
|
|
}
|
|
|
|
@Override
|
|
public Relighter getRelighter() {
|
|
return NullRelighter.INSTANCE;
|
|
}
|
|
|
|
private static Class<?> classRegionFileCache;
|
|
private static Class<?> classRegionFile;
|
|
private static Class<?> classCraftChunk;
|
|
private static Class<?> classCraftWorld;
|
|
private static Class<?> classNMSChunk;
|
|
private static Class<?> classNMSWorld;
|
|
private static Class<?> classChunkProviderServer;
|
|
private static Class<?> classIChunkLoader;
|
|
private static Class<?> classChunkRegionLoader;
|
|
private static Class<?> classIChunkProvider;
|
|
private static Method methodGetHandleChunk;
|
|
private static Method methodGetHandleWorld;
|
|
private static Method methodFlush;
|
|
private static Method methodNeedsSaving;
|
|
private static Field fieldChunkProvider;
|
|
private static Field fieldChunkLoader;
|
|
private static Field fieldRegionMap;
|
|
private static Field fieldRegionRAF;
|
|
|
|
static {
|
|
try {
|
|
BukkitReflectionUtils.init();
|
|
classRegionFileCache = BukkitReflectionUtils.getNmsClass("RegionFileCache");
|
|
classRegionFile = BukkitReflectionUtils.getNmsClass("RegionFile");
|
|
classCraftChunk = BukkitReflectionUtils.getCbClass("CraftChunk");
|
|
classNMSChunk = BukkitReflectionUtils.getNmsClass("Chunk");
|
|
classCraftWorld = BukkitReflectionUtils.getCbClass("CraftWorld");
|
|
classNMSWorld = BukkitReflectionUtils.getNmsClass("World");
|
|
classChunkProviderServer = BukkitReflectionUtils.getNmsClass("ChunkProviderServer");
|
|
classIChunkProvider = BukkitReflectionUtils.getNmsClass("IChunkProvider");
|
|
classIChunkLoader = BukkitReflectionUtils.getNmsClass("IChunkLoader");
|
|
classChunkRegionLoader = BukkitReflectionUtils.getNmsClass("ChunkRegionLoader");
|
|
|
|
methodGetHandleChunk = ReflectionUtils.setAccessible(classCraftChunk.getDeclaredMethod("getHandle"));
|
|
methodGetHandleWorld = ReflectionUtils.setAccessible(classCraftWorld.getDeclaredMethod("getHandle"));
|
|
methodFlush = ReflectionUtils.findMethod(classChunkRegionLoader, boolean.class);
|
|
methodNeedsSaving = ReflectionUtils.findMethod(classNMSChunk, boolean.class, boolean.class);
|
|
|
|
fieldChunkProvider = ReflectionUtils.findField(classNMSWorld, classIChunkProvider);
|
|
fieldChunkLoader = ReflectionUtils.findField(classChunkProviderServer, classIChunkLoader);
|
|
|
|
fieldRegionMap = ReflectionUtils.findField(classRegionFileCache, Map.class);
|
|
fieldRegionRAF = ReflectionUtils.findField(classRegionFile, RandomAccessFile.class);
|
|
} catch (Throwable ignore) {
|
|
ignore.printStackTrace();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean setMCA(int mcaX, int mcaZ, RegionWrapper allowed, Runnable whileLocked, boolean saveChunks, boolean load) {
|
|
if (classRegionFileCache == null) {
|
|
return super.setMCA(mcaX, mcaZ, allowed, whileLocked, saveChunks, load);
|
|
}
|
|
TaskManager.IMP.sync(new RunnableVal<Boolean>() {
|
|
@Override
|
|
public void run(Boolean value) {
|
|
long start = System.currentTimeMillis();
|
|
long last = start;
|
|
synchronized (classRegionFileCache) {
|
|
try {
|
|
World world = getWorld();
|
|
boolean autoSave = world.isAutoSave();
|
|
|
|
if (world.getKeepSpawnInMemory()) world.setKeepSpawnInMemory(false);
|
|
|
|
ArrayDeque<Chunk> unloaded = null;
|
|
if (load) {
|
|
int bcx = mcaX << 5;
|
|
int bcz = mcaZ << 5;
|
|
int tcx = bcx + 31;
|
|
int tcz = bcz + 31;
|
|
for (Chunk chunk : world.getLoadedChunks()) {
|
|
int cx = chunk.getX();
|
|
int cz = chunk.getZ();
|
|
if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) {
|
|
Object nmsChunk = methodGetHandleChunk.invoke(chunk);
|
|
boolean mustSave = saveChunks && (boolean) methodNeedsSaving.invoke(nmsChunk, false);
|
|
chunk.unload(mustSave, false);
|
|
if (unloaded == null) unloaded = new ArrayDeque<Chunk>();
|
|
unloaded.add(chunk);
|
|
}
|
|
}
|
|
} else {
|
|
world.save();
|
|
}
|
|
|
|
Object nmsWorld = methodGetHandleWorld.invoke(world);
|
|
Object chunkProviderServer = fieldChunkProvider.get(nmsWorld);
|
|
Object chunkRegionLoader = fieldChunkLoader.get(chunkProviderServer);
|
|
while ((boolean) methodFlush.invoke(chunkRegionLoader));
|
|
|
|
if (unloaded != null) {
|
|
Map regionMap = (Map) fieldRegionMap.get(null);
|
|
File file = new File(world.getWorldFolder(), "region" + File.separator + "r." + mcaX + "." + mcaZ + ".mca");
|
|
Object regionFile = regionMap.remove(file);
|
|
if (regionFile != null) {
|
|
RandomAccessFile raf = (RandomAccessFile) fieldRegionRAF.get(regionFile);
|
|
raf.close();
|
|
}
|
|
}
|
|
|
|
whileLocked.run();
|
|
|
|
if (load && unloaded != null) {
|
|
final ArrayDeque<Chunk> finalUnloaded = unloaded;
|
|
TaskManager.IMP.async(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
for (Chunk chunk : finalUnloaded) {
|
|
int cx = chunk.getX();
|
|
int cz = chunk.getZ();
|
|
if (world.isChunkLoaded(cx, cz)) continue;
|
|
SetQueue.IMP.addTask(() -> {
|
|
world.loadChunk(chunk.getX(), chunk.getZ(), false);
|
|
world.refreshChunk(chunk.getX(), chunk.getZ());
|
|
});
|
|
|
|
}
|
|
}
|
|
});
|
|
// load chunks
|
|
|
|
}
|
|
} catch (Throwable e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void setHeightMap(FaweChunk chunk, byte[] heightMap) {
|
|
// Not supported
|
|
}
|
|
|
|
@Override
|
|
public void setSkyLight(ChunkSnapshot chunk, int x, int y, int z, int value) {
|
|
// Not supported
|
|
}
|
|
|
|
@Override
|
|
public void setBlockLight(ChunkSnapshot chunk, int x, int y, int z, int value) {
|
|
// Not supported
|
|
}
|
|
|
|
@Override
|
|
public int getCombinedId4Data(ChunkSnapshot chunk, int x, int y, int z) {
|
|
if (chunk.isSectionEmpty(y >> 4)) {
|
|
return 0;
|
|
}
|
|
int id = chunk.getBlockTypeId(x & 15, y, z & 15);
|
|
if (FaweCache.hasData(id)) {
|
|
int data = chunk.getBlockData(x & 15, y, z & 15);
|
|
return (id << 4) + data;
|
|
} else {
|
|
return id << 4;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getBiome(ChunkSnapshot chunkSnapshot, int x, int z) {
|
|
Biome biome = chunkSnapshot.getBiome(x & 15, z & 15);
|
|
return getAdapter().getBiomeId(biome);
|
|
}
|
|
|
|
@Override
|
|
public ChunkSnapshot getSections(ChunkSnapshot chunkSnapshot) {
|
|
return chunkSnapshot;
|
|
}
|
|
|
|
@Override
|
|
public ChunkSnapshot getCachedChunk(World world, int cx, int cz) {
|
|
long pair = MathMan.pairInt(cx, cz);
|
|
ChunkSnapshot cached = chunkCache.get(pair);
|
|
if (cached != null) return cached;
|
|
if (world.isChunkLoaded(cx, cz)) {
|
|
Long originalKeep = keepLoaded.get(pair);
|
|
keepLoaded.put(pair, Long.MAX_VALUE);
|
|
if (world.isChunkLoaded(cx, cz)) {
|
|
Chunk chunk = world.getChunkAt(cx, cz);
|
|
ChunkSnapshot snapshot = getAndCacheChunk(chunk);
|
|
if (originalKeep != null) {
|
|
keepLoaded.put(pair, originalKeep);
|
|
} else {
|
|
keepLoaded.remove(pair);
|
|
}
|
|
return snapshot;
|
|
} else {
|
|
keepLoaded.remove(pair);
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getEmmittedLight(final ChunkSnapshot chunk, int x, int y, int z) {
|
|
return chunk.getBlockEmittedLight(x & 15, y, z & 15);
|
|
}
|
|
|
|
@Override
|
|
public int getSkyLight(final ChunkSnapshot chunk, int x, int y, int z) {
|
|
return chunk.getBlockSkyLight(x & 15, y, z & 15);
|
|
}
|
|
|
|
@Override
|
|
public int getLight(final ChunkSnapshot chunk, int x, int y, int z) {
|
|
x = x & 15;
|
|
z = z & 15;
|
|
return Math.max(chunk.getBlockEmittedLight(x, y, z), chunk.getBlockSkyLight(x, y, z));
|
|
}
|
|
|
|
@Override
|
|
public ChunkSnapshot loadChunk(World world, int x, int z, boolean generate) {
|
|
Chunk chunk = world.getChunkAt(x, z);
|
|
chunk.load(generate);
|
|
return chunk.isLoaded() ? getAndCacheChunk(chunk) : null;
|
|
}
|
|
|
|
private ChunkSnapshot getAndCacheChunk(Chunk chunk) {
|
|
ChunkSnapshot snapshot = chunk.getChunkSnapshot(false, true, false);
|
|
chunkCache.put(MathMan.pairInt(chunk.getX(), chunk.getZ()), snapshot);
|
|
return snapshot;
|
|
}
|
|
|
|
@Override
|
|
public ChunkSnapshot getCachedSections(World impWorld, int cx, int cz) {
|
|
return getCachedChunk(impWorld, cx, cz);
|
|
}
|
|
|
|
@Override
|
|
public CompoundTag getTileEntity(ChunkSnapshot chunk, int x, int y, int z) {
|
|
if (getAdapter() == null) {
|
|
return null;
|
|
}
|
|
Location loc = new Location(getWorld(), x, y, z);
|
|
BaseBlock block = getAdapter().getBlock(loc);
|
|
return block != null ? block.getNbtData() : null;
|
|
}
|
|
|
|
@Override
|
|
public FaweChunk getFaweChunk(int x, int z) {
|
|
return new BukkitChunk_All(this, x, z);
|
|
}
|
|
|
|
@Override
|
|
public boolean supports(Capability capability) {
|
|
switch (capability) {
|
|
case CHANGE_TASKS: return getAdapter() != null;
|
|
}
|
|
return super.supports(capability);
|
|
}
|
|
|
|
private int skip;
|
|
|
|
@Override
|
|
public void startSet(boolean parallel) {
|
|
super.startSet(true);
|
|
}
|
|
|
|
private Field fieldNeighbors;
|
|
private Method chunkGetHandle;
|
|
|
|
/**
|
|
* Exploiting a bug in the vanilla lighting algorithm for faster block placement
|
|
* - Could have been achieved without reflection by force unloading specific chunks
|
|
* - Much faster just setting the variable manually though
|
|
* @param chunk
|
|
* @return
|
|
*/
|
|
protected Object[] disableLighting(Chunk chunk) {
|
|
try {
|
|
if (chunkGetHandle == null) {
|
|
chunkGetHandle = chunk.getClass().getDeclaredMethod("getHandle");
|
|
chunkGetHandle.setAccessible(true);
|
|
}
|
|
Object nmsChunk = chunkGetHandle.invoke(chunk);
|
|
if (fieldNeighbors == null) {
|
|
fieldNeighbors = nmsChunk.getClass().getDeclaredField("neighbors");
|
|
fieldNeighbors.setAccessible(true);
|
|
}
|
|
Object value = fieldNeighbors.get(nmsChunk);
|
|
fieldNeighbors.set(nmsChunk, 0);
|
|
return new Object[] {nmsChunk, value};
|
|
} catch (Throwable ignore) {}
|
|
return null;
|
|
}
|
|
|
|
protected void disableLighting(Object[] disableResult) {
|
|
if (disableResult != null) {
|
|
try {
|
|
fieldNeighbors.set(disableResult[0], 0);
|
|
} catch (IllegalAccessException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void resetLighting(Object[] disableResult) {
|
|
if (disableResult != null) {
|
|
try {
|
|
fieldNeighbors.set(disableResult[0], disableResult[1]);
|
|
} catch (Throwable ignore) {
|
|
ignore.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void enableLighting(Object[] disableResult) {
|
|
if (disableResult != null) {
|
|
try {
|
|
fieldNeighbors.set(disableResult[0], 0x739C0);
|
|
} catch (Throwable ignore) {}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void endSet(boolean parallel) {
|
|
super.endSet(true);
|
|
}
|
|
}
|