From 9e297dc34a72193e52f5081c9f2a3ea19628f558 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Mon, 5 Sep 2016 12:35:05 +1000 Subject: [PATCH] MCPE port is now async --- .../com/boydti/fawe/bukkit/FaweBukkit.java | 1 - .../boydti/fawe/example/CharFaweChunk.java | 12 +- .../boydti/fawe/example/MappedFaweQueue.java | 6 +- .../fawe/example/NMSMappedFaweQueue.java | 2 +- .../com/boydti/fawe/object/FaweChunk.java | 5 +- .../object/extent/FastWorldEditExtent.java | 264 +------- nukkit/build.gradle | 1 + .../com/boydti/fawe/nukkit/FaweNukkit.java | 84 --- .../boydti/fawe/nukkit/core/NBTConverter.java | 2 +- .../fawe/nukkit/core/NukkitPlatform.java | 8 +- .../fawe/nukkit/core/NukkitTaskManager.java | 14 +- .../boydti/fawe/nukkit/core/NukkitWorld.java | 5 +- .../fawe/nukkit/core/NukkitWorldEdit.java | 9 +- .../fawe/nukkit/optimization/FaweNukkit.java | 121 ++++ .../nukkit/optimization/FaweNukkitPlayer.java | 73 +++ .../fawe/nukkit/optimization/Metrics.java | 580 ++++++++++++++++++ .../nukkit/optimization/NukkitCommand.java | 29 + .../optimization/queue/NukkitChunk.java | 215 +++++++ .../optimization/queue/NukkitQueue.java | 228 +++++++ 19 files changed, 1290 insertions(+), 369 deletions(-) delete mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/FaweNukkit.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkit.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkitPlayer.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/Metrics.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/NukkitCommand.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitChunk.java create mode 100644 nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java diff --git a/bukkit0/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit0/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index 9dd649e9..d90e1f3b 100644 --- a/bukkit0/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit0/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -68,7 +68,6 @@ public class FaweBukkit implements IFawe, Listener { debug(" - This is only a recommendation"); debug("=============================="); } - } catch (final Throwable e) { MainUtil.handleError(e); Bukkit.getServer().shutdown(); diff --git a/core/src/main/java/com/boydti/fawe/example/CharFaweChunk.java b/core/src/main/java/com/boydti/fawe/example/CharFaweChunk.java index 128b129d..65c35b92 100644 --- a/core/src/main/java/com/boydti/fawe/example/CharFaweChunk.java +++ b/core/src/main/java/com/boydti/fawe/example/CharFaweChunk.java @@ -41,10 +41,10 @@ public abstract class CharFaweChunk extends FaweChunk { */ public CharFaweChunk(FaweQueue parent, int x, int z) { super(parent, x, z); - this.ids = new char[16][]; - this.count = new short[16]; - this.air = new short[16]; - this.relight = new short[16]; + this.ids = new char[HEIGHT >> 4][]; + this.count = new short[HEIGHT >> 4]; + this.air = new short[HEIGHT >> 4]; + this.relight = new short[HEIGHT >> 4]; } @Override @@ -91,7 +91,7 @@ public abstract class CharFaweChunk extends FaweChunk { public int getTotalCount() { int total = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < count.length; i++) { total += Math.min(4096, this.count[i]); } return total; @@ -104,7 +104,7 @@ public abstract class CharFaweChunk extends FaweChunk { return Short.MAX_VALUE; } int total = 0; - for (int i = 0; i < 16; i++) { + for (int i = 0; i < relight.length; i++) { total += this.relight[i]; } return total; diff --git a/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java b/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java index 25fb09a8..cae500e8 100644 --- a/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java +++ b/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java @@ -150,7 +150,7 @@ public abstract class MappedFaweQueue extends FaweQueue { @Override public void setTile(int x, int y, int z, CompoundTag tag) { - if ((y > 255) || (y < 0)) { + if ((y >= FaweChunk.HEIGHT) || (y < 0)) { return; } int cx = x >> 4; @@ -161,7 +161,7 @@ public abstract class MappedFaweQueue extends FaweQueue { @Override public void setEntity(int x, int y, int z, CompoundTag tag) { - if ((y > 255) || (y < 0)) { + if ((y >= FaweChunk.HEIGHT) || (y < 0)) { return; } int cx = x >> 4; @@ -172,7 +172,7 @@ public abstract class MappedFaweQueue extends FaweQueue { @Override public void removeEntity(int x, int y, int z, UUID uuid) { - if ((y > 255) || (y < 0)) { + if ((y >= FaweChunk.HEIGHT) || (y < 0)) { return; } int cx = x >> 4; diff --git a/core/src/main/java/com/boydti/fawe/example/NMSMappedFaweQueue.java b/core/src/main/java/com/boydti/fawe/example/NMSMappedFaweQueue.java index cb829267..427d2c78 100644 --- a/core/src/main/java/com/boydti/fawe/example/NMSMappedFaweQueue.java +++ b/core/src/main/java/com/boydti/fawe/example/NMSMappedFaweQueue.java @@ -59,7 +59,7 @@ public abstract class NMSMappedFaweQueue ex boolean relight = false; boolean[] fix = new boolean[16]; boolean sky = hasSky(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < chunk.ids.length; i++) { if ((sky && ((chunk.getAir(i) & 4095) != 0 || (chunk.getCount(i) & 4095) != 0)) || chunk.getRelight(i) != 0) { relight = true; fix[i] = true; diff --git a/core/src/main/java/com/boydti/fawe/object/FaweChunk.java b/core/src/main/java/com/boydti/fawe/object/FaweChunk.java index 8d70a53b..f9d4b628 100644 --- a/core/src/main/java/com/boydti/fawe/object/FaweChunk.java +++ b/core/src/main/java/com/boydti/fawe/object/FaweChunk.java @@ -14,6 +14,7 @@ public abstract class FaweChunk { private FaweQueue parent; private int x,z; + public static int HEIGHT = 256; private final ArrayDeque tasks = new ArrayDeque(); @@ -123,7 +124,7 @@ public abstract class FaweChunk { public char[][] getCombinedIdArrays() { char[][] ids = new char[16][]; - for (int y = 0; y < 16; y++) { + for (int y = 0; y < HEIGHT >> 4; y++) { int y4 = y >> 4; short[][] i1 = FaweCache.CACHE_J[y]; for (int z = 0; z < 16; z++) { @@ -151,7 +152,7 @@ public abstract class FaweChunk { * @param data */ public void fill(int id, byte data) { - fillCuboid(0, 15, 0, 255, 0, 15, id, data); + fillCuboid(0, 15, 0, HEIGHT - 1, 0, 15, id, data); } /** diff --git a/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java index a0fdc092..940a49f3 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java @@ -27,10 +27,12 @@ import java.util.Map; public class FastWorldEditExtent extends AbstractDelegateExtent { private final FaweQueue queue; + private final int maxY; public FastWorldEditExtent(final World world, FaweQueue queue) { super(world); this.queue = queue; + this.maxY = world.getMaxY(); } public FaweQueue getQueue() { @@ -116,266 +118,8 @@ public class FastWorldEditExtent extends AbstractDelegateExtent { @Override public boolean setBlock(int x, int y, int z, final BaseBlock block) throws WorldEditException { - switch (y) { - case 0: - 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: - case 15: - case 16: - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - case 38: - case 39: - case 40: - case 41: - case 42: - case 43: - case 44: - case 45: - case 46: - case 47: - case 48: - case 49: - case 50: - case 51: - case 52: - case 53: - case 54: - case 55: - case 56: - case 57: - case 58: - case 59: - case 60: - case 61: - case 62: - case 63: - case 64: - case 65: - case 66: - case 67: - case 68: - case 69: - case 70: - case 71: - case 72: - case 73: - case 74: - case 75: - case 76: - case 77: - case 78: - case 79: - case 80: - case 81: - case 82: - case 83: - case 84: - case 85: - case 86: - case 87: - case 88: - case 89: - case 90: - case 91: - case 92: - case 93: - case 94: - case 95: - case 96: - case 97: - case 98: - case 99: - case 100: - case 101: - case 102: - case 103: - case 104: - case 105: - case 106: - case 107: - case 108: - case 109: - case 110: - case 111: - case 112: - case 113: - case 114: - case 115: - case 116: - case 117: - case 118: - case 119: - case 120: - case 121: - case 122: - case 123: - case 124: - case 125: - case 126: - case 127: - case 128: - case 129: - case 130: - case 131: - case 132: - case 133: - case 134: - case 135: - case 136: - case 137: - case 138: - case 139: - case 140: - case 141: - case 142: - case 143: - case 144: - case 145: - case 146: - case 147: - case 148: - case 149: - case 150: - case 151: - case 152: - case 153: - case 154: - case 155: - case 156: - case 157: - case 158: - case 159: - case 160: - case 161: - case 162: - case 163: - case 164: - case 165: - case 166: - case 167: - case 168: - case 169: - case 170: - case 171: - case 172: - case 173: - case 174: - case 175: - case 176: - case 177: - case 178: - case 179: - case 180: - case 181: - case 182: - case 183: - case 184: - case 185: - case 186: - case 187: - case 188: - case 189: - case 190: - case 191: - case 192: - case 193: - case 194: - case 195: - case 196: - case 197: - case 198: - case 199: - case 200: - case 201: - case 202: - case 203: - case 204: - case 205: - case 206: - case 207: - case 208: - case 209: - case 210: - case 211: - case 212: - case 213: - case 214: - case 215: - case 216: - case 217: - case 218: - case 219: - case 220: - case 221: - case 222: - case 223: - case 224: - case 225: - case 226: - case 227: - case 228: - case 229: - case 230: - case 231: - case 232: - case 233: - case 234: - case 235: - case 236: - case 237: - case 238: - case 239: - case 240: - case 241: - case 242: - case 243: - case 244: - case 245: - case 246: - case 247: - case 248: - case 249: - case 250: - case 251: - case 252: - case 253: - case 254: - case 255: - break; - default: - return false; + if (y >= maxY) { + return false; } final short id = (short) block.getId(); switch (id) { diff --git a/nukkit/build.gradle b/nukkit/build.gradle index dc0f28f6..7d9a97d2 100644 --- a/nukkit/build.gradle +++ b/nukkit/build.gradle @@ -26,6 +26,7 @@ shadowJar { include(dependency('com.google.code.gson:gson:2.2.4')) include(dependency('org.yaml:snakeyaml:1.16')) include(dependency('com.google.guava:guava:17.0')) + include(dependency(':core')) } archiveName = "${parent.name}-${project.name}-${parent.version}.jar" destinationDir = file '../target' diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/FaweNukkit.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/FaweNukkit.java deleted file mode 100644 index cca3c544..00000000 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/FaweNukkit.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.boydti.fawe.nukkit; - -import com.boydti.fawe.IFawe; -import com.boydti.fawe.object.FaweCommand; -import com.boydti.fawe.object.FawePlayer; -import com.boydti.fawe.object.FaweQueue; -import com.boydti.fawe.regions.FaweMaskManager; -import com.boydti.fawe.util.TaskManager; -import com.sk89q.worldedit.world.World; -import java.io.File; -import java.util.Collection; -import java.util.UUID; - -public class FaweNukkit implements IFawe { - @Override - public void debug(String s) { - - } - - @Override - public File getDirectory() { - return null; - } - - @Override - public void setupCommand(String label, FaweCommand cmd) { - - } - - @Override - public FawePlayer wrap(Object obj) { - return null; - } - - @Override - public void setupVault() { - - } - - @Override - public TaskManager getTaskManager() { - return null; - } - - @Override - public FaweQueue getNewQueue(String world, boolean fast) { - return null; - } - - @Override - public String getWorldName(World world) { - return null; - } - - @Override - public Collection getMaskManagers() { - return null; - } - - @Override - public void startMetrics() { - - } - - @Override - public String getPlatform() { - return null; - } - - @Override - public UUID getUUID(String name) { - return null; - } - - @Override - public String getName(UUID uuid) { - return null; - } - - @Override - public Object getBlocksHubApi() { - return null; - } -} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NBTConverter.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NBTConverter.java index 8d8ea45d..8c24b783 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NBTConverter.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NBTConverter.java @@ -24,7 +24,7 @@ import java.util.Map.Entry; /** * Converts between Jcn.nukkit.nbt.tag. and Minecraft cn.nukkit.nbt.tag. classes. */ -final class NBTConverter { +public final class NBTConverter { private NBTConverter() { } diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitPlatform.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitPlatform.java index 539be3f4..e1f0658e 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitPlatform.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitPlatform.java @@ -39,14 +39,14 @@ import java.util.Map; import java.util.UUID; import javax.annotation.Nullable; -class NukkitPlatform extends AbstractPlatform implements MultiUserPlatform { +public class NukkitPlatform extends AbstractPlatform implements MultiUserPlatform { private final NukkitWorldEdit mod; private final NukkitTaskManager taskManager; private boolean hookingEvents = false; private NukkitCommandManager commandManager; - NukkitPlatform(NukkitWorldEdit mod) { + public NukkitPlatform(NukkitWorldEdit mod) { this.mod = mod; this.commandManager = new NukkitCommandManager(mod.getServer().getCommandMap()); this.taskManager = new NukkitTaskManager(mod); @@ -93,6 +93,10 @@ class NukkitPlatform extends AbstractPlatform implements MultiUserPlatform { return ret; } + public NukkitTaskManager getTaskManager() { + return taskManager; + } + @Nullable @Override public Player matchPlayer(Player player) { diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitTaskManager.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitTaskManager.java index c653e74f..5b2adee3 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitTaskManager.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitTaskManager.java @@ -2,10 +2,11 @@ package com.boydti.fawe.nukkit.core; import cn.nukkit.plugin.Plugin; import cn.nukkit.scheduler.TaskHandler; +import com.boydti.fawe.util.TaskManager; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; -public class NukkitTaskManager { +public class NukkitTaskManager extends TaskManager{ private final Plugin plugin; @@ -13,19 +14,22 @@ public class NukkitTaskManager { this.plugin = plugin; } + @Override public int repeat(final Runnable r, final int interval) { TaskHandler task = this.plugin.getServer().getScheduler().scheduleRepeatingTask(r, interval, false); return task.getTaskId(); } + @Override public int repeatAsync(final Runnable r, final int interval) { TaskHandler task = this.plugin.getServer().getScheduler().scheduleRepeatingTask(r, interval, true); return task.getTaskId(); } - public AtomicInteger index = new AtomicInteger(0); - public HashMap tasks = new HashMap<>(); + private AtomicInteger index = new AtomicInteger(0); + private HashMap tasks = new HashMap<>(); + @Override public void async(final Runnable r) { if (r == null) { return; @@ -33,6 +37,7 @@ public class NukkitTaskManager { this.plugin.getServer().getScheduler().scheduleTask(r, true); } + @Override public void task(final Runnable r) { if (r == null) { return; @@ -40,6 +45,7 @@ public class NukkitTaskManager { this.plugin.getServer().getScheduler().scheduleTask(r, false); } + @Override public void later(final Runnable r, final int delay) { if (r == null) { return; @@ -47,10 +53,12 @@ public class NukkitTaskManager { this.plugin.getServer().getScheduler().scheduleDelayedTask(r, delay); } + @Override public void laterAsync(final Runnable r, final int delay) { this.plugin.getServer().getScheduler().scheduleDelayedTask(r, delay, true); } + @Override public void cancel(final int task) { if (task != -1) { this.plugin.getServer().getScheduler().cancelTask(task); diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorld.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorld.java index 6f003ecc..da4bca38 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorld.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorld.java @@ -19,6 +19,7 @@ import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.world.biome.BaseBiome; +import com.sk89q.worldedit.world.registry.LegacyWorldData; import com.sk89q.worldedit.world.registry.WorldData; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -246,7 +247,7 @@ public class NukkitWorld extends LocalWorld { @Override public int getMaxY() { - return 255; + return 127; } @Override @@ -261,7 +262,7 @@ public class NukkitWorld extends LocalWorld { @Override public WorldData getWorldData() { - throw new UnsupportedOperationException("Not implemented yet"); + return LegacyWorldData.getInstance(); } @Override diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java index fbf14984..6f67eb2b 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java @@ -24,6 +24,8 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.plugin.PluginBase; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.nukkit.optimization.FaweNukkit; import com.google.common.base.Joiner; import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.worldedit.LocalSession; @@ -31,7 +33,6 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; import com.sk89q.worldedit.extension.platform.Actor; -import com.sk89q.worldedit.extension.platform.Platform; import java.io.File; import java.util.Arrays; import java.util.logging.Logger; @@ -65,9 +66,8 @@ public class NukkitWorldEdit extends PluginBase { @Override public void onEnable() { try { - // TODO load FAWE logger = Logger.getLogger(NukkitWorldEdit.class.getCanonicalName()); - File file = new File(getDataFolder(), "config.yml"); + File file = new File(getDataFolder(), "config-basic.yml"); if (!file.exists()) { file.getParentFile().mkdirs(); file.createNewFile(); @@ -77,6 +77,7 @@ public class NukkitWorldEdit extends PluginBase { this.platform = new NukkitPlatform(this); getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); WorldEdit.getInstance().getPlatformManager().register(platform); + Fawe.set(new FaweNukkit(this)); logger.info("WorldEdit for Nukkit (version " + getInternalVersion() + ") is loaded"); WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); } catch (Throwable e) { @@ -151,7 +152,7 @@ public class NukkitWorldEdit extends PluginBase { * * @return the WorldEdit platform */ - public Platform getPlatform() { + public NukkitPlatform getPlatform() { return this.platform; } diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkit.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkit.java new file mode 100644 index 00000000..ebe1b8db --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkit.java @@ -0,0 +1,121 @@ +package com.boydti.fawe.nukkit.optimization; + +import cn.nukkit.Player; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.IFawe; +import com.boydti.fawe.nukkit.core.NukkitWorldEdit; +import com.boydti.fawe.nukkit.optimization.queue.NukkitQueue; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.regions.FaweMaskManager; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.world.World; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; +import java.util.logging.Level; + +public class FaweNukkit implements IFawe { + + private final NukkitWorldEdit plugin; + + public FaweNukkit(NukkitWorldEdit mod) { + this.plugin = mod; + FaweChunk.HEIGHT = 128; + } + + @Override + public void debug(String s) { + plugin.getWELogger().log(Level.INFO, s); + } + + @Override + public File getDirectory() { + return plugin.getDataFolder(); + } + + @Override + public void setupCommand(String label, final FaweCommand cmd) { + plugin.getServer().getCommandMap().register(label, new NukkitCommand(cmd)); + } + + @Override + public FawePlayer wrap(Object obj) { + if (obj.getClass() == String.class) { + String name = (String) obj; + FawePlayer existing = Fawe.get().getCachedPlayer(name); + if (existing != null) { + return existing; + } + return new FaweNukkitPlayer(getPlugin().getServer().getPlayer(name)); + } else if (obj instanceof Player) { + Player player = (Player) obj; + FawePlayer existing = Fawe.get().getCachedPlayer(player.getName()); + return existing != null ? existing : new FaweNukkitPlayer(player); + } else { + return null; + } + } + + public NukkitWorldEdit getPlugin() { + return plugin; + } + + @Override + public void setupVault() { + + } + + @Override + public TaskManager getTaskManager() { + return plugin.getPlatform().getTaskManager(); + } + + @Override + public FaweQueue getNewQueue(String world, boolean fast) { + return new NukkitQueue(this, world); + } + + @Override + public String getWorldName(World world) { + return world.getName(); + } + + @Override + public Collection getMaskManagers() { + return new ArrayList<>(); + } + + @Override + public void startMetrics() { + Metrics metrics = new Metrics(plugin); + metrics.start(); + } + + @Override + public String getPlatform() { + return "Nukkit-" + plugin.getServer().getNukkitVersion(); + } + + @Override + public UUID getUUID(String name) { + try { + return UUID.fromString(name); + } catch (Exception e) { + return null; + } + } + + @Override + public String getName(UUID uuid) { + return uuid.toString(); + } + + @Override + public Object getBlocksHubApi() { + return null; + } +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkitPlayer.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkitPlayer.java new file mode 100644 index 00000000..f0ba35e6 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/FaweNukkitPlayer.java @@ -0,0 +1,73 @@ +package com.boydti.fawe.nukkit.optimization; + +import cn.nukkit.Player; +import cn.nukkit.command.ConsoleCommandSender; +import cn.nukkit.level.Location; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.nukkit.core.NukkitPlatform; +import com.boydti.fawe.nukkit.core.NukkitPlayer; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.object.FawePlayer; +import java.util.UUID; + +public class FaweNukkitPlayer extends FawePlayer { + + private static ConsoleCommandSender console; + + public FaweNukkitPlayer(final Player parent) { + super(parent); + } + + @Override + public String getName() { + return this.parent.getName(); + } + + @Override + public UUID getUUID() { + return this.parent.getUniqueId(); + } + + @Override + public boolean hasPermission(final String perm) { + return this.parent.hasPermission(perm); + } + + @Override + public void setPermission(final String perm, final boolean flag) { + this.parent.addAttachment(Fawe. imp().getPlugin()).setPermission("fawe.bypass", flag); + } + + + @Override + public void resetTitle() { + sendTitle("",""); + } + + public void sendTitle(String title, String sub) { + throw new UnsupportedOperationException("Titles are not implemented in MCPE!"); + } + + @Override + public void sendMessage(final String message) { + this.parent.sendMessage(BBC.color(message)); + } + + @Override + public void executeCommand(final String cmd) { + Fawe. imp().getPlugin().getServer().dispatchCommand(parent, cmd); + } + + @Override + public FaweLocation getLocation() { + final Location loc = this.parent.getLocation(); + return new FaweLocation(loc.getLevel().getName(), loc.getFloorX(), loc.getFloorY(), loc.getFloorZ()); + } + + @Override + public com.sk89q.worldedit.entity.Player getPlayer() { + return new NukkitPlayer((NukkitPlatform) Fawe. imp().getPlugin().getPlatform(), parent); + } + +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/Metrics.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/Metrics.java new file mode 100644 index 00000000..38adda69 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/Metrics.java @@ -0,0 +1,580 @@ +package com.boydti.fawe.nukkit.optimization; + +import cn.nukkit.plugin.Plugin; +import cn.nukkit.plugin.PluginDescription; +import cn.nukkit.utils.LogLevel; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.io.FastByteArrayOutputStream; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.TaskManager; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import java.util.zip.GZIPOutputStream; + +public class Metrics { + + /** + * The current revision number. + */ + private static final int REVISION = 7; + /** + * The base url of the metrics domain. + */ + private static final String BASE_URL = "http://report.mcstats.org"; + /** + * The url used to report a server's status. + */ + private static final String REPORT_URL = "/plugin/%s"; + /** + * Interval of time to ping (in minutes). + */ + private static final int PING_INTERVAL = 15; + /** + * The plugin this metrics submits for. + */ + private final Plugin plugin; + /** + * All of the custom graphs to submit to metrics. + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + /** + * Unique server id. + */ + private final String guid; + /** + * Debug mode. + */ + private final boolean debug; + /** + * The scheduled task. + */ + private volatile int taskId = -1; + + public Metrics(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + this.plugin = plugin; + this.guid = UUID.randomUUID().toString(); + this.debug = false; + } + + /** + * GZip compress a string of bytes. + * + * @param input + * + * @return byte[] the file as a byte array + */ + public static byte[] gzip(String input) { + FastByteArrayOutputStream baos = new FastByteArrayOutputStream(); + GZIPOutputStream gzos = null; + try { + gzos = new GZIPOutputStream(baos); + gzos.write(input.getBytes("UTF-8")); + } catch (IOException e) { + MainUtil.handleError(e); + } finally { + if (gzos != null) { + try { + gzos.close(); + } catch (IOException ignore) { + } + } + } + return baos.toByteArray(); + } + + /** + * Appends a json encoded key/value pair to the given string builder. + * + * @param json + * @param key + * @param value + * + */ + private static void appendJSONPair(StringBuilder json, String key, String value) { + boolean isValueNumeric = false; + try { + if (value.equals("0") || !value.endsWith("0")) { + Double.parseDouble(value); + isValueNumeric = true; + } + } catch (NumberFormatException e) { + isValueNumeric = false; + } + if (json.charAt(json.length() - 1) != '{') { + json.append(','); + } + json.append(escapeJSON(key)); + json.append(':'); + if (isValueNumeric) { + json.append(value); + } else { + json.append(escapeJSON(value)); + } + } + + /** + * Escape a string to create a valid JSON string + * + * @param text + * + * @return String + */ + private static String escapeJSON(String text) { + StringBuilder builder = new StringBuilder(); + builder.append('"'); + for (int index = 0; index < text.length(); index++) { + char chr = text.charAt(index); + switch (chr) { + case '"': + case '\\': + builder.append('\\'); + builder.append(chr); + break; + case '\b': + builder.append("\\b"); + break; + case '\t': + builder.append("\\t"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + default: + if (chr < ' ') { + String t = "000" + Integer.toHexString(chr); + builder.append("\\u" + t.substring(t.length() - 4)); + } else { + builder.append(chr); + } + break; + } + } + builder.append('"'); + return builder.toString(); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * + * @return the encoded text, as UTF-8 + */ + private static String urlEncode(String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Construct and create a Graph that can be used to separate specific plotters to their own graphs on the metrics + * website. Plotters can be added to the graph object returned. + * + * @param name The name of the graph + * + * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given + */ + public Graph createGraph(String name) { + if (name == null) { + throw new IllegalArgumentException("Graph name cannot be null"); + } + // Construct the graph object + Graph graph = new Graph(name); + // Now we can add our graph + this.graphs.add(graph); + // and return back + return graph; + } + + /** + * Add a Graph object to BukkitMetrics that represents data for the plugin that should be sent to the backend + * + * @param graph The name of the graph + */ + public void addGraph(Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + this.graphs.add(graph); + } + + /** + * Start measuring statistics. This will immediately create an async repeating task as the plugin and send the + * initial data to the metrics backend, and then after that it will post in increments of PING_INTERVAL * 1200 + * ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + // Is metrics already running? + if (this.taskId != -1) { + return true; + } + // Begin hitting the server with glorious data + this.taskId = TaskManager.IMP.repeatAsync(new Runnable() { + private boolean firstPost = true; + @Override + public void run() { + try { + postPlugin(!this.firstPost); + // After the first post we set firstPost to + // false + // Each post thereafter will be a ping + this.firstPost = false; + } catch (IOException e) { + MainUtil.handleError(e); + if (Metrics.this.debug) { + plugin.getLogger().log(LogLevel.INFO, "[Metrics] " + e.getMessage()); + } + } + } + }, PING_INTERVAL * 1200); + return true; + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws java.io.IOException + */ + public void enable() { + // Enable Task, if it is not running + if (this.taskId == -1) { + start(); + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + */ + public void disable() { + // Disable Task, if it is running + if (this.taskId != -1) { + TaskManager.IMP.cancel(this.taskId); + this.taskId = -1; + } + } + + /** + * Gets the File object of the config file that should be used to store + * data such as the GUID and opt-out status. + * + * @return the File object for the config file + */ + public File getConfigFile() { + // I believe the easiest way to get the base folder (e.g craftbukkit set + // via -P) for plugins to use + // is to abuse the plugin object we already have + // plugin.getDataFolder() => base/plugins/PluginA/ + // pluginsFolder => base/plugins/ + // The base is not necessarily relative to the startup directory. + File pluginsFolder = this.plugin.getDataFolder().getParentFile(); + // return => base/plugins/PluginMetrics/config.yml + return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml"); + } + + /** + * Generic method that posts a plugin to the metrics website. + */ + private void postPlugin(boolean isPing) throws IOException { + // Server software specific section + PluginDescription description = this.plugin.getDescription(); + String pluginName = description.getName(); + boolean onlineMode = false; + String pluginVersion = description.getVersion(); + String serverVersion = plugin.getServer().getNukkitVersion(); + int playersOnline = plugin.getServer().getOnlinePlayers().size(); + // END server software specific section -- all code below does not use + // any code outside of this class / Java + // Construct the post data + StringBuilder json = new StringBuilder(1024); + json.append('{'); + // The plugin's description file containing all of the plugin data such as name, version, author, etc + appendJSONPair(json, "guid", this.guid); + appendJSONPair(json, "plugin_version", pluginVersion); + appendJSONPair(json, "server_version", serverVersion); + appendJSONPair(json, "players_online", Integer.toString(playersOnline)); + // New data as of R6 + String osname = System.getProperty("os.name"); + String osarch = System.getProperty("os.arch"); + String osversion = System.getProperty("os.version"); + String java_version = System.getProperty("java.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + // normalize os arch .. amd64 -> x86_64 + if (osarch.equals("amd64")) { + osarch = "x86_64"; + } + appendJSONPair(json, "osname", osname); + appendJSONPair(json, "osarch", osarch); + appendJSONPair(json, "osversion", osversion); + appendJSONPair(json, "cores", Integer.toString(coreCount)); + appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0"); + appendJSONPair(json, "java_version", java_version); + // If we're pinging, append it + if (isPing) { + appendJSONPair(json, "ping", "1"); + } + if (!this.graphs.isEmpty()) { + synchronized (this.graphs) { + json.append(','); + json.append('"'); + json.append("graphs"); + json.append('"'); + json.append(':'); + json.append('{'); + boolean firstGraph = true; + for (Graph graph : this.graphs) { + StringBuilder graphJson = new StringBuilder(); + graphJson.append('{'); + for (Plotter plotter : graph.getPlotters()) { + appendJSONPair(graphJson, plotter.getColumnName(), Integer.toString(plotter.getValue())); + } + graphJson.append('}'); + if (!firstGraph) { + json.append(','); + } + json.append(escapeJSON(graph.getName())); + json.append(':'); + json.append(graphJson); + firstGraph = false; + } + json.append('}'); + } + } + // close json + json.append('}'); + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName))); + // Connect to the website + URLConnection connection; + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + byte[] uncompressed = json.toString().getBytes(); + byte[] compressed = gzip(json.toString()); + // Headers + connection.addRequestProperty("User-Agent", "MCStats/" + REVISION); + connection.addRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", Integer.toString(compressed.length)); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.setDoOutput(true); + if (this.debug) { + Fawe.debug("[Metrics] Prepared request for " + pluginName + " uncompressed=" + uncompressed.length + " compressed=" + compressed.length); + } + try { + try (OutputStream os = connection.getOutputStream()) { + os.write(compressed); + os.flush(); + } + String response; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + response = reader.readLine(); + if (this.debug) { + Fawe.debug("[Metrics] Response for " + pluginName + ": " + response); + } + } + if (response == null || response.startsWith("ERR") || response.startsWith("7")) { + if (response == null) { + response = "null"; + } else if (response.startsWith("7")) { + response = response.substring(response.startsWith("7,") ? 2 : 1); + } + throw new IOException(response); + } else { + // Is this the first update this hour? + if ("1".equals(response) || response.contains("This is your first update this hour")) { + synchronized (this.graphs) { + for (Graph graph : this.graphs) { + for (Plotter plotter : graph.getPlotters()) { + plotter.reset(); + } + } + } + } + } + } catch (Exception e) { + if (this.debug) { + MainUtil.handleError(e); + } + } + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + /** + * Represents a custom graph on the website + */ + public static class Graph { + + /** + * The graph's name, alphanumeric and spaces only :) If it does not comply to the above when submitted, it is + * rejected + */ + private final String name; + /** + * The set of plotters that are contained within this graph + */ + private final Set plotters = new LinkedHashSet<>(); + + private Graph(String name) { + this.name = name; + } + + /** + * Gets the graph's name + * + * @return the Graph's name + */ + public String getName() { + return this.name; + } + + /** + * Add a plotter to the graph, which will be used to plot entries + * + * @param plotter the plotter to add to the graph + */ + public void addPlotter(Plotter plotter) { + this.plotters.add(plotter); + } + + /** + * Remove a plotter from the graph + * + * @param plotter the plotter to remove from the graph + */ + public void removePlotter(Plotter plotter) { + this.plotters.remove(plotter); + } + + /** + * Gets an unmodifiable set of the plotter objects in the graph + * + * @return an unmodifiable {@link java.util.Set} of the plotter objects + */ + public Set getPlotters() { + return Collections.unmodifiableSet(this.plotters); + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Graph)) { + return false; + } + Graph graph = (Graph) object; + return graph.name.equals(this.name); + } + + /** + * Called when the server owner decides to opt-out of BukkitMetrics while the server is running. + */ + protected void onOptOut() { + } + } + + /** + * Interface used to collect custom data for a plugin + */ + public abstract static class Plotter { + + /** + * The plot's name + */ + private final String name; + + /** + * Construct a plotter with the default plot name + */ + public Plotter() { + this("Default"); + } + + /** + * Construct a plotter with a specific plot name + * + * @param name the name of the plotter to use, which will show up on the website + */ + public Plotter(String name) { + this.name = name; + } + + /** + * Get the current value for the plotted point. Since this function defers to an external function it may or may + * not return immediately thus cannot be guaranteed to be thread friendly or safe. This function can be called + * from any thread so care should be taken when accessing resources that need to be synchronized. + * + * @return the current value for the point to be plotted. + */ + public abstract int getValue(); + + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public String getColumnName() { + return this.name; + } + + /** + * Called after the website graphs have been updated + */ + public void reset() { + } + + @Override + public int hashCode() { + return getColumnName().hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Plotter)) { + return false; + } + Plotter plotter = (Plotter) object; + return plotter.name.equals(this.name) && plotter.getValue() == getValue(); + } + } +} \ No newline at end of file diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/NukkitCommand.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/NukkitCommand.java new file mode 100644 index 00000000..bbe424cb --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/NukkitCommand.java @@ -0,0 +1,29 @@ +package com.boydti.fawe.nukkit.optimization; + +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandSender; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; + +public class NukkitCommand extends Command { + + private final FaweCommand cmd; + + public NukkitCommand(final FaweCommand cmd) { + super(cmd.getPerm()); + this.cmd = cmd; + } + + @Override + public boolean execute(CommandSender sender, String label, String[] args) { + final FawePlayer plr = Fawe.imp().wrap(sender); + if (!sender.hasPermission(this.cmd.getPerm()) && !sender.isOp()) { + BBC.NO_PERM.send(plr, this.cmd.getPerm()); + return true; + } + this.cmd.executeSafe(plr, args); + return true; + } +} diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitChunk.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitChunk.java new file mode 100644 index 00000000..dca6abbf --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitChunk.java @@ -0,0 +1,215 @@ +package com.boydti.fawe.nukkit.optimization.queue; + +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.generic.BaseFullChunk; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.nukkit.core.NBTConverter; +import com.boydti.fawe.nukkit.core.NukkitUtil; +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.util.MainUtil; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.world.biome.BaseBiome; +import java.util.ArrayList; + +public class NukkitChunk extends CharFaweChunk { + + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + * + * @param parent + * @param x + * @param z + */ + public NukkitChunk(FaweQueue parent, int x, int z) { + super(parent, x, z); + } + + @Override + public BaseFullChunk getNewChunk() { + return ((NukkitQueue) getParent()).getWorld().getChunk(getX(), getZ()); + } + + private int layer = -1; + private int index; + private boolean place = true; + + public void execute(long start) { + int recommended = 25 + NukkitQueue.ALLOCATE; + boolean more = true; + NukkitQueue parent = (NukkitQueue) getParent(); + Level world = ((NukkitQueue) getParent()).getWorld(); + world.clearCache(true); + final BaseFullChunk chunk = (world.getChunk(getX(), getZ(), true)); + char[][] sections = getCombinedIdArrays(); + if (layer == -1) { + // Biomes + if (layer == 0) { + final int[][] biomes = getBiomeArray(); + if (biomes != null) { + final LocalWorld lw = NukkitUtil.getLocalWorld(world); + final int X = getX() << 4; + final int Z = getZ() << 4; + final BaseBiome bb = new BaseBiome(0); + int last = 0; + for (int x = 0; x < 16; x++) { + final int[] array = biomes[x]; + if (array == null) { + continue; + } + for (int z = 0; z < 16; z++) { + final int biome = array[z]; + if (biome == 0) { + continue; + } + if (last != biome) { + last = biome; + bb.setId(biome); + } + lw.setBiome(new Vector2D(X + x, Z + z), bb); + } + } + } + } + } else if (index != 0) { + if (place) { + layer--; + } else { + layer++; + } + } + mainloop: + do { + if (place) { + if (++layer >= sections.length) { + place = false; + layer = sections.length - 1; + } + } else if (--layer < 0) { + more = false; + break; + } + try { + // Efficiently merge sections + int changes = getCount(layer); + int lighting = getRelight(layer); + if (changes == 0) { + continue; + } + final char[] newArray = sections[layer]; + if (newArray == null) { + continue; + } + final byte[] cacheX = FaweCache.CACHE_X[layer]; + final short[] cacheY = FaweCache.CACHE_Y[layer]; + final byte[] cacheZ = FaweCache.CACHE_Z[layer]; + boolean checkTime = !((getAir(layer) == 4096 || (getCount(layer) == 4096 && getAir(layer) == 0) || (getCount(layer) == getAir(layer))) && getRelight(layer) == 0); + if (!checkTime) { + ArrayList threads = new ArrayList(); + for (int k = 0; k < 16; k++) { + final int l = k << 8; + final int y = cacheY[l]; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + for (int m = l; m < l + 256; m++) { + char combined = newArray[m]; + switch (combined) { + case 0: + continue; + case 1: + if (!place) { + int x = cacheX[m]; + int z = cacheZ[m]; + chunk.setBlockId(x, y, z, 0); + } + continue; + default: + if (place) { + int x = cacheX[m]; + int z = cacheZ[m]; + int id = combined >> 4; + chunk.setBlockId(x, y, z, id); + chunk.setBlockData(x, y, z, (combined & 0xF)); + } + continue; + } + } + } + }); + threads.add(thread); + thread.start(); + } + for (Thread thread : threads) { + thread.join(); + } + } else { + for (;index < 4096; index++) { + int j = place ? index : 4095 - index; + char combined = newArray[j]; + switch (combined) { + case 0: + continue; + case 1: + if (!place) { + int x = cacheX[j]; + int z = cacheZ[j]; + int y = cacheY[j]; + chunk.setBlockId(x, y, z, 0); + } + break; + default: + int id = combined >> 4; + boolean light = FaweCache.hasLight(id); + if (light) { + if (place) { + continue; + } + } else if (!place) { + continue; + } + if (light != place) { + int data = combined & 0xF; + int x = cacheX[j]; + int z = cacheZ[j]; + int y = cacheY[j]; + chunk.setBlockId(x, y, z, id); + chunk.setBlockData(x, y, z, data); + if (FaweCache.hasNBT(id)) { + CompoundTag tile = getTile(x, y, z); + if (tile != null) { + cn.nukkit.nbt.tag.CompoundTag tag = (cn.nukkit.nbt.tag.CompoundTag) NBTConverter.toNative(tile); + chunk.addBlockEntity(new BlockEntity(chunk, tag) { + @Override + public boolean isBlockEntityValid() { + return getBlock().getId() == id; + } + }); + break; + } + } + } else { + continue; + } + break; + } + if (checkTime && System.currentTimeMillis() - start > recommended) { + index++; + break mainloop; + } + } + index = 0; + } + } catch (final Throwable e) { + MainUtil.handleError(e); + } + } while (System.currentTimeMillis() - start < recommended); + if (more || place) { + this.addToQueue(); + } + } +} \ No newline at end of file diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java new file mode 100644 index 00000000..f7c6d3a3 --- /dev/null +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java @@ -0,0 +1,228 @@ +package com.boydti.fawe.nukkit.optimization.queue; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.math.Vector3; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.example.CharFaweChunk; +import com.boydti.fawe.example.NMSMappedFaweQueue; +import com.boydti.fawe.nukkit.core.NBTConverter; +import com.boydti.fawe.nukkit.optimization.FaweNukkit; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.RunnableVal; +import com.sk89q.jnbt.CompoundTag; +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class NukkitQueue extends NMSMappedFaweQueue { + private final FaweNukkit faweNukkit; + private final Level world; + + public static int ALLOCATE; + public static double TPS_TARGET = 18.5; + private static int LIGHT_MASK = 0x739C0; + + public NukkitQueue(FaweNukkit fn, String world) { + super(world); + this.faweNukkit = fn; + this.world = faweNukkit.getPlugin().getServer().getLevelByName(world); + if (Settings.QUEUE.EXTRA_TIME_MS != Integer.MIN_VALUE) { + ALLOCATE = Settings.QUEUE.EXTRA_TIME_MS; + Settings.QUEUE.EXTRA_TIME_MS = Integer.MIN_VALUE; + } + } + + public FaweNukkit getFaweNukkit() { + return faweNukkit; + } + + @Override + public boolean execute(FaweChunk fc) { + if (super.execute(fc)) { + return true; + } else { + return false; + } + } + + @Override + public void refreshChunk(FaweChunk fs) { + NukkitChunk fc = (NukkitChunk) fs; + Collection players = faweNukkit.getPlugin().getServer().getOnlinePlayers().values(); + int view = faweNukkit.getPlugin().getServer().getViewDistance(); + for (Player player : players) { + Position pos = player.getPosition(); + int pcx = pos.getFloorX() >> 4; + int pcz = pos.getFloorZ() >> 4; + if (Math.abs(pcx - fs.getX()) > view || Math.abs(pcz - fs.getZ()) > view) { + continue; + } + world.requestChunk(fs.getX(), fs.getZ(), player); + } + } + + @Override + public CharFaweChunk getPrevious(CharFaweChunk fs, BaseFullChunk sections, Map tiles, Collection[] entities, Set createdEntities, boolean all) throws Exception { + return fs; + } + + private int skip; + + @Override + public boolean setComponents(FaweChunk fc, RunnableVal changeTask) { + if (skip > 0) { + skip--; + fc.addToQueue(); + return true; + } + long start = System.currentTimeMillis(); + ((NukkitChunk) fc).execute(start); + if (System.currentTimeMillis() - start > 50 || Fawe.get().getTPS() < TPS_TARGET) { + skip = 10; + } + return true; + } + + @Override + public File getSaveFolder() { + return new File(world.getFolderName() + File.separator + "region"); + } + + @Override + public boolean hasSky() { + return world.getDimension() == 0; + } + + @Override + public void setFullbright(BaseFullChunk sections) { + for (int y = 0; y < 128; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + sections.setBlockSkyLight(x, y, z, 15); + } + } + } + } + + @Override + public boolean removeLighting(BaseFullChunk sections, RelightMode mode, boolean hasSky) { + for (int y = 0; y < 128; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + sections.setBlockSkyLight(x, y, z, 0); + sections.setBlockLight(x, y, z, 0); + } + } + } + return true; + } + + private Vector3 mutable = new Vector3(); + private Vector3 getMutable(int x, int y, int z) { + mutable.x = x; + mutable.y = y; + mutable.z = z; + return mutable; + } + + @Override + public void relight(int x, int y, int z) { + world.updateAllLight(getMutable(x, y, z)); + } + + @Override + public void relightBlock(int x, int y, int z) { + world.updateBlockLight(x, y, z); + } + + @Override + public void relightSky(int x, int y, int z) { + world.updateBlockSkyLight(x, y, z); + } + + @Override + public void setSkyLight(BaseFullChunk chunkSection, int x, int y, int z, int value) { + chunkSection.setBlockSkyLight(x & 15, y & 15, z & 15, value); + } + + @Override + public void setBlockLight(BaseFullChunk chunkSection, int x, int y, int z, int value) { + chunkSection.setBlockLight(x & 15, y & 15, z & 15, value); + } + + @Override + public CompoundTag getTileEntity(BaseFullChunk baseChunk, int x, int y, int z) { + BlockEntity entity = baseChunk.getTile(x & 15, y, z & 15); + if (entity == null) { + return null; + } + cn.nukkit.nbt.tag.CompoundTag nbt = entity.namedTag; + return NBTConverter.fromNative(nbt); + } + + @Override + public BaseFullChunk getChunk(Level level, int x, int z) { + return (BaseFullChunk) level.getChunk(x, z); + } + + @Override + public Level getImpWorld() { + return world; + } + + @Override + public boolean isChunkLoaded(Level level, int x, int z) { + return level.isChunkLoaded(x, z); + } + + @Override + public boolean regenerateChunk(Level level, int x, int z) { + level.regenerateChunk(x, z); + return true; + } + + @Override + public FaweChunk getFaweChunk(int x, int z) { + return new NukkitChunk(this, x, z); + } + + @Override + public boolean loadChunk(Level level, int x, int z, boolean generate) { + return level.loadChunk(x, z, generate); + } + + @Override + public BaseFullChunk getCachedSections(Level level, int cx, int cz) { + BaseFullChunk chunk = (BaseFullChunk) world.getChunk(cx, cz); + return chunk; + } + + @Override + public int getCombinedId4Data(BaseFullChunk chunkSection, int x, int y, int z) { + int id = chunkSection.getBlockId(x & 15, y & 15, z & 15); + if (FaweCache.hasData(id)) { + int data = chunkSection.getBlockData(x & 15, y & 15, z & 15); + return (id << 4) + data; + } else { + return (id << 4); + } + } + + @Override + public int getSkyLight(BaseFullChunk sections, int x, int y, int z) { + return sections.getBlockSkyLight(x & 15, y & 15, z & 15); + } + + @Override + public int getEmmittedLight(BaseFullChunk sections, int x, int y, int z) { + return sections.getBlockLight(x & 15, y & 15, z & 15); + } +}