FastAsyncWorldedit/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java

459 lines
16 KiB
Java

package com.boydti.fawe.bukkit.v0;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.bukkit.BukkitPlayer;
import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.bukkit.util.BukkitReflectionUtils;
import com.boydti.fawe.bukkit.v1_12.packet.FaweChunkPacket;
import com.boydti.fawe.bukkit.v1_12.packet.MCAChunkPacket;
import com.boydti.fawe.example.CharFaweChunk;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.queue.LazyFaweChunk;
import com.boydti.fawe.object.visitor.FaweChunkVisitor;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.ReflectionUtils;
import com.boydti.fawe.util.TaskManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.plugin.Plugin;
public abstract class BukkitQueue_0<CHUNK, CHUNKSECTIONS, SECTION> extends NMSMappedFaweQueue<World, CHUNK, CHUNKSECTIONS, SECTION> implements Listener {
protected static boolean PAPER = true;
private static BukkitImplAdapter adapter;
private static FaweAdapter_All backupAdaper;
private static Method methodToNative;
private static Method methodFromNative;
private static boolean setupAdapter = false;
private static Method methodGetHandle;
static {
Class<?> classCraftChunk = BukkitReflectionUtils.getCbClass("CraftChunk");
try {
methodGetHandle = ReflectionUtils.setAccessible(classCraftChunk.getDeclaredMethod("getHandle"));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public BukkitQueue_0(final com.sk89q.worldedit.world.World world) {
super(world);
setupAdapter(null);
if (!registered) {
registered = true;
Bukkit.getServer().getPluginManager().registerEvents(this, ((FaweBukkit) Fawe.imp()).getPlugin());
}
}
public BukkitQueue_0(String world) {
super(world);
setupAdapter(null);
if (!registered) {
registered = true;
Bukkit.getServer().getPluginManager().registerEvents(this, ((FaweBukkit) Fawe.imp()).getPlugin());
}
}
@Override
public boolean supports(Capability capability) {
switch (capability) {
case CHUNK_PACKETS:
Plugin plib = Bukkit.getPluginManager().getPlugin("ProtocolLib");
return plib != null && plib.isEnabled();
}
return super.supports(capability);
}
@Override
public void sendChunkUpdate(FaweChunk chunk, FawePlayer... players) {
if (supports(Capability.CHUNK_PACKETS)) {
sendChunkUpdatePLIB(chunk, players);
} else {
sendBlockUpdate(chunk, players);
}
}
public void sendChunkUpdatePLIB(FaweChunk chunk, FawePlayer... players) {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
WirePacket packet = null;
int viewDistance = Bukkit.getViewDistance();
try {
for (int i = 0; i < players.length; i++) {
int cx = chunk.getX();
int cz = chunk.getZ();
Player player = ((BukkitPlayer) players[i]).parent;
Location loc = player.getLocation();
if (Math.abs((loc.getBlockX() >> 4) - cx) <= viewDistance && Math.abs((loc.getBlockZ() >> 4) - cz) <= viewDistance) {
if (packet == null) {
byte[] data;
byte[] buffer = new byte[8192];
if (chunk instanceof LazyFaweChunk) {
chunk = (FaweChunk) chunk.getChunk();
}
if (chunk instanceof MCAChunk) {
data = new MCAChunkPacket((MCAChunk) chunk, true, true, hasSky()).apply(buffer);
} else {
data = new FaweChunkPacket(chunk, true, true, hasSky()).apply(buffer);
}
packet = new WirePacket(PacketType.Play.Server.MAP_CHUNK, data);
}
manager.sendWirePacket(player, packet);
}
}
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean queueChunkLoad(int cx, int cz, RunnableVal<CHUNK> operation) {
if (PAPER) {
try {
new PaperChunkCallback(getImpWorld(), cx, cz) {
@Override
public void onLoad(Chunk bukkitChunk) {
try {
CHUNK chunk = (CHUNK) methodGetHandle.invoke(bukkitChunk);
try {
operation.run(chunk);
} catch (Throwable e) {
e.printStackTrace();
}
} catch (Throwable e) {
PAPER = false;
}
}
};
return true;
} catch (Throwable ignore) {
PAPER = false;
}
}
return super.queueChunkLoad(cx, cz);
}
public static BukkitImplAdapter getAdapter() {
if (adapter == null) setupAdapter(null);
if (adapter == null) return backupAdaper;
return adapter;
}
public static Tag toNative(Object tag) {
BukkitImplAdapter adapter = getAdapter();
if (adapter == null) {
if (backupAdaper != null) return backupAdaper.toNative(tag);
return null;
}
try {
return (Tag) methodToNative.invoke(adapter, tag);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static Object fromNative(Tag tag) {
BukkitImplAdapter adapter = getAdapter();
if (adapter == null) {
if (backupAdaper != null) return backupAdaper.fromNative(tag);
return null;
}
try {
return methodFromNative.invoke(adapter, tag);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@Override
public File getSaveFolder() {
return new File(Bukkit.getWorldContainer(), getWorldName() + File.separator + "region");
}
@Override
public void setFullbright(CHUNKSECTIONS sections) {}
@Override
public void relight(int x, int y, int z) {}
@Override
public void relightBlock(int x, int y, int z) {}
@Override
public void relightSky(int x, int y, int z) {}
@Override
public boolean removeLighting(CHUNKSECTIONS sections, RelightMode mode, boolean hasSky) {
return false;
}
@Override
public boolean removeSectionLighting(SECTION sections, int layer, boolean hasSky) {
return false;
}
public static void checkVersion(String supported) {
String version = Bukkit.getServer().getClass().getPackage().getName();
if (!version.contains(supported)) {
throw new IllegalStateException("Unsupported version: " + version + " (supports: " + supported + ")");
}
}
protected static boolean registered = false;
protected static boolean disableChunkLoad = false;
@EventHandler
public static void onWorldLoad(WorldInitEvent event) {
if (disableChunkLoad) {
World world = event.getWorld();
world.setKeepSpawnInMemory(false);
}
}
public static ConcurrentHashMap<Long, Long> keepLoaded = new ConcurrentHashMap<>(8, 0.9f, 1);
@EventHandler
public static void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
long pair = MathMan.pairInt(chunk.getX(), chunk.getZ());
keepLoaded.putIfAbsent(pair, Fawe.get().getTimer().getTickStart());
}
@EventHandler
public static void onChunkUnload(ChunkUnloadEvent event) {
Chunk chunk = event.getChunk();
long pair = MathMan.pairInt(chunk.getX(), chunk.getZ());
Long lastLoad = keepLoaded.get(pair);
if (lastLoad != null) {
if (Fawe.get().getTimer().getTickStart() - lastLoad < 10000) {
event.setCancelled(true);
} else {
keepLoaded.remove(pair);
}
}
}
@Override
public boolean queueChunkLoad(int cx, int cz) {
if (super.queueChunkLoad(cx, cz)) {
keepLoaded.put(MathMan.pairInt(cx, cz), System.currentTimeMillis());
return true;
}
return false;
}
public World createWorld(final WorldCreator creator) {
World world = TaskManager.IMP.sync(new RunnableVal<World>() {
@Override
public void run(World value) {
disableChunkLoad = true;
this.value = creator.createWorld();
disableChunkLoad = false;
}
});
return world;
}
public static void setupAdapter(BukkitImplAdapter adapter) {
try {
if (adapter == null && setupAdapter == (setupAdapter = true)) {
return;
}
WorldEditPlugin instance = (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit");
Field fieldAdapter = WorldEditPlugin.class.getDeclaredField("bukkitAdapter");
fieldAdapter.setAccessible(true);
if (adapter != null) {
BukkitQueue_0.adapter = adapter;
fieldAdapter.set(instance, adapter);
} else {
BukkitQueue_0.adapter = adapter = (BukkitImplAdapter) fieldAdapter.get(instance);
if (adapter == null) {
BukkitQueue_0.adapter = adapter = new FaweAdapter_All();
fieldAdapter.set(instance, adapter);
}
}
if (adapter != null) {
for (Method method : adapter.getClass().getDeclaredMethods()) {
switch (method.getName()) {
case "toNative":
methodToNative = method;
methodToNative.setAccessible(true);
break;
case "fromNative":
methodFromNative = method;
methodFromNative.setAccessible(true);
break;
}
}
}
return;
} catch (Throwable ignore) {
ignore.printStackTrace();
}
}
@Override
public World getImpWorld() {
return getWorldName() != null ? Bukkit.getWorld(getWorldName()) : null;
}
@Override
public void sendChunk(int x, int z, int bitMask) {}
@Override
public void refreshChunk(FaweChunk fs) {}
@Override
public boolean regenerateChunk(World world, int x, int z, BaseBiome biome, Long seed) {
if (!keepLoaded.isEmpty()) keepLoaded.remove(MathMan.pairInt(x, z));
boolean result = world.regenerateChunk(x, z);
return result;
}
@Override
public CharFaweChunk getPrevious(CharFaweChunk fs, CHUNKSECTIONS sections, Map<?, ?> tiles, Collection<?>[] entities, Set<UUID> createdEntities, boolean all) throws Exception {
return fs;
}
@Override
public boolean hasSky() {
World world = getWorld();
return world == null || world.getEnvironment() == World.Environment.NORMAL;
}
private volatile boolean timingsEnabled;
private static boolean alertTimingsChange = true;
private static Field fieldTimingsEnabled;
private static Field fieldAsyncCatcherEnabled;
private static Method methodCheck;
static {
try {
fieldAsyncCatcherEnabled = Class.forName("org.spigotmc.AsyncCatcher").getField("enabled");
fieldAsyncCatcherEnabled.setAccessible(true);
} catch (Throwable ignore) {}
try {
fieldTimingsEnabled = Class.forName("co.aikar.timings.Timings").getDeclaredField("timingsEnabled");
fieldTimingsEnabled.setAccessible(true);
methodCheck = Class.forName("co.aikar.timings.TimingsManager").getDeclaredMethod("recheckEnabled");
methodCheck.setAccessible(true);
} catch (Throwable ignore){}
}
@Override
public void startSet(boolean parallel) {
ChunkListener.physicsFreeze = true;
if (parallel) {
try {
if (fieldAsyncCatcherEnabled != null) {
fieldAsyncCatcherEnabled.set(null, false);
}
if (fieldTimingsEnabled != null) {
timingsEnabled = (boolean) fieldTimingsEnabled.get(null);
if (timingsEnabled) {
if (alertTimingsChange) {
alertTimingsChange = false;
Fawe.debug("Having `parallel-threads` > 1 interferes with the timings.");
}
fieldTimingsEnabled.set(null, false);
methodCheck.invoke(null);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
@Override
public void endSet(boolean parallel) {
ChunkListener.physicsFreeze = false;
if (parallel) {
try {
if (fieldAsyncCatcherEnabled != null) {
fieldAsyncCatcherEnabled.set(null, true);
}
if (fieldTimingsEnabled != null && timingsEnabled) {
fieldTimingsEnabled.set(null, true);
methodCheck.invoke(null);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
@Override
public void sendBlockUpdate(final FaweChunk chunk, FawePlayer... players) {
if (players.length == 0) {
return;
}
int cx = chunk.getX();
int cz = chunk.getZ();
int view = Bukkit.getServer().getViewDistance();
boolean sendAny = false;
boolean[] send = new boolean[players.length];
for (int i = 0; i < players.length; i++) {
FawePlayer player = players[i];
Player bp = ((BukkitPlayer) player).parent;
Location loc = bp.getLocation();
if (Math.abs((loc.getBlockX() >> 4) - cx) <= view && Math.abs((loc.getBlockZ() >> 4) - cz) <= view) {
sendAny = true;
send[i] = true;
}
}
if (!sendAny) {
return;
}
final World world = getWorld();
final int bx = cx << 4;
final int bz = cz << 4;
chunk.forEachQueuedBlock(new FaweChunkVisitor() {
@Override
public void run(int localX, int y, int localZ, int combined) {
Location loc = new Location(world, bx + localX, y, bz + localZ);
for (int i = 0; i < players.length; i++) {
if (send[i]) {
((BukkitPlayer) players[i]).parent.sendBlockChange(loc, FaweCache.getId(combined), (byte) FaweCache.getData(combined));
}
}
}
});
}
}