From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Fri, 14 Dec 2018 21:53:58 -0800 Subject: [PATCH] Tuinity Server Changes diff --git a/pom.xml b/pom.xml index ef8ee637a..6fd596817 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,11 @@ - 4.0.0 - paper + tuinity jar 1.16.1-R0.1-SNAPSHOT - Paper - https://papermc.io + Tuinity-Server + https://github.com/Spottedleaf/Tuinity @@ -18,16 +18,16 @@ - com.destroystokyo.paper - paper-parent + com.tuinity + tuinity-parent dev-SNAPSHOT ../pom.xml - com.destroystokyo.paper - paper-api + com.tuinity + tuinity-api ${project.version} compile @@ -164,15 +164,15 @@ - paper-${minecraft.version} - clean install + tuinity-${minecraft.version} + install com.lukegb.mojo gitdescribe-maven-plugin 1.3 - git-Paper- + git-Tuinity- .. diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java index dd0722397..2966c5731 100644 --- a/src/main/java/co/aikar/timings/MinecraftTimings.java +++ b/src/main/java/co/aikar/timings/MinecraftTimings.java @@ -43,6 +43,8 @@ public final class MinecraftTimings { public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Tuinity - add timings for scoreboard search + private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); private MinecraftTimings() {} diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java index e33e889c2..5dfa06588 100644 --- a/src/main/java/co/aikar/timings/TimingsExport.java +++ b/src/main/java/co/aikar/timings/TimingsExport.java @@ -229,7 +229,8 @@ public class TimingsExport extends Thread { parent.put("config", createObject( pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), - pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) + pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report + pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report )); new TimingsExport(listeners, parent, history).start(); diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java index 49a38c660..255bbd6e4 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -24,8 +24,8 @@ public class PaperVersionFetcher implements VersionFetcher { @Nonnull @Override public String getVersionMessage(@Nonnull String serverVersion) { - String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); - String updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); + String[] parts = serverVersion.substring("git-Tuinity-".length()).split("[-\\s]"); // Tuinity + String updateMessage = getUpdateStatusMessage("Spottedleaf/Tuinity", GITHUB_BRANCH_NAME, parts[0]); // Tuinity String history = getHistory(); return history != null ? history + "\n" + updateMessage : updateMessage; @@ -49,13 +49,10 @@ public class PaperVersionFetcher implements VersionFetcher { private static String getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { int distance; - try { - int jenkinsBuild = Integer.parseInt(versionInfo); - distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); - } catch (NumberFormatException ignored) { + // Tuinity - we don't have jenkins setup versionInfo = versionInfo.replace("\"", ""); distance = fetchDistanceFromGitHub(repo, branch, versionInfo); - } + // Tuinity - we don't have jenkins setup switch (distance) { case -1: diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java index 0692fe33b..4263eb917 100644 --- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java @@ -188,6 +188,7 @@ public final class PaperTickList extends TickListServer { // extend to avo } public void onChunkSetTicking(final int chunkX, final int chunkZ) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list chunk ticking update"); // Tuinity - soft async catcher final ArrayList> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ)); if (pending == null) { return; @@ -268,6 +269,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public void tick() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher final ChunkProviderServer chunkProvider = this.world.getChunkProvider(); this.world.getMethodProfiler().enter("cleaning"); @@ -296,6 +298,7 @@ public final class PaperTickList extends TickListServer { // extend to avo if (toTick.tickState == STATE_TICKING) { toTick.tickState = STATE_TICKED; } // else it's STATE_CANCELLED_TICK + MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick } else { // re-schedule eventually toTick.tickState = STATE_SCHEDULED; @@ -413,6 +416,7 @@ public final class PaperTickList extends TickListServer { // extend to avo } public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list schedule"); // Tuinity - soft async catcher final NextTickListEntry entry = new NextTickListEntry<>(pos, data, targetTick, priority); if (this.excludeFromScheduling.test(entry.getData())) { return; @@ -468,6 +472,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public List> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get in bounding box"); // Tuinity - soft async catcher if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) { return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above } @@ -524,6 +529,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list copy"); // Tuinity - soft async catcher // start copy from TickListServer // TODO check on update List> list = this.getEntriesInBoundingBox(structureboundingbox, false, false); Iterator> iterator = list.iterator(); @@ -543,6 +549,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public List> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get"); // Tuinity - soft async catcher // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks // not at ticking status, and ticking status requires neighbours loaded // so with this method we will reduce scheduler churning @@ -574,6 +581,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list serialize"); // Tuinity - soft async catcher // start copy from TickListServer // TODO check on update List> list = this.getEntriesInChunk(chunkcoordintpair, false, true); @@ -583,6 +591,7 @@ public final class PaperTickList extends TickListServer { // extend to avo @Override public int getTotalScheduledEntries() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get size"); // Tuinity - soft async catcher // good thing this is only used in debug reports // TODO check on update int ret = 0; diff --git a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java new file mode 100644 index 000000000..97c4100c5 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java @@ -0,0 +1,159 @@ +package com.tuinity.tuinity.chunk; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.server.MCUtil; +import net.minecraft.server.WorldServer; +import java.util.List; + +public final class SingleThreadChunkRegionManager { + + static final int REGION_SECTION_MERGE_RADIUS = 1; + + static final int REGION_SECTION_CHUNK_SIZE = 8; + static final int REGION_SECTION_CHUNK_SIZE_SHIFT = 3; + + final Long2ObjectOpenHashMap regionsBySection = new Long2ObjectOpenHashMap<>(4096, 0.25f); + final LongOpenHashSet chunks = new LongOpenHashSet(8192, 0.25f); + + public final WorldServer world; + + public SingleThreadChunkRegionManager(final WorldServer world) { + this.world = world; + } + + public void addChunk(final int chunkX, final int chunkZ) { + this.addChunk(chunkX, chunkZ, true); + } + + void addChunk(final int chunkX, final int chunkZ, boolean addToChunks) { + final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ); + final long sectionPos = MCUtil.getCoordinateKey(chunkX >> REGION_SECTION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_SECTION_CHUNK_SIZE_SHIFT); + if (addToChunks) { + this.chunks.add(coordinate); + } + + // merge nearby regions first + + // gather regions to merge + + SingleThreadChunkRegionManager.ChunkRegion mergeIntoCandidate = null; + int mergeCandidateChunkCount = -1; + + List toMerge = null; + + final int regionSectionX = chunkX >> REGION_SECTION_CHUNK_SIZE_SHIFT; + final int regionSectionZ = chunkZ >> REGION_SECTION_CHUNK_SIZE_SHIFT; + + final int checkXStart = regionSectionX - REGION_SECTION_MERGE_RADIUS; + final int checkZStart = regionSectionZ - REGION_SECTION_MERGE_RADIUS; + final int checkXEnd = regionSectionX + REGION_SECTION_MERGE_RADIUS; + final int checkZEnd = regionSectionZ + REGION_SECTION_MERGE_RADIUS; + + // select the ideal region to merge into + for (int checkX = checkXStart; checkX <= checkXEnd; ++checkX) { + for (int checkZ = checkZStart; checkZ <= checkZEnd; ++checkZ) { + final SingleThreadChunkRegionManager.ChunkRegion region = this.regionsBySection.get(MCUtil.getCoordinateKey(checkX, checkZ)); + if (region == null) { + continue; + } + + final int coordinateSize = region.coordinates.size(); + if (coordinateSize > mergeCandidateChunkCount) { + mergeIntoCandidate = region; + mergeCandidateChunkCount = coordinateSize; + } + if (toMerge == null) { + toMerge = new java.util.ArrayList<>(4); + toMerge.add(region); + } + } + } + + // merge + if (toMerge != null) { + for (int i = 0, len = toMerge.size(); i < len; ++i) { + final SingleThreadChunkRegionManager.ChunkRegion needsMerge = toMerge.get(i); + if (needsMerge == mergeIntoCandidate) { + continue; + } + // this function forwards the sections + needsMerge.mergeInto(this, mergeIntoCandidate); + } + } else { + mergeIntoCandidate = new ChunkRegion(); + } + + mergeIntoCandidate.addChunk(coordinate); + if (mergeIntoCandidate.addSection(sectionPos)) { + this.regionsBySection.put(sectionPos, mergeIntoCandidate); + } + } + + public void removeChunk(final int chunkX, final int chunkZ) { + final long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ); + final long sectionPos = MCUtil.getCoordinateKey(chunkX >> REGION_SECTION_CHUNK_SIZE_SHIFT, chunkZ >> REGION_SECTION_CHUNK_SIZE_SHIFT); + this.chunks.remove(coordinate); + + final SingleThreadChunkRegionManager.ChunkRegion region = this.regionsBySection.get(sectionPos); + if (region == null) { + throw new IllegalStateException("Cannot remove chunk form no region"); + } + + if (!region.removeChunk(coordinate)) { + throw new IllegalStateException("Cannot remove chunk from region, has no chunk"); + } + } + + public void recalculateRegions() { + this.regionsBySection.clear(); + for (final LongIterator iterator = this.chunks.iterator(); iterator.hasNext();) { + final long coordinate = iterator.nextLong(); + this.addChunk(MCUtil.getCoordinateX(coordinate), MCUtil.getCoordinateZ(coordinate), false); + } + } + + static final class ChunkRegion { + private final LongOpenHashSet coordinates = new LongOpenHashSet(); + private final LongOpenHashSet sections = new LongOpenHashSet(); + private boolean dead; + + public void mergeInto(final SingleThreadChunkRegionManager regionManager, final ChunkRegion region) { + if (region.dead) { + throw new IllegalStateException("Attempting to merge into a dead region"); + } else if (this.dead) { + throw new IllegalStateException("Attempting to merge from a dead region"); + } + + for (final LongIterator iterator = this.coordinates.iterator(); iterator.hasNext();) { + final long coordinate = iterator.nextLong(); + if (!region.addChunk(coordinate)) { + throw new IllegalStateException("Regions cannot share chunks"); + } + } + + for (final LongIterator iterator = this.sections.iterator(); iterator.hasNext();) { + regionManager.regionsBySection.replace(iterator.nextLong(), region); + } + + this.dead = true; + } + + boolean addSection(final long sectionPos) { + return this.sections.add(sectionPos); + } + + boolean removeSection(final long sectionPos) { + return this.sections.remove(sectionPos); + } + + boolean addChunk(final long coordinate) { + return this.coordinates.add(coordinate); + } + + boolean removeChunk(final long coordinate) { + return this.coordinates.remove(coordinate); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java new file mode 100644 index 000000000..1c7b858ed --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java @@ -0,0 +1,279 @@ +package com.tuinity.tuinity.config; + +import com.destroystokyo.paper.util.SneakyThrow; +import net.minecraft.server.TicketType; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import java.io.File; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.logging.Level; + +public final class TuinityConfig { + + public static final String CONFIG_HEADER = "Configuration file for Tuinity."; + public static final int CURRENT_CONFIG_VERSION = 2; + + private static final Object[] EMPTY = new Object[0]; + + private static File configFile; + public static YamlConfiguration config; + private static int configVersion; + + public static void init(final File file) { + // TODO remove this in the future... + final File tuinityConfig = new File(file.getParent(), "tuinity.yml"); + if (!tuinityConfig.exists()) { + final File oldConfig = new File(file.getParent(), "concrete.yml"); + oldConfig.renameTo(tuinityConfig); + } + TuinityConfig.configFile = file; + final YamlConfiguration config = new YamlConfiguration(); + config.options().header(CONFIG_HEADER); + config.options().copyDefaults(true); + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failure to create tuinity config", ex); + } + } else { + try { + config.load(file); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Failure to load tuinity config", ex); + SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ + throw new RuntimeException(ex); // unreachable + } + } + + TuinityConfig.load(config); + } + + public static void load(final YamlConfiguration config) { + TuinityConfig.config = config; + TuinityConfig.configVersion = TuinityConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); + TuinityConfig.set("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); + + for (final Method method : TuinityConfig.class.getDeclaredMethods()) { + if (method.getReturnType() != void.class || method.getParameterCount() != 0 || + !Modifier.isPrivate(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) { + continue; + } + + try { + method.setAccessible(true); + method.invoke(null, EMPTY); + } catch (final Exception ex) { + SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ + throw new RuntimeException(ex); // unreachable + } + } + + /* We re-save to add new options */ + try { + config.save(TuinityConfig.configFile); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); + } + } + + static void set(final String path, final Object value) { + TuinityConfig.config.set(path, value); + } + + static boolean getBoolean(final String path, final boolean dfl) { + TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl)); + return TuinityConfig.config.getBoolean(path, dfl); + } + + static int getInt(final String path, final int dfl) { + TuinityConfig.config.addDefault(path, Integer.valueOf(dfl)); + return TuinityConfig.config.getInt(path, dfl); + } + + static long getLong(final String path, final long dfl) { + TuinityConfig.config.addDefault(path, Long.valueOf(dfl)); + return TuinityConfig.config.getLong(path, dfl); + } + + static double getDouble(final String path, final double dfl) { + TuinityConfig.config.addDefault(path, Double.valueOf(dfl)); + return TuinityConfig.config.getDouble(path, dfl); + } + + public static boolean tickWorldsInParallel; + + /** + * if tickWorldsInParallel == true, then this value is used as a default only for worlds + */ + public static int tickThreads; + + /* + private static void worldticking() { + tickWorldsInParallel = TuinityConfig.getBoolean("tick-worlds-in-parallel", false); + tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future + }*/ + + public static int delayChunkUnloadsBy; + + private static void delayChunkUnloadsBy() { + delayChunkUnloadsBy = TuinityConfig.getInt("delay-chunkunloads-by", 1) * 20; + if (delayChunkUnloadsBy >= 0) { + TicketType.DELAYED_UNLOAD.loadPeriod = delayChunkUnloadsBy; + } + } + + public static boolean lagCompensateBlockBreaking; + + private static void lagCompensateBlockBreaking() { + lagCompensateBlockBreaking = TuinityConfig.getBoolean("lag-compensate-block-breaking", true); + } + + public static boolean pistonsCanPushTileEntities; + + private static void pistonsCanPushTileEntities() { + pistonsCanPushTileEntities = TuinityConfig.getBoolean("pistons-can-push-tile-entities", false); + } + + public static final class WorldConfig { + + public final String worldName; + public ConfigurationSection config; + ConfigurationSection worldDefaults; + + public WorldConfig(final String worldName) { + this.worldName = worldName; + this.init(); + } + + public void init() { + this.worldDefaults = TuinityConfig.config.getConfigurationSection("world-settings.default"); + if (this.worldDefaults == null) { + this.worldDefaults = TuinityConfig.config.createSection("world-settings.default"); + } + + String worldSectionPath = TuinityConfig.configVersion < 1 ? this.worldName : "world-settings.".concat(this.worldName); + ConfigurationSection section = TuinityConfig.config.getConfigurationSection(worldSectionPath); + if (section == null) { + section = TuinityConfig.config.createSection(worldSectionPath); + } + TuinityConfig.config.set(worldSectionPath, section); + + this.load(section); + } + + public void load(final ConfigurationSection config) { + this.config = config; + + for (final Method method : TuinityConfig.WorldConfig.class.getDeclaredMethods()) { + if (method.getReturnType() != void.class || method.getParameterCount() != 0 || + !Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) { + continue; + } + + try { + method.setAccessible(true); + method.invoke(this, EMPTY); + } catch (final Exception ex) { + SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ + throw new RuntimeException(ex); // unreachable + } + } + + if (TuinityConfig.configVersion < 1) { + ConfigurationSection oldSection = TuinityConfig.config.getConfigurationSection(this.worldName); + TuinityConfig.config.set("world-settings.".concat(this.worldName), oldSection); + TuinityConfig.config.set(this.worldName, null); + } + + /* We re-save to add new options */ + try { + TuinityConfig.config.save(TuinityConfig.configFile); + } catch (final Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); + } + } + + /** + * update world defaults for the specified path, but also sets this world's config value for the path + * if it exists + */ + void set(final String path, final Object val) { + this.worldDefaults.set(path, val); + if (this.config.get(path) != null) { + this.config.set(path, val); + } + } + + boolean getBoolean(final String path, final boolean dfl) { + this.worldDefaults.addDefault(path, Boolean.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getBoolean(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getBoolean(path, this.worldDefaults.getBoolean(path)); + } + + int getInt(final String path, final int dfl) { + this.worldDefaults.addDefault(path, Integer.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getInt(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getInt(path, this.worldDefaults.getInt(path)); + } + + long getLong(final String path, final long dfl) { + this.worldDefaults.addDefault(path, Long.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getLong(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getLong(path, this.worldDefaults.getLong(path)); + } + + double getDouble(final String path, final double dfl) { + this.worldDefaults.addDefault(path, Double.valueOf(dfl)); + if (TuinityConfig.configVersion < 1) { + if (this.config.getDouble(path) == dfl) { + this.config.set(path, null); + } + } + return this.config.getDouble(path, this.worldDefaults.getDouble(path)); + } + + /** ignored if {@link TuinityConfig#tickWorldsInParallel} == false */ + public int threads; + + /* + private void worldthreading() { + final int threads = this.getInt("tick-threads", -1); + this.threads = threads == -1 ? TuinityConfig.tickThreads : threads; + }*/ + + public int spawnLimitMonsters; + public int spawnLimitAnimals; + public int spawnLimitWaterAmbient; + public int spawnLimitWaterAnimals; + public int spawnLimitAmbient; + + private void perWorldSpawnLimit() { + final String path = "spawn-limits"; + + this.spawnLimitMonsters = this.getInt(path + ".monsters", -1); + this.spawnLimitAnimals = this.getInt(path + ".animals", -1); + this.spawnLimitWaterAmbient = this.getInt(path + ".water-ambient", -1); + this.spawnLimitWaterAnimals = this.getInt(path + ".water-animals", -1); + this.spawnLimitAmbient = this.getInt(path + ".ambient", -1); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/CachedLists.java b/src/main/java/com/tuinity/tuinity/util/CachedLists.java new file mode 100644 index 000000000..a54f516ba --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java @@ -0,0 +1,53 @@ +package com.tuinity.tuinity.util; + +import net.minecraft.server.AxisAlignedBB; +import net.minecraft.server.Entity; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.util.UnsafeList; +import java.util.List; + +public class CachedLists { + + static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); + static boolean tempCollisionListInUse; + + public static List getTempCollisionList() { + if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { + return new UnsafeList<>(16); + } + tempCollisionListInUse = true; + return TEMP_COLLISION_LIST; + } + + public static void returnTempCollisionList(List list) { + if (list != TEMP_COLLISION_LIST) { + return; + } + ((UnsafeList)list).setSize(0); + tempCollisionListInUse = false; + } + + static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); + static boolean tempGetEntitiesListInUse; + + public static List getTempGetEntitiesList() { + if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { + return new UnsafeList<>(16); + } + tempGetEntitiesListInUse = true; + return TEMP_GET_ENTITIES_LIST; + } + + public static void returnTempGetEntitiesList(List list) { + if (list != TEMP_GET_ENTITIES_LIST) { + return; + } + ((UnsafeList)list).setSize(0); + tempGetEntitiesListInUse = false; + } + + public static void reset() { + TEMP_COLLISION_LIST.completeReset(); + TEMP_GET_ENTITIES_LIST.completeReset(); + } +} diff --git a/src/main/java/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java new file mode 100644 index 000000000..08ed24325 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java @@ -0,0 +1,41 @@ +package com.tuinity.tuinity.util; + +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; + +public final class TickThread extends Thread { + + public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("tuinity.strict-thread-checks"); + + static { + if (STRICT_THREAD_CHECKS) { + MinecraftServer.LOGGER.warn("Strict thread checks enabled - performance may suffer"); + } + } + + public static void softEnsureTickThread(final String reason) { + if (!STRICT_THREAD_CHECKS) { + return; + } + ensureTickThread(reason); + } + + + public static void ensureTickThread(final String reason) { + if (!Bukkit.isPrimaryThread()) { + MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); + throw new IllegalStateException(reason); + } + } + + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ + + public TickThread(final Runnable run, final String name, final int id) { + super(run, name); + this.id = id; + } + + public static TickThread getCurrentTickThread() { + return (TickThread)Thread.currentThread(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java new file mode 100644 index 000000000..e12d09645 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java @@ -0,0 +1,265 @@ +package com.tuinity.tuinity.util.maplist; + +import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import java.util.Arrays; +import java.util.NoSuchElementException; + +public final class IteratorSafeOrderedReferenceSet { + + protected final Reference2IntLinkedOpenHashMap indexMap; + protected int firstInvalidIndex = -1; + + protected final ReferenceLinkedOpenHashSet pendingAdditions; + + /* list impl */ + protected E[] listElements; + protected int listSize; + + protected final double maxFragFactor; + + protected int iteratorCount; + + public IteratorSafeOrderedReferenceSet() { + this(16, 0.75f, 16, 0.2); + } + + public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, final double maxFragFactor) { + this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor); + this.indexMap.defaultReturnValue(-1); + this.pendingAdditions = new ReferenceLinkedOpenHashSet<>(); + this.maxFragFactor = maxFragFactor; + this.listElements = (E[])new Object[arrayCapacity]; + } + + protected final double getFragFactor() { + return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); + } + + public int createRawIterator() { + ++this.iteratorCount; + if (this.indexMap.isEmpty()) { + return -1; + } else { + return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0; + } + } + + public int advanceRawIterator(final int index) { + final E[] elements = this.listElements; + int ret = index + 1; + for (int len = this.listSize; ret < len; ++ret) { + if (elements[ret] != null) { + return ret; + } + } + + return -1; + } + + public void finishRawIterator() { + if (--this.iteratorCount == 0) { + if (this.getFragFactor() >= this.maxFragFactor) { + this.defrag(); + } + if (!this.pendingAdditions.isEmpty()) { + int index = this.listSize; + int neededLen = index + this.pendingAdditions.size(); + + if (neededLen < 0) { + throw new IllegalStateException("Too large"); + } + + if (neededLen > this.listElements.length) { + this.listElements = Arrays.copyOf(this.listElements, neededLen * 2); + } + + final E[] elements = this.listElements; + java.util.Iterator iterator = this.pendingAdditions.iterator(); + for (int i = index; i < neededLen; ++i) { + final E element = iterator.next(); + elements[i] = element; + this.indexMap.put(element, i); + } + + this.pendingAdditions.clear(); + this.listSize = neededLen; + } + } + } + + public boolean remove(final E element) { + final int index = this.indexMap.removeInt(element); + if (index >= 0) { + if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) { + this.firstInvalidIndex = index; + } + this.listElements[index] = null; + return true; + } else { + return this.pendingAdditions.remove(element); + } + } + + public boolean add(final E element) { + if (this.iteratorCount > 0) { + if (this.indexMap.containsKey(element)) { + return true; + } + return this.pendingAdditions.add(element); + } else { + final int listSize = this.listSize; + + final int previous = this.indexMap.putIfAbsent(element, listSize); + if (previous != -1) { + return false; + } + + if (listSize >= this.listElements.length) { + this.listElements = Arrays.copyOf(this.listElements, listSize * 2); + } + this.listElements[listSize] = element; + this.listSize = listSize + 1; + + return true; + } + } + + protected void defrag() { + if (this.firstInvalidIndex < 0) { + return; // nothing to do + } + + if (this.indexMap.isEmpty()) { + Arrays.fill(this.listElements, 0, this.listSize, null); + this.listSize = 0; + this.firstInvalidIndex = -1; + return; + } + + final E[] backingArray = this.listElements; + + int lastValidIndex; + java.util.Iterator> iterator; + + if (this.firstInvalidIndex == 0) { + iterator = this.indexMap.reference2IntEntrySet().fastIterator(); + lastValidIndex = 0; + } else { + lastValidIndex = this.firstInvalidIndex; + final E key = backingArray[lastValidIndex - 1]; + iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry() { + @Override + public int getIntValue() { + throw new UnsupportedOperationException(); + } + + @Override + public int setValue(int i) { + throw new UnsupportedOperationException(); + } + + @Override + public E getKey() { + return key; + } + }); + } + + while (iterator.hasNext()) { + final Reference2IntMap.Entry entry = iterator.next(); + + final int newIndex = lastValidIndex++; + backingArray[newIndex] = entry.getKey(); + entry.setValue(newIndex); + } + + // cleanup end + Arrays.fill(backingArray, lastValidIndex, this.listSize, null); + this.listSize = lastValidIndex; + this.firstInvalidIndex = -1; + } + + public E rawGet(final int index) { + return this.listElements[index]; + } + + public int size() { + // always returns the correct amount - listSize can be different + return this.indexMap.size(); + } + + public IteratorSafeOrderedReferenceSet.Iterator iterator() { + ++this.iteratorCount; + return new BaseIterator<>(this); + } + + public static interface Iterator extends java.util.Iterator { + + public void finishedIterating(); + + } + + protected static final class BaseIterator implements IteratorSafeOrderedReferenceSet.Iterator { + + protected final IteratorSafeOrderedReferenceSet set; + protected int nextIndex; + protected E currentValue; + protected boolean finished; + + protected BaseIterator(final IteratorSafeOrderedReferenceSet set) { + this.set = set; + } + + @Override + public boolean hasNext() { + if (this.finished) { + return false; + } + if (this.currentValue != null) { + return true; + } + + final E[] elements = this.set.listElements; + int index, len; + for (index = this.nextIndex, len = this.set.listSize; index < len; ++index) { + final E element = elements[index]; + if (element != null) { + this.currentValue = element; + this.nextIndex = index + 1; + return true; + } + } + + this.nextIndex = index; + return false; + } + + @Override + public E next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + final E ret = this.currentValue; + + this.currentValue = null; + + return ret; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void finishedIterating() { + if (this.finished) { + throw new IllegalStateException(); + } + this.finished = true; + this.set.finishRawIterator(); + } + } +} diff --git a/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java new file mode 100644 index 000000000..76593df29 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java @@ -0,0 +1,246 @@ +package com.tuinity.tuinity.voxel; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.doubles.DoubleList; +import net.minecraft.server.AxisAlignedBB; +import net.minecraft.server.EnumDirection; +import net.minecraft.server.VoxelShape; +import net.minecraft.server.VoxelShapes; +import java.util.ArrayList; +import java.util.List; + +public final class AABBVoxelShape extends VoxelShape { + + public final AxisAlignedBB aabb; + private boolean isEmpty; + + public AABBVoxelShape(AxisAlignedBB aabb) { + super(VoxelShapes.getFullUnoptimisedCube().getShape()); + this.aabb = aabb; + this.isEmpty = (fuzzyEquals(aabb.minX, aabb.maxX) && fuzzyEquals(aabb.minY, aabb.maxY) && fuzzyEquals(aabb.minZ, aabb.maxZ)); + } + + static boolean fuzzyEquals(double d0, double d1) { + return Math.abs(d0 - d1) <= 1.0e-7; + } + + @Override + public boolean isEmpty() { + return this.isEmpty; + } + + @Override + public double b(EnumDirection.EnumAxis enumdirection_enumaxis) { // getMin + switch (enumdirection_enumaxis.ordinal()) { + case 0: + return this.aabb.minX; + case 1: + return this.aabb.minY; + case 2: + return this.aabb.minZ; + default: + throw new IllegalStateException("Unknown axis requested"); + } + } + + @Override + public double c(EnumDirection.EnumAxis enumdirection_enumaxis) { //getMax + switch (enumdirection_enumaxis.ordinal()) { + case 0: + return this.aabb.maxX; + case 1: + return this.aabb.maxY; + case 2: + return this.aabb.maxZ; + default: + throw new IllegalStateException("Unknown axis requested"); + } + } + + @Override + public AxisAlignedBB getBoundingBox() { // rets bounding box enclosing this entire shape + return this.aabb; + } + + // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis. + @Override + protected double a(EnumDirection.EnumAxis enumdirection_enumaxis, int i) { // getPointFromIndex + switch (enumdirection_enumaxis.ordinal() | (i << 2)) { + case (0 | (0 << 2)): + return this.aabb.minX; + case (1 | (0 << 2)): + return this.aabb.minY; + case (2 | (0 << 2)): + return this.aabb.minZ; + case (0 | (1 << 2)): + return this.aabb.maxX; + case (1 | (1 << 2)): + return this.aabb.maxY; + case (2 | (1 << 2)): + return this.aabb.maxZ; + default: + throw new IllegalStateException("Unknown axis requested"); + } + } + + private DoubleList cachedListX; + private DoubleList cachedListY; + private DoubleList cachedListZ; + + @Override + protected DoubleList a(EnumDirection.EnumAxis enumdirection_enumaxis) { // getPoints + switch (enumdirection_enumaxis.ordinal()) { + case 0: + return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX; + case 1: + return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY; + case 2: + return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ; + default: + throw new IllegalStateException("Unknown axis requested"); + } + } + + @Override + public VoxelShape a(double d0, double d1, double d2) { // createOffset + return new AABBVoxelShape(this.aabb.offset(d0, d1, d2)); + } + + @Override + public VoxelShape c() { // simplify + return this; + } + + @Override + public void b(VoxelShapes.a voxelshapes_a) { // forEachAABB + voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ); + } + + @Override + public List d() { // getAABBs + List ret = new ArrayList<>(1); + ret.add(this.aabb); + return ret; + } + + @Override + protected int a(EnumDirection.EnumAxis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset + switch (enumdirection_enumaxis.ordinal()) { + case 0: + return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1; + case 1: + return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1; + case 2: + return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1; + default: + throw new IllegalStateException("Unknown axis requested"); + } + } + + @Override + protected boolean b(double d0, double d1, double d2) { // containsPoint + return this.aabb.contains(d0, d1, d2); + } + + @Override + public VoxelShape a(EnumDirection enumdirection) { // unknown + return super.a(enumdirection); + } + + @Override + public double a(EnumDirection.EnumAxis enumdirection_enumaxis, AxisAlignedBB axisalignedbb, double d0) { // collide + if (this.isEmpty) { + return d0; + } + if (Math.abs(d0) < 1.0e-7) { + return 0.0; + } + switch (enumdirection_enumaxis.ordinal()) { + case 0: + return this.collideX(axisalignedbb, d0); + case 1: + return this.collideY(axisalignedbb, d0); + case 2: + return this.collideZ(axisalignedbb, d0); + default: + throw new IllegalStateException("Unknown axis requested"); + } + } + + // collideX, collideY, collideZ are copied from 1.12 src and remapped + // so the code all belongs to mojang + + public double collideX(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxY > this.aabb.minY && axisalignedbb.minY < this.aabb.maxY + && axisalignedbb.maxZ > this.aabb.minZ && axisalignedbb.minZ < this.aabb.maxZ) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxX <= this.aabb.minX) { + d1 = this.aabb.minX - axisalignedbb.maxX; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minX >= this.aabb.maxX) { + d1 = this.aabb.maxX - axisalignedbb.minX; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public double collideY(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxX > this.aabb.minX && axisalignedbb.minX < this.aabb.maxX + && axisalignedbb.maxZ > this.aabb.minZ && axisalignedbb.minZ < this.aabb.maxZ) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxY <= this.aabb.minY) { + d1 = this.aabb.minY - axisalignedbb.maxY; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minY >= this.aabb.maxY) { + d1 = this.aabb.maxY - axisalignedbb.minY; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public double collideZ(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxX > this.aabb.minX && axisalignedbb.minX < this.aabb.maxX + && axisalignedbb.maxY > this.aabb.minY && axisalignedbb.minY < this.aabb.maxY) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxZ <= this.aabb.minZ) { + d1 = this.aabb.minZ - axisalignedbb.maxZ; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minZ >= this.aabb.maxZ) { + d1 = this.aabb.maxZ - axisalignedbb.minZ; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + @Override + public boolean intersects(AxisAlignedBB axisalingedbb) { + return this.aabb.intersects(axisalingedbb); + } +} diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java index d3387a4e1..1588d101e 100644 --- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java +++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java @@ -51,6 +51,10 @@ public class AdvancementDataPlayer { private Advancement l; private boolean m = true; + // Tuinity start - fix advancement data player leakage + final Map> criterionData = Maps.newIdentityHashMap(); + // Tuinity end - fix advancement data player leakage + public AdvancementDataPlayer(DataFixer datafixer, PlayerList playerlist, AdvancementDataWorld advancementdataworld, File file, EntityPlayer entityplayer) { this.d = datafixer; this.e = playerlist; diff --git a/src/main/java/net/minecraft/server/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java index ed9b2f9ad..d54bf7140 100644 --- a/src/main/java/net/minecraft/server/AxisAlignedBB.java +++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java @@ -13,6 +13,119 @@ public class AxisAlignedBB { public final double maxY; public final double maxZ; + // Tuinity start + public final boolean isEmpty() { + return (this.maxX - this.minX) < 1.0E-7 && (this.maxY - this.minY) < 1.0E-7 && (this.maxZ - this.minZ) < 1.0E-7; + } + + public static AxisAlignedBB getBoxForChunk(int chunkX, int chunkZ) { + double x = (double)(chunkX << 4); + double z = (double)(chunkZ << 4); + // use a bounding box bigger than the chunk to prevent entities from entering it on move + return new AxisAlignedBB(x - 1.0E-7, 0.0, z - 1.0E-7, x + (16.0 + 1.0E-7), 255.0, z + (16.0 + 1.0E-7), false); + } + + // collideX, collideY, collideZ are copied from 1.12 src + // so the code all belongs to mojang + public double collideX(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY + && axisalignedbb.maxZ > this.minZ && axisalignedbb.minZ < this.maxZ) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxX <= this.minX) { + d1 = this.minX - axisalignedbb.maxX; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minX >= this.maxX) { + d1 = this.maxX - axisalignedbb.minX; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public double collideY(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX + && axisalignedbb.maxZ > this.minZ && axisalignedbb.minZ < this.maxZ) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxY <= this.minY) { + d1 = this.minY - axisalignedbb.maxY; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minY >= this.maxY) { + d1 = this.maxY - axisalignedbb.minY; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public double collideZ(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX + && axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxZ <= this.minZ) { + d1 = this.minZ - axisalignedbb.maxZ; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minZ >= this.maxZ) { + d1 = this.maxZ - axisalignedbb.minZ; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public final AxisAlignedBB offsetX(double dx) { + return new AxisAlignedBB(this.minX + dx, this.minY, this.minZ, this.maxX + dx, this.maxY, this.maxZ, false); + } + + public final AxisAlignedBB offsetY(double dy) { + return new AxisAlignedBB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); + } + + public final AxisAlignedBB offsetZ(double dz) { + return new AxisAlignedBB(this.minX, this.minY, this.minZ + dz, this.maxX, this.maxY, this.maxZ + dz, false); + } + + public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5, boolean dummy) { + this.minX = d0; + this.minY = d1; + this.minZ = d2; + this.maxX = d3; + this.maxY = d4; + this.maxZ = d5; + } + + public final AxisAlignedBB expandUpwards(double dy) { + return new AxisAlignedBB(this.minX, this.minY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); + } + + public final AxisAlignedBB expandUpwardsAndCutBelow(double dy) { + return new AxisAlignedBB(this.minX, this.maxY, this.minZ, this.maxX, this.maxY + dy, this.maxZ, false); + } + // Tuinity end + public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5) { this.minX = Math.min(d0, d3); this.minY = Math.min(d1, d4); @@ -185,6 +298,7 @@ public class AxisAlignedBB { return new AxisAlignedBB(d0, d1, d2, d3, d4, d5); } + public final AxisAlignedBB offset(double d0, double d1, double d2) { return this.d(d0, d1, d2); } // Tuinity - OBFHELPER public AxisAlignedBB d(double d0, double d1, double d2) { return new AxisAlignedBB(this.minX + d0, this.minY + d1, this.minZ + d2, this.maxX + d0, this.maxY + d1, this.maxZ + d2); } @@ -193,6 +307,7 @@ public class AxisAlignedBB { return new AxisAlignedBB(this.minX + (double) blockposition.getX(), this.minY + (double) blockposition.getY(), this.minZ + (double) blockposition.getZ(), this.maxX + (double) blockposition.getX(), this.maxY + (double) blockposition.getY(), this.maxZ + (double) blockposition.getZ()); } + public final AxisAlignedBB offset(Vec3D vec3d) { return this.b(vec3d); } // Tuinity - OBFHELPER public AxisAlignedBB c(Vec3D vec3d) { return this.d(vec3d.x, vec3d.y, vec3d.z); } @@ -212,6 +327,7 @@ public class AxisAlignedBB { return this.e(vec3d.x, vec3d.y, vec3d.z); } + public final boolean contains(double d0, double d1, double d2) { return this.e(d0, d1, d2); } // Tuinity - OBFHELPER public boolean e(double d0, double d1, double d2) { return d0 >= this.minX && d0 < this.maxX && d1 >= this.minY && d1 < this.maxY && d2 >= this.minZ && d2 < this.maxZ; } diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java index 1842e6983..dab89b0c6 100644 --- a/src/main/java/net/minecraft/server/BaseBlockPosition.java +++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java @@ -16,9 +16,9 @@ public class BaseBlockPosition implements Comparable { return IntStream.of(new int[]{baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()}); }); public static final BaseBlockPosition ZERO = new BaseBlockPosition(0, 0, 0); - private int a; - private int b; - private int e; + protected int a; // Tuinity - private->protected - diff on change, this is the x coordinate + protected int b; // Tuinity - private->protected - diff on change, this is the y coordinate + protected int e; // Tuinity - private->protected - diff on change, this is the z coordinate // Paper start public boolean isValidLocation() { @@ -71,15 +71,15 @@ public class BaseBlockPosition implements Comparable { return this.e; } - protected void o(int i) { + protected void o_unused(int i) { // Tuinity - not needed here this.a = i; } - protected void p(int i) { + protected void p_unused(int i) { // Tuinity - not needed here this.b = i; } - protected void q(int i) { + protected void q_unused(int i) { // Tuinity - not needed here this.e = i; } diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java index db198811d..52ebdfcc0 100644 --- a/src/main/java/net/minecraft/server/BiomeBase.java +++ b/src/main/java/net/minecraft/server/BiomeBase.java @@ -92,6 +92,18 @@ public class BiomeBase { return new WorldGenCarverWrapper<>(worldgencarverabstract, c0); } + // Tuinity start - optimise biome conversion + private org.bukkit.block.Biome bukkitBiome; + + public final org.bukkit.block.Biome getBukkitBiome() { + if (this.bukkitBiome == null) { + this.bukkitBiome = org.bukkit.block.Biome.valueOf(IRegistry.BIOME.getKey(this).getKey().toUpperCase(java.util.Locale.ENGLISH)); + } + + return this.bukkitBiome; + } + // Tuinity end - optimise biome conversion + protected BiomeBase(BiomeBase.a biomebase_a) { if (biomebase_a.a != null && biomebase_a.b != null && biomebase_a.c != null && biomebase_a.d != null && biomebase_a.e != null && biomebase_a.f != null && biomebase_a.g != null && biomebase_a.j != null) { this.m = biomebase_a.a; diff --git a/src/main/java/net/minecraft/server/BlockBase.java b/src/main/java/net/minecraft/server/BlockBase.java index ff770a3b0..12027ecad 100644 --- a/src/main/java/net/minecraft/server/BlockBase.java +++ b/src/main/java/net/minecraft/server/BlockBase.java @@ -176,8 +176,8 @@ public abstract class BlockBase { return VoxelShapes.a(); } - @Deprecated - public int f(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { + @Deprecated public final int getOpacity(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { return this.f(iblockdata, iblockaccess, blockposition); } // Tuinity - OBFHELPER + @Deprecated public int f(IBlockData iblockdata, IBlockAccess iblockaccess, BlockPosition blockposition) { // Tuinity - OBFHELPER return iblockdata.i(iblockaccess, blockposition) ? iblockaccess.H() : (iblockdata.a(iblockaccess, blockposition) ? 0 : 1); } @@ -289,14 +289,14 @@ public abstract class BlockBase { public abstract static class BlockData extends IBlockDataHolder { - private final int b; - private final boolean e; + private final int b; public final int getEmittedLight() { return this.b; } // Tuinity - OBFHELPER + private final boolean e; public final boolean isTransparentOnSomeFaces() { return this.e; } // Tuinity - OBFHELPER private final boolean f; private final Material g; private final MaterialMapColor h; public final float strength; private final boolean j; - private final boolean k; + private final boolean k; public final boolean isOpaque() { return this.k; } // Tuinity - OBFHELPER private final BlockBase.e l; private final BlockBase.e m; private final BlockBase.e n; @@ -332,10 +332,25 @@ public abstract class BlockBase { } // Paper end + // Tuinity start - micro the hell out of this call + protected boolean shapeExceedsCube = true; + public final boolean shapeExceedsCube() { + return this.shapeExceedsCube; + } + // Tuinity end + + // Tuinity start + protected boolean isTicking; + protected Fluid fluid; + // Tuinity end + public void a() { + this.fluid = this.getBlock().d(this.p()); // Tuinity - moved from getFluid() + this.isTicking = this.getBlock().isTicking(this.p()); // Tuinity - moved from isTicking() if (!this.getBlock().o()) { this.a = new BlockBase.BlockData.a(this.p()); } + this.shapeExceedsCube = this.a == null || this.a.c; // Tuinity - moved from actual method to here } @@ -359,10 +374,12 @@ public abstract class BlockBase { return this.a != null ? this.a.g : this.getBlock().b(this.p(), iblockaccess, blockposition); } + public final int getOpacity(IBlockAccess iblockaccess, BlockPosition blockposition) { return this.b(iblockaccess, blockposition); } // Tuinity - OBFHELPER public int b(IBlockAccess iblockaccess, BlockPosition blockposition) { return this.a != null ? this.a.h : this.getBlock().f(this.p(), iblockaccess, blockposition); } + public final VoxelShape getCullingFace(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { return this.a(iblockaccess, blockposition, enumdirection); } // Tuinity - OBFHELPER public VoxelShape a(IBlockAccess iblockaccess, BlockPosition blockposition, EnumDirection enumdirection) { return this.a != null && this.a.i != null ? this.a.i[enumdirection.ordinal()] : VoxelShapes.a(this.c(iblockaccess, blockposition), enumdirection); } @@ -371,19 +388,19 @@ public abstract class BlockBase { return this.getBlock().d(this.p(), iblockaccess, blockposition); } - public boolean d() { - return this.a == null || this.a.c; + public final boolean d() { // Tuinity + return this.shapeExceedsCube; // Tuinity - moved into shape cache init } - public boolean e() { + public final boolean e() { // Tuinity return this.e; } - public int f() { + public final int f() { // Tuinity return this.b; } - public boolean isAir() { + public final boolean isAir() { // Tuinity return this.f; } @@ -449,7 +466,7 @@ public abstract class BlockBase { } } - public boolean l() { + public final boolean l() { // Tuinity return this.k; } @@ -621,12 +638,12 @@ public abstract class BlockBase { return this.getBlock().a(block); } - public Fluid getFluid() { - return this.getBlock().d(this.p()); + public final Fluid getFluid() { // Tuinity + return this.fluid; // Tuinity - moved into init } - public boolean isTicking() { - return this.getBlock().isTicking(this.p()); + public final boolean isTicking() { // Tuinity + return this.isTicking; // Tuinity - moved into init } public SoundEffectType getStepSound() { diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java index 44b9bfcdc..dba774018 100644 --- a/src/main/java/net/minecraft/server/BlockChest.java +++ b/src/main/java/net/minecraft/server/BlockChest.java @@ -10,7 +10,7 @@ import javax.annotation.Nullable; public class BlockChest extends BlockChestAbstract implements IBlockWaterlogged { public static final BlockStateDirection FACING = BlockFacingHorizontal.FACING; - public static final BlockStateEnum c = BlockProperties.aF; + public static final BlockStateEnum c = BlockProperties.aF; public static final BlockStateEnum getChestTypeEnum() { return BlockChest.c; } // Tuinity - OBFHELPER public static final BlockStateBoolean d = BlockProperties.C; public static final BlockStateBoolean waterlogged() { return d; } // Paper OBFHELPER protected static final VoxelShape e = Block.a(1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D); protected static final VoxelShape f = Block.a(1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D); @@ -195,7 +195,7 @@ public class BlockChest extends BlockChestAbstract implements I @Override public void remove(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { if (!iblockdata.a(iblockdata1.getBlock())) { - TileEntity tileentity = world.getTileEntity(blockposition); + TileEntity tileentity = world.getTileEntity(blockposition, false); // Tuinity - block has since changed. if (tileentity instanceof IInventory) { InventoryUtils.dropInventory(world, blockposition, (IInventory) tileentity); diff --git a/src/main/java/net/minecraft/server/BlockPiston.java b/src/main/java/net/minecraft/server/BlockPiston.java index c3133814f..4f10ca5ad 100644 --- a/src/main/java/net/minecraft/server/BlockPiston.java +++ b/src/main/java/net/minecraft/server/BlockPiston.java @@ -270,7 +270,10 @@ public class BlockPiston extends BlockDirectional { return false; } - return !iblockdata.getBlock().isTileEntity(); + // Tuinity start - pushable TE's + TileEntity tileEntity; + return !iblockdata.getBlock().isTileEntity() || ((tileEntity = world.getTileEntity(blockposition)) != null && tileEntity.isPushable()); + // Tuinity end - pushable TE's } else { return false; } @@ -369,7 +372,7 @@ public class BlockPiston extends BlockDirectional { for (k = list.size() - 1; k >= 0; --k) { // Paper start - fix a variety of piston desync dupes - boolean allowDesync = com.destroystokyo.paper.PaperConfig.allowPistonDuplication; + boolean allowDesync = com.destroystokyo.paper.PaperConfig.allowPistonDuplication && !list1.get(k).getBlock().isTileEntity(); // Tuinity - pushable TE's BlockPosition oldPos = blockposition3 = (BlockPosition) list.get(k); iblockdata1 = allowDesync ? world.getType(oldPos) : null; // Paper end - fix a variety of piston desync dupes @@ -381,10 +384,29 @@ public class BlockPiston extends BlockDirectional { iblockdata1 = world.getType(oldPos); map.replace(oldPos, iblockdata1); } - world.setTileEntity(blockposition3, BlockPistonMoving.a(allowDesync ? list1.get(k) : iblockdata1, enumdirection, flag, false)); + // Tuinity start - pushable TE's + TileEntity tileEntity = world.getTileEntity(oldPos); + if (tileEntity != null) { + if (!tileEntity.isPushable()) { + tileEntity = null; + } else { + // ensure the death of world tied state + if (tileEntity instanceof IInventory) { + MCUtil.closeInventory((IInventory)tileEntity, org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); + } + if (tileEntity instanceof TileEntityLectern) { + MCUtil.closeInventory(((TileEntityLectern)tileEntity).inventory, org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); + } + + // now copy + tileEntity = tileEntity.createCopyForPush((WorldServer)world, oldPos, blockposition3, iblockdata1); + } + } + // Tuinity end - pushable TE's if (!allowDesync) { - world.setTypeAndData(oldPos, Blocks.AIR.getBlockData(), 2 | 4 | 16 | 1024); // set air to prevent later physics updates from seeing this block + world.setTypeAndDataRaw(oldPos, Blocks.AIR.getBlockData(), null); // Tuinity - don't fire logic for removing the old block } + world.setTileEntity(blockposition3, BlockPistonMoving.createPistonTile(allowDesync ? list1.get(k) : iblockdata1, enumdirection, flag, false, tileEntity)); // Tuinity - pushable TE's // Paper end - fix a variety of piston desync dupes aiblockdata[j++] = iblockdata1; } diff --git a/src/main/java/net/minecraft/server/BlockPistonMoving.java b/src/main/java/net/minecraft/server/BlockPistonMoving.java index 4bf66420f..bf76615d7 100644 --- a/src/main/java/net/minecraft/server/BlockPistonMoving.java +++ b/src/main/java/net/minecraft/server/BlockPistonMoving.java @@ -21,7 +21,12 @@ public class BlockPistonMoving extends BlockTileEntity { } public static TileEntity a(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1) { - return new TileEntityPiston(iblockdata, enumdirection, flag, flag1); + // Tuinity start - add tileEntity parameter + return createPistonTile(iblockdata, enumdirection, flag, flag1, null); + } + public static TileEntity createPistonTile(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1, TileEntity tileEntity) { + return new TileEntityPiston(iblockdata, enumdirection, flag, flag1, tileEntity); + // Tuinity end - add tileEntity parameter } @Override diff --git a/src/main/java/net/minecraft/server/BlockPosition.java b/src/main/java/net/minecraft/server/BlockPosition.java index 8c2a4b57a..7ff4948a4 100644 --- a/src/main/java/net/minecraft/server/BlockPosition.java +++ b/src/main/java/net/minecraft/server/BlockPosition.java @@ -386,11 +386,11 @@ public class BlockPosition extends BaseBlockPosition { return super.a(enumblockrotation).immutableCopy(); } - public BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER - public BlockPosition.MutableBlockPosition d(int i, int j, int k) { - this.o(i); - this.p(j); - this.q(k); + public final BlockPosition.MutableBlockPosition setValues(int i, int j, int k) { return d(i, j, k);} // Paper - OBFHELPER // Tuinity + public final BlockPosition.MutableBlockPosition d(int i, int j, int k) { // Tuinity + ((BaseBlockPosition)this).a = i; // Tuinity - force inline + ((BaseBlockPosition)this).b = j; // Tuinity - force inline + ((BaseBlockPosition)this).e = k; // Tuinity - force inline return this; } @@ -400,12 +400,18 @@ public class BlockPosition extends BaseBlockPosition { } public final BlockPosition.MutableBlockPosition setValues(final BaseBlockPosition baseblockposition) { return this.g(baseblockposition); } // Paper - OBFHELPER - public BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { - return this.d(baseblockposition.getX(), baseblockposition.getY(), baseblockposition.getZ()); + public final BlockPosition.MutableBlockPosition g(BaseBlockPosition baseblockposition) { // Tuinity + ((BaseBlockPosition)this).a = baseblockposition.a; // Tuinity - force inline + ((BaseBlockPosition)this).b = baseblockposition.b; // Tuinity - force inline + ((BaseBlockPosition)this).e = baseblockposition.e; // Tuinity - force inline + return this; } - public BlockPosition.MutableBlockPosition g(long i) { - return this.d(b(i), c(i), d(i)); + public final BlockPosition.MutableBlockPosition g(long i) { // Tuinity + ((BaseBlockPosition)this).a = (int)(i >> 38); // Tuinity - force inline + ((BaseBlockPosition)this).b = (int)((i << 52) >> 52); // Tuinity - force inline + ((BaseBlockPosition)this).e = (int)((i << 26) >> 38); // Tuinity - force inline + return this; } public BlockPosition.MutableBlockPosition a(EnumAxisCycle enumaxiscycle, int i, int j, int k) { @@ -420,8 +426,11 @@ public class BlockPosition extends BaseBlockPosition { return this.d(baseblockposition.getX() + i, baseblockposition.getY() + j, baseblockposition.getZ() + k); } - public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) { - return this.c(enumdirection, 1); + public final BlockPosition.MutableBlockPosition c(EnumDirection enumdirection) { // Tuinity + ((BaseBlockPosition)this).a += enumdirection.getAdjacentX(); // Tuinity - force inline + ((BaseBlockPosition)this).b += enumdirection.getAdjacentY(); // Tuinity - force inline + ((BaseBlockPosition)this).e += enumdirection.getAdjacentZ(); // Tuinity - force inline + return this; } public BlockPosition.MutableBlockPosition c(EnumDirection enumdirection, int i) { @@ -445,22 +454,31 @@ public class BlockPosition extends BaseBlockPosition { } } - public final void setX(final int x) { super.o(x); } // Paper - OBFHELPER - @Override - public void o(int i) { - super.o(i); + // Tuinity start + public final void setX(int value) { + ((BaseBlockPosition)this).a = value; + } + public final void setY(int value) { + ((BaseBlockPosition)this).b = value; } + public final void setZ(int value) { + ((BaseBlockPosition)this).e = value; + } + // Tuinity end - public final void setY(final int y) { super.p(y); } // Paper - OBFHELPER - @Override - public void p(int i) { - super.p(i); + // Tuinity - moved up + public final void o(int i) { // Tuinity + ((BaseBlockPosition)this).a = i; // need cast thanks to name conflict } - public final void setZ(final int z) { super.q(z); } // Paper - OBFHELPER - @Override - public void q(int i) { - super.q(i); + // Tuinity - moved up + public final void p(int i) { // Tuinity + ((BaseBlockPosition)this).b = i; // Tuinity + } + + // Tuinity - moved up + public final void q(int i) { // Tuinity + ((BaseBlockPosition)this).e = i; // Tuinity } @Override diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index 84dc89d96..09b8f9a3f 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -375,7 +375,7 @@ public class Chunk implements IChunkAccess { Entry entry = (Entry) iterator.next(); if (ChunkStatus.FULL.h().contains(entry.getKey())) { - this.a((HeightMap.Type) entry.getKey()).a(((HeightMap) entry.getValue()).a()); + this.a((HeightMap.Type) entry.getKey()).copyFrom(((HeightMap) entry.getValue())); // Tuinity } } @@ -510,8 +510,35 @@ public class Chunk implements IChunkAccess { return this.setType(blockposition, iblockdata, flag, true); } + // Tuinity start + final void setTypeAndDataRaw(BlockPosition blockposition, IBlockData iblockdata) { + // copied from setType + int i = blockposition.getX() & 15; + int j = blockposition.getY(); + int k = blockposition.getZ() & 15; + ChunkSection chunksection = this.sections[j >> 4]; + + if (chunksection == Chunk.a) { + if (iblockdata.isAir()) { + return; + } + + chunksection = new ChunkSection(j >> 4 << 4, this, this.world, true); // Paper - Anti-Xray - Add parameters + this.sections[j >> 4] = chunksection; + } + + chunksection.setType(i, j & 15, k, iblockdata); + } + // Tuinity end + @Nullable public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace) { + // Tuinity start - add tileEntity parameter + return this.setType(blockposition, iblockdata, flag, doPlace, null); + } + @Nullable + public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace, TileEntity newTileEntity) { + // Tuinity end - add tileEntity parameter // CraftBukkit end int i = blockposition.getX() & 15; int j = blockposition.getY(); @@ -570,6 +597,10 @@ public class Chunk implements IChunkAccess { } if (block instanceof ITileEntity) { + // Tuinity start - add tileEntity parameter + if (newTileEntity != null) { + this.world.setTileEntity(blockposition, newTileEntity); + } else { // Tuinity end - add tileEntity parameter tileentity = this.a(blockposition, Chunk.EnumTileEntityState.CHECK); if (tileentity == null) { tileentity = ((ITileEntity) block).createTile(this.world); @@ -577,6 +608,7 @@ public class Chunk implements IChunkAccess { } else { tileentity.invalidateBlockCache(); } + } // Tuinity - add tileEntity parameter } this.s = true; @@ -592,6 +624,7 @@ public class Chunk implements IChunkAccess { @Override public void a(Entity entity) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async addEntity call"); // Tuinity this.q = true; int i = MathHelper.floor(entity.locX() / 16.0D); int j = MathHelper.floor(entity.locZ() / 16.0D); @@ -661,6 +694,7 @@ public class Chunk implements IChunkAccess { } public void a(Entity entity, int i) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async removeEntity call"); // Tuinity if (i < 0) { i = 0; } @@ -918,6 +952,7 @@ public class Chunk implements IChunkAccess { } public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async getEntities call"); // Tuinity int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); @@ -957,6 +992,7 @@ public class Chunk implements IChunkAccess { } public void a(@Nullable EntityTypes entitytypes, AxisAlignedBB axisalignedbb, List list, Predicate predicate) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async getEntities call"); // Tuinity int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); @@ -987,6 +1023,7 @@ public class Chunk implements IChunkAccess { } public void a(Class oclass, AxisAlignedBB axisalignedbb, List list, @Nullable Predicate predicate) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async getEntities call"); // Tuinity int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxY + 2.0D) / 16.0D); diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java index 893c0085b..d83d3b54d 100644 --- a/src/main/java/net/minecraft/server/ChunkMapDistance.java +++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java @@ -31,7 +31,7 @@ public abstract class ChunkMapDistance { private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; private final Long2ObjectMap> c = new Long2ObjectOpenHashMap(); public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); - private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); + private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.ticketLevelTracker; } // Tuinity - OBFHELPER public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); // Paper start use a queue, but still keep unique requirement @@ -53,6 +53,47 @@ public abstract class ChunkMapDistance { PlayerChunkMap chunkMap; // Paper + // Tuinity start - delay chunk unloads + private long nextUnloadId; // delay chunk unloads + private final Long2ObjectOpenHashMap> delayedChunks = new Long2ObjectOpenHashMap<>(); + public final void removeTickets(long chunk, TicketType type) { + ArraySetSorted> tickets = this.tickets.get(chunk); + if (tickets == null) { + return; + } + if (type == TicketType.DELAYED_UNLOAD) { + this.delayedChunks.remove(chunk); + } + boolean changed = tickets.removeIf((Ticket ticket) -> { + return ticket.getTicketType() == type; + }); + if (changed) { + this.getTicketTracker().update(chunk, getLowestTicketLevel(tickets), false); + } + } + + private final java.util.function.LongFunction> computeFuntion = (long key) -> { + Ticket ret = new Ticket<>(TicketType.DELAYED_UNLOAD, -1, ++ChunkMapDistance.this.nextUnloadId); + ret.isCached = true; + return ret; + }; + + private void computeDelayedTicketFor(long chunk, int removedLevel, ArraySetSorted> tickets) { + int lowestLevel = getLowestTicketLevel(tickets); + if (removedLevel > lowestLevel) { + return; + } + final Ticket ticket = this.delayedChunks.computeIfAbsent(chunk, this.computeFuntion); + if (ticket.getTicketLevel() != -1) { + // since we modify data used in sorting, we need to remove before + tickets.remove(ticket); + } + ticket.setCreationTick(this.currentTick); + ticket.setTicketLevel(removedLevel); + tickets.add(ticket); // re-add with new expire time and ticket level + } + // Tuinity end - delay chunk unloads + protected ChunkMapDistance(Executor executor, Executor executor1) { executor1.getClass(); Mailbox mailbox = Mailbox.a("player ticket throttler", executor1::execute); @@ -65,15 +106,34 @@ public abstract class ChunkMapDistance { } protected void purgeTickets() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async purge tickets"); // Tuinity ++this.currentTick; ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + int[] tempLevel = new int[] { PlayerChunkMap.GOLDEN_TICKET + 1 }; // Tuinity - delay chunk unloads while (objectiterator.hasNext()) { Entry>> entry = (Entry) objectiterator.next(); if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error - return ticket.b(this.currentTick); + // Tuinity start - delay chunk unloads + boolean ret = ticket.isExpired(this.currentTick); + if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy <= 0) { + return ret; + } + if (ret && ticket.getTicketType() != TicketType.DELAYED_UNLOAD && ticket.getTicketLevel() < tempLevel[0]) { + tempLevel[0] = ticket.getTicketLevel(); + } + if (ticket.getTicketType() == TicketType.DELAYED_UNLOAD && ticket.isCached) { + this.delayedChunks.remove(entry.getLongKey(), ticket); // clean up ticket... + } + return ret; + // Tuinity end - delay chunk unloads })) { + // Tuinity start - delay chunk unloads + if (tempLevel[0] < (PlayerChunkMap.GOLDEN_TICKET + 1)) { + this.computeDelayedTicketFor(entry.getLongKey(), tempLevel[0], entry.getValue()); + } + // Tuinity end - delay chunk unloads this.ticketLevelTracker.update(entry.getLongKey(), getLowestTicketLevel((ArraySetSorted) entry.getValue()), false); } @@ -98,6 +158,7 @@ public abstract class ChunkMapDistance { protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k); public boolean a(PlayerChunkMap playerchunkmap) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot tick ChunkMapDistance off of the main-thread");// Tuinity //this.f.a(); // Paper - no longer used AsyncCatcher.catchOp("DistanceManagerTick"); // Paper this.g.a(); @@ -176,27 +237,11 @@ public abstract class ChunkMapDistance { boolean removed = false; // CraftBukkit if (arraysetsorted.remove(ticket)) { removed = true; // CraftBukkit - // Paper start - delay chunk unloads for player tickets - long delayChunkUnloadsBy = chunkMap.world.paperConfig.delayChunkUnloadsBy; - if (ticket.getTicketType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) { - boolean hasPlayer = false; - for (Ticket ticket1 : arraysetsorted) { - if (ticket1.getTicketType() == TicketType.PLAYER) { - hasPlayer = true; - break; - } - } - PlayerChunk playerChunk = chunkMap.getUpdatingChunk(i); - if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) { - Ticket delayUnload = new Ticket(TicketType.DELAY_UNLOAD, 33, i); - delayUnload.delayUnloadBy = delayChunkUnloadsBy; - delayUnload.setCurrentTick(this.currentTick); - arraysetsorted.remove(delayUnload); - // refresh ticket - arraysetsorted.add(delayUnload); - } + // Tuinity start - delay chunk unloads + if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy > 0 && ticket.getTicketType() != TicketType.DELAYED_UNLOAD) { + this.computeDelayedTicketFor(i, ticket.getTicketLevel(), arraysetsorted); } - // Paper end + // Tuinity end - delay chunk unloads } if (arraysetsorted.isEmpty()) { @@ -370,6 +415,7 @@ public abstract class ChunkMapDistance { } private ArraySetSorted> e(long i) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async tickets compute"); // Tuinity return (ArraySetSorted) this.tickets.computeIfAbsent(i, (j) -> { return ArraySetSorted.a(4); }); @@ -387,6 +433,7 @@ public abstract class ChunkMapDistance { } public void a(SectionPosition sectionposition, EntityPlayer entityplayer) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player add"); // Tuinity long i = sectionposition.r().pair(); ((ObjectSet) this.c.computeIfAbsent(i, (j) -> { @@ -397,6 +444,7 @@ public abstract class ChunkMapDistance { } public void b(SectionPosition sectionposition, EntityPlayer entityplayer) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player remove"); // Tuinity long i = sectionposition.r().pair(); ObjectSet objectset = (ObjectSet) this.c.get(i); @@ -446,6 +494,7 @@ public abstract class ChunkMapDistance { // CraftBukkit start public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket remove"); // Tuinity Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index ef980f985..0459a591a 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -120,7 +120,7 @@ public class ChunkProviderServer extends IChunkProvider { return (Chunk)this.getChunkAt(x, z, ChunkStatus.FULL, true); } - private long chunkFutureAwaitCounter; + long chunkFutureAwaitCounter; // Tuinity - private -> package private public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { if (Thread.currentThread() != this.serverThread) { @@ -182,9 +182,9 @@ public class ChunkProviderServer extends IChunkProvider { try { if (onLoad != null) { - playerChunkMap.callbackExecutor.execute(() -> { + // Tuinity - revert incorrect use of callback executor onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. - }); + // Tuinity - revert incorrect use of callback executor } } catch (Throwable thr) { if (thr instanceof ThreadDeath) { @@ -209,6 +209,164 @@ public class ChunkProviderServer extends IChunkProvider { } // Paper end - rewrite ticklistserver + // Tuinity start + // this will try to avoid chunk neighbours for lighting + public final IChunkAccess getFullStatusChunkAt(int chunkX, int chunkZ) { + Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (ifLoaded != null) { + return ifLoaded; + } + + IChunkAccess empty = this.getChunkAt(chunkX, chunkZ, ChunkStatus.EMPTY, true); + if (empty != null && empty.getChunkStatus() == ChunkStatus.FULL) { + return empty; + } + return this.getChunkAt(chunkX, chunkZ, ChunkStatus.FULL, true); + } + + public final IChunkAccess getFullStatusChunkAtIfLoaded(int chunkX, int chunkZ) { + Chunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (ifLoaded != null) { + return ifLoaded; + } + + IChunkAccess ret = this.getChunkAtImmediately(chunkX, chunkZ); + if (ret != null && ret.getChunkStatus() == ChunkStatus.FULL) { + return ret; + } else { + return null; + } + } + + void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel, + java.util.function.Consumer consumer) { + this.getChunkAtAsynchronously(chunkX, chunkZ, ticketLevel, (PlayerChunk playerChunk) -> { + if (ticketLevel <= 33) { + return (CompletableFuture)playerChunk.getFullChunkFuture(); + } else { + return playerChunk.getOrCreateFuture(PlayerChunk.getChunkStatus(ticketLevel), ChunkProviderServer.this.playerChunkMap); + } + }, consumer); + } + + void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel, + java.util.function.Function>> function, + java.util.function.Consumer consumer) { + if (Thread.currentThread() != this.serverThread) { + throw new IllegalStateException(); + } + ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(chunkX, chunkZ); + Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++); + this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); + this.tickDistanceManager(); + + PlayerChunk chunk = this.playerChunkMap.getUpdatingChunk(chunkPos.pair()); + + if (chunk == null) { + throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.world.getWorld().getName() + "'"); + } + + CompletableFuture> future = function.apply(chunk); + + future.whenCompleteAsync((either, throwable) -> { + try { + if (throwable != null) { + if (throwable instanceof ThreadDeath) { + throw (ThreadDeath)throwable; + } + MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", throwable); + } else if (either.right().isPresent()) { + MinecraftServer.LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "': " + either.right().get().toString()); + } + + try { + if (consumer != null) { + consumer.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. + } + } catch (Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + MinecraftServer.LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ChunkProviderServer.this.world.getWorld().getName() + "'", thr); + return; + } + } finally { + // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. + ChunkProviderServer.this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); + ChunkProviderServer.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); + } + }, this.serverThreadQueue); + } + + void chunkLoadAccept(int chunkX, int chunkZ, IChunkAccess chunk, java.util.function.Consumer consumer) { + try { + consumer.accept(chunk); + } catch (Throwable throwable) { + if (throwable instanceof ThreadDeath) { + throw (ThreadDeath)throwable; + } + MinecraftServer.LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.world.getWorld().getName() + "' threw an exception", throwable); + } + } + + public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer onLoad) { + // try to fire sync + int chunkStatusTicketLevel = 33 + ChunkStatus.getTicketLevelOffset(status); + IChunkAccess immediate = this.getChunkAtImmediately(chunkX, chunkZ); + if (immediate != null) { + if (allowSubTicketLevel || this.playerChunkMap.getUpdatingChunk(MCUtil.getCoordinateKey(chunkX, chunkZ)).getTicketLevel() <= chunkStatusTicketLevel) { + if (immediate.getChunkStatus().isAtLeastStatus(status)) { + this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad); + } else { + if (gen) { + this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); + } else { + this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); + } + } + } else { + if (gen || immediate.getChunkStatus().isAtLeastStatus(status)) { + this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); + } else { + this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); + } + } + return; + } + + // need to fire async + + if (gen && !allowSubTicketLevel) { + this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); + return; + } + + this.getChunkAtAsynchronously(chunkX, chunkZ, 33 + ChunkStatus.getTicketLevelOffset(ChunkStatus.EMPTY), (IChunkAccess chunk) -> { + if (chunk == null) { + throw new IllegalStateException("Chunk cannot be null"); + } + + if (!chunk.getChunkStatus().isAtLeastStatus(status)) { + if (gen) { + this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); + return; + } else { + ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); + return; + } + } else { + if (allowSubTicketLevel) { + ChunkProviderServer.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad); + return; + } else { + this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); + return; + } + } + }); + } + // Tuinity end + public ChunkProviderServer(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, boolean flag, WorldLoadListener worldloadlistener, Supplier supplier) { this.world = worldserver; this.serverThreadQueue = new ChunkProviderServer.a(worldserver); @@ -544,6 +702,8 @@ public class ChunkProviderServer extends IChunkProvider { Arrays.fill(this.cacheChunk, (Object) null); } + private long syncLoadCounter; // Tuinity - prevent plugin unloads from removing our ticket + private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) { // Paper start - add isUrgent - old sig left in place for dirty nms plugins return getChunkFutureMainThread(i, j, chunkstatus, flag, false); @@ -562,9 +722,12 @@ public class ChunkProviderServer extends IChunkProvider { PlayerChunk.State currentChunkState = PlayerChunk.getChunkState(playerchunk.getTicketLevel()); currentlyUnloading = (oldChunkState.isAtLeast(PlayerChunk.State.BORDER) && !currentChunkState.isAtLeast(PlayerChunk.State.BORDER)); } + final Long identifier; // Tuinity - prevent plugin unloads from removing our ticket if (flag && !currentlyUnloading) { // CraftBukkit end this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); + identifier = Long.valueOf(this.syncLoadCounter++); // Tuinity - prevent plugin unloads from removing our ticket + this.chunkMapDistance.addTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity - prevent plugin unloads from removing our ticket if (isUrgent) this.chunkMapDistance.markUrgent(chunkcoordintpair); // Paper if (this.a(playerchunk, l)) { GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); @@ -575,12 +738,20 @@ public class ChunkProviderServer extends IChunkProvider { playerchunk = this.getChunk(k); gameprofilerfiller.exit(); if (this.a(playerchunk, l)) { + this.chunkMapDistance.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity throw (IllegalStateException) SystemUtils.c(new IllegalStateException("No chunk holder after ticket has been added")); } } - } + } else { identifier = null; } // Tuinity - prevent plugin unloads from removing our ticket // Paper start CompletableFuture> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap); + // Tuinity start - prevent plugin unloads from removing our ticket + if (flag && !currentlyUnloading) { + future.thenAcceptAsync((either) -> { + ChunkProviderServer.this.chunkMapDistance.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); + }, ChunkProviderServer.this.serverThreadQueue); + } + // Tuinity end - prevent plugin unloads from removing our ticket if (isUrgent) { future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair)); } @@ -599,8 +770,8 @@ public class ChunkProviderServer extends IChunkProvider { return !this.a(playerchunk, k); } - @Override - public IBlockAccess c(int i, int j) { + public final IBlockAccess getFeaturesReadyChunk(int x, int z) { return this.c(x, z); } // Tuinity - OBFHELPER + @Override public IBlockAccess c(int i, int j) { // Tuinity - OBFHELPER long k = ChunkCoordIntPair.pair(i, j); PlayerChunk playerchunk = this.getChunk(k); @@ -734,7 +905,7 @@ public class ChunkProviderServer extends IChunkProvider { this.world.getMethodProfiler().enter("purge"); this.world.timings.doChunkMap.startTiming(); // Spigot this.chunkMapDistance.purgeTickets(); - this.world.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic this.tickDistanceManager(); this.world.timings.doChunkMap.stopTiming(); // Spigot this.world.getMethodProfiler().exitEnter("chunks"); @@ -744,12 +915,22 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.doChunkUnload.startTiming(); // Spigot this.world.getMethodProfiler().exitEnter("unload"); this.playerChunkMap.unloadChunks(booleansupplier); - this.world.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic this.world.timings.doChunkUnload.stopTiming(); // Spigot this.world.getMethodProfiler().exit(); this.clearCache(); } + // Tuinity start - optimise chunk tick iteration + // We need this here because since we remove the COW op for chunk map, we also remove + // the iterator safety of the visible map - meaning the only way for us to still + // iterate is to use a copy. Not acceptable at all, so here we hack in an iterable safe + // chunk map that will give the same behaviour as previous - without COW. + final com.destroystokyo.paper.util.maplist.ChunkList entityTickingChunks = new com.destroystokyo.paper.util.maplist.ChunkList(); + boolean isTickingChunks; + final it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap pendingEntityTickingChunkChanges = new it.unimi.dsi.fastutil.objects.Object2BooleanLinkedOpenHashMap<>(16, 0.8f); + // Tuinity end - optimise chunk tick iteration + private void tickChunks() { long i = this.world.getTime(); long j = i - this.lastTickTime; @@ -820,19 +1001,21 @@ public class ChunkProviderServer extends IChunkProvider { //List list = Lists.newArrayList(this.playerChunkMap.f()); // Paper //Collections.shuffle(list); // Paper // Paper - moved up - final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping - Optional optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); - - if (optional.isPresent()) { + // Tuinity start - optimise chunk tick iteration + this.isTickingChunks = true; + for (Chunk chunk : this.entityTickingChunks) { + PlayerChunk playerchunk = chunk.playerChunk; + if (playerchunk != null) { // make sure load event has been called along with the load logic we put there + // Tuinity end - optimise chunk tick iteration this.world.getMethodProfiler().enter("broadcast"); this.world.timings.broadcastChunkUpdates.startTiming(); // Paper - timings - playerchunk.a((Chunk) optional.get()); + playerchunk.a(chunk); // Tuinity this.world.timings.broadcastChunkUpdates.stopTiming(); // Paper - timings this.world.getMethodProfiler().exit(); - Optional optional1 = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); + // Tuinity - if (optional1.isPresent()) { - Chunk chunk = (Chunk) optional1.get(); + if (true) { // Tuinity + // Tuinity ChunkCoordIntPair chunkcoordintpair = playerchunk.i(); if (!this.playerChunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange @@ -844,11 +1027,27 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.chunkTicks.startTiming(); // Spigot // Paper this.world.a(chunk, k); this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper - if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper + MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick } } } - }); + } // Tuinity start - optimise chunk tick iteration + this.isTickingChunks = false; + if (!this.pendingEntityTickingChunkChanges.isEmpty()) { + // iterate backwards: fastutil maps have better remove times when iterating backwards + // (this is due to the fact that we likely wont shift entries on remove calls) + for (it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator> iterator = this.pendingEntityTickingChunkChanges.object2BooleanEntrySet().fastIterator(this.pendingEntityTickingChunkChanges.object2BooleanEntrySet().last()); iterator.hasPrevious();) { + it.unimi.dsi.fastutil.objects.Object2BooleanMap.Entry entry = iterator.previous(); + + if (entry.getBooleanValue()) { + this.entityTickingChunks.add(entry.getKey()); + } else { + this.entityTickingChunks.remove(entry.getKey()); + } + iterator.remove(); + } + } + // Tuinity end - optimise chunk tick iteration this.world.getMethodProfiler().enter("customSpawners"); if (flag1) { try (co.aikar.timings.Timing ignored = this.world.timings.miscMobSpawning.startTiming()) { // Paper - timings @@ -860,7 +1059,25 @@ public class ChunkProviderServer extends IChunkProvider { this.world.getMethodProfiler().exit(); } + // Tuinity start - controlled flush for entity tracker packets + List disabledFlushes = new java.util.ArrayList<>(this.world.getPlayers().size()); + for (EntityPlayer player : this.world.getPlayers()) { + PlayerConnection connection = player.playerConnection; + if (connection != null) { + connection.networkManager.disableAutomaticFlush(); + disabledFlushes.add(connection.networkManager); + } + } + try { + // Tuinity end - controlled flush for entity tracker packets this.playerChunkMap.g(); + // Tuinity start - controlled flush for entity tracker packets + } finally { + for (NetworkManager networkManager : disabledFlushes) { + networkManager.enableAutomaticFlush(); + } + } + // Tuinity end - controlled flush for entity tracker packets } private void a(long i, Consumer consumer) { @@ -1000,44 +1217,11 @@ public class ChunkProviderServer extends IChunkProvider { ChunkProviderServer.this.world.getMethodProfiler().c("runTask"); super.executeTask(runnable); } - - // Paper start - private long lastMidTickChunkTask = 0; - public boolean pollChunkLoadTasks() { - if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask()) { - try { - ChunkProviderServer.this.tickDistanceManager(); - } finally { - // from below: process pending Chunk loadCallback() and unloadCallback() after each run task - playerChunkMap.callbackExecutor.run(); - } - return true; - } - return false; - } - public void midTickLoadChunks() { - MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer(); - // always try to load chunks, restrain generation/other updates only. don't count these towards tick count - //noinspection StatementWithEmptyBody - while (pollChunkLoadTasks()) {} - - if (System.nanoTime() - lastMidTickChunkTask < 200000) { - return; - } - - for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.canSleepForTick();) { - if (this.executeNext()) { - server.midTickChunksTasksRan++; - lastMidTickChunkTask = System.nanoTime(); - } else { - break; - } - } - } - // Paper end + // Tuinity - replace logic @Override protected boolean executeNext() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot execute chunk tasks off-main thread");// Tuinity // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task try { boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index ac58fcb79..742c59cb0 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -24,6 +24,14 @@ public class ChunkRegionLoader { private static final Logger LOGGER = LogManager.getLogger(); + // Tuinity start + // TODO: Check on update + public static long getLastWorldSaveTime(NBTTagCompound chunkData) { + NBTTagCompound levelData = chunkData.getCompound("Level"); + return levelData.getLong("LastUpdate"); + } + // Tuinity end + // Paper start - guard against serializing mismatching coordinates // TODO Note: This needs to be re-checked each update public static ChunkCoordIntPair getChunkCoordinate(NBTTagCompound chunkData) { @@ -374,10 +382,10 @@ public class ChunkRegionLoader { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); nbttagcompound.setInt("DataVersion", SharedConstants.getGameVersion().getWorldVersion()); - nbttagcompound.set("Level", nbttagcompound1); + nbttagcompound.set("Level", nbttagcompound1); // Tuinity - diff on change nbttagcompound1.setInt("xPos", chunkcoordintpair.x); nbttagcompound1.setInt("zPos", chunkcoordintpair.z); - nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading + nbttagcompound1.setLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : worldserver.getTime()); // Paper - async chunk unloading // Tuinity - diff on change nbttagcompound1.setLong("InhabitedTime", ichunkaccess.getInhabitedTime()); nbttagcompound1.setString("Status", ichunkaccess.getChunkStatus().d()); ChunkConverter chunkconverter = ichunkaccess.p(); diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java index 8d45588ec..4ab741985 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -96,6 +96,7 @@ public class ChunkSection { return iblockdata1; } + public final boolean isFullOfAir() { return this.c(); } // Tuinity - OBFHELPER public boolean c() { return this.nonEmptyBlockCount == 0; } diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java index 36c265122..dd8a3dc4f 100644 --- a/src/main/java/net/minecraft/server/ChunkStatus.java +++ b/src/main/java/net/minecraft/server/ChunkStatus.java @@ -109,7 +109,7 @@ public class ChunkStatus { private final ChunkStatus.c w; private final int x; private final ChunkStatus.Type y; - private final EnumSet z; + private final EnumSet z; public final HeightMap.Type[] heightMaps; // Tuinity private static CompletableFuture> a(ChunkStatus chunkstatus, LightEngineThreaded lightenginethreaded, IChunkAccess ichunkaccess) { boolean flag = a(chunkstatus, ichunkaccess); @@ -171,7 +171,7 @@ public class ChunkStatus { this.w = chunkstatus_c; this.x = i; this.y = chunkstatus_type; - this.z = enumset; + this.z = enumset; this.heightMaps = new java.util.ArrayList<>(this.z).toArray(new HeightMap.Type[0]); // Tuinity this.t = chunkstatus == null ? 0 : chunkstatus.c() + 1; } diff --git a/src/main/java/net/minecraft/server/CriterionTriggerAbstract.java b/src/main/java/net/minecraft/server/CriterionTriggerAbstract.java index 2419c57f9..c61ee03ef 100644 --- a/src/main/java/net/minecraft/server/CriterionTriggerAbstract.java +++ b/src/main/java/net/minecraft/server/CriterionTriggerAbstract.java @@ -12,25 +12,25 @@ import java.util.function.Predicate; public abstract class CriterionTriggerAbstract implements CriterionTrigger { - private final Map>> a = Maps.newIdentityHashMap(); + //private final Map>> a = Maps.newIdentityHashMap(); // Tuinity - moved into AdvancementDataPlayer to fix memory leak public CriterionTriggerAbstract() {} @Override public final void a(AdvancementDataPlayer advancementdataplayer, CriterionTrigger.a criteriontrigger_a) { - ((Set) this.a.computeIfAbsent(advancementdataplayer, (advancementdataplayer1) -> { + (advancementdataplayer.criterionData.computeIfAbsent(this, (advancementdataplayer1) -> { // Tuinity - fix AdvancementDataPlayer leak return Sets.newHashSet(); })).add(criteriontrigger_a); } @Override public final void b(AdvancementDataPlayer advancementdataplayer, CriterionTrigger.a criteriontrigger_a) { - Set> set = (Set) this.a.get(advancementdataplayer); + Set> set = (Set) advancementdataplayer.criterionData.get(this); // Tuinity - fix AdvancementDataPlayer leak if (set != null) { set.remove(criteriontrigger_a); if (set.isEmpty()) { - this.a.remove(advancementdataplayer); + advancementdataplayer.criterionData.remove(this); // Tuinity - fix AdvancementDataPlayer leak } } @@ -38,7 +38,7 @@ public abstract class CriterionTriggerAbstract predicate) { AdvancementDataPlayer advancementdataplayer = entityplayer.getAdvancementData(); - Set> set = (Set) this.a.get(advancementdataplayer); + Set> set = (Set) advancementdataplayer.criterionData.get(this); // Tuinity - fix AdvancementDataPlayer leak if (set != null && !set.isEmpty()) { LootTableInfo loottableinfo = CriterionConditionEntity.b(entityplayer, entityplayer); @@ -63,7 +63,7 @@ public abstract class CriterionTriggerAbstract> 32 >> this.i); } + public final int getAndSet(final int index, final int value) { return this.a(index, value); } // Tuinity - OBFHELPER public int a(int i, int j) { //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper @@ -64,6 +65,7 @@ public class DataBits { return j1; } + public final void set(final int index, final int value) { this.b(index, value); } // Tuinity - OBFHELPER public void b(int i, int j) { //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper //Validate.inclusiveBetween(0L, this.d, (long) j); // Paper @@ -74,6 +76,7 @@ public class DataBits { this.b[k] = l & ~(this.d << i1) | ((long) j & this.d) << i1; } + public final int get(final int index) { return this.a(index); } // Tuinity - OBFHELPER public int a(int i) { //Validate.inclusiveBetween(0L, (long) (this.e - 1), (long) i); // Paper int j = this.b(i); diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java index 1cb45f97b..e60ad41b2 100644 --- a/src/main/java/net/minecraft/server/DataPaletteBlock.java +++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java @@ -163,6 +163,7 @@ public class DataPaletteBlock implements DataPaletteExpandable { return this.a(j << 8 | k << 4 | i); // Paper - inline } + public final T rawGet(int index) { return this.a(index); } // Tuinity - OBFHELPER protected T a(int i) { T t0 = this.h.a(this.a.a(i)); diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java index 8b2755a3b..578f7809c 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -170,6 +170,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer com.destroystokyo.paper.PaperConfig.registerCommands(); com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now // Paper end + com.tuinity.tuinity.config.TuinityConfig.init((java.io.File) options.valueOf("tuinity-settings")); // Tuinity - Server Config this.setPVP(dedicatedserverproperties.pvp); this.setAllowFlight(dedicatedserverproperties.allowFlight); diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java index 550232cb3..229c3b0f0 100644 --- a/src/main/java/net/minecraft/server/EULA.java +++ b/src/main/java/net/minecraft/server/EULA.java @@ -70,7 +70,7 @@ public class EULA { Properties properties = new Properties(); properties.setProperty("eula", "false"); - properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; + properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); // Paper - fix lag; // Tuinity - Tacos are disgusting } catch (Throwable throwable1) { throwable = throwable1; throw throwable1; diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 9c4b02d77..3db19a9ba 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -136,7 +136,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke public double D; public double E; public double F; - public float G; + public float G; public final float getStepHeight() { return this.G; } // Tuinity - OBFHELPER public boolean noclip; public float I; protected final Random random; @@ -212,6 +212,14 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // CraftBukkit end + // Tuinity start + public final AxisAlignedBB getBoundingBoxAt(double x, double y, double z) { + double widthHalf = (double)this.size.width / 2.0; + double height = (double)this.size.height; + return new AxisAlignedBB(x - widthHalf, y, z - widthHalf, x + widthHalf, y + height, z + widthHalf); + } + // Tuinity end + // Paper start /** * Overriding this field will cause memory leaks. @@ -625,7 +633,40 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return this.onGround; } + // Tuinity start - detailed watchdog information + private Vec3D moveVector; + private double moveStartX; + private double moveStartY; + private double moveStartZ; + + public final Vec3D getMoveVector() { + return this.moveVector; + } + + public final double getMoveStartX() { + return this.moveStartX; + } + + public final double getMoveStartY() { + return this.moveStartY; + } + + public final double getMoveStartZ() { + return this.moveStartZ; + } + // Tuinity end - detailed watchdog information + public void move(EnumMoveType enummovetype, Vec3D vec3d) { + // Tuinity start - detailed watchdog information + com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot move an entity off-main"); + synchronized (this.posLock) { + this.moveStartX = this.locX(); + this.moveStartY = this.locY(); + this.moveStartZ = this.locZ(); + this.moveVector = vec3d; + } + try { + // Tuinity end - detailed watchdog information if (this.noclip) { this.a(this.getBoundingBox().c(vec3d)); this.recalcPosition(); @@ -653,7 +694,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke // Paper end vec3d = this.a(vec3d, enummovetype); - Vec3D vec3d1 = this.f(vec3d); + Vec3D vec3d1 = this.performCollision(vec3d); // Tuinity - optimise collisions if (vec3d1.g() > 1.0E-7D) { this.a(this.getBoundingBox().c(vec3d1)); @@ -770,6 +811,13 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.world.getMethodProfiler().exit(); } + // Tuinity start - detailed watchdog information + } finally { + synchronized (this.posLock) { // Tuinity + this.moveVector = null; + } // Tuinity + } + // Tuinity end - detailed watchdog information } protected BlockPosition ak() { @@ -850,6 +898,132 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return d0; } + // Tuinity start - optimise entity movement + private static double performCollisionsX(AxisAlignedBB currentBoundingBox, double value, List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < 1.0E-7) { + return 0.0; + } + AxisAlignedBB target = potentialCollisions.get(i); + value = target.collideX(currentBoundingBox, value); + } + + return value; + } + + private static double performCollisionsY(AxisAlignedBB currentBoundingBox, double value, List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < 1.0E-7) { + return 0.0; + } + AxisAlignedBB target = potentialCollisions.get(i); + value = target.collideY(currentBoundingBox, value); + } + + return value; + } + + private static double performCollisionsZ(AxisAlignedBB currentBoundingBox, double value, List potentialCollisions) { + for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { + if (Math.abs(value) < 1.0E-7) { + return 0.0; + } + AxisAlignedBB target = potentialCollisions.get(i); + value = target.collideZ(currentBoundingBox, value); + } + + return value; + } + + private static Vec3D performCollisions(Vec3D moveVector, AxisAlignedBB axisalignedbb, List potentialCollisions) { + double x = moveVector.x; + double y = moveVector.y; + double z = moveVector.z; + + if (y != 0.0) { + y = Entity.performCollisionsY(axisalignedbb, y, potentialCollisions); + if (y != 0.0) { + axisalignedbb = axisalignedbb.offsetY(y); + } + } + + boolean xSmaller = Math.abs(x) < Math.abs(z); + + if (xSmaller && z != 0.0) { + z = Entity.performCollisionsZ(axisalignedbb, z, potentialCollisions); + if (z != 0.0) { + axisalignedbb = axisalignedbb.offsetZ(z); + } + } + + if (x != 0.0) { + x = Entity.performCollisionsX(axisalignedbb, x, potentialCollisions); + if (!xSmaller && x != 0.0) { + axisalignedbb = axisalignedbb.offsetX(x); + } + } + + if (!xSmaller && z != 0.0) { + z = Entity.performCollisionsZ(axisalignedbb, z, potentialCollisions); + } + + return new Vec3D(x, y, z); + } + + Vec3D performCollision(Vec3D moveVector) { + if (moveVector.getX() == 0.0 && moveVector.getY() == 0.0 && moveVector.getZ() == 0.0) { + return moveVector; + } + + WorldServer world = ((WorldServer)this.world); + AxisAlignedBB currBoundingBox = this.getBoundingBox(); + + List potentialCollisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList(); + try { + AxisAlignedBB collisionBox; + double stepHeight = (double)this.getStepHeight(); + if (stepHeight > 0.0 && (this.onGround || (moveVector.y < 0.0)) && (moveVector.x != 0.0 || moveVector.z != 0.0)) { + // don't bother getting the collisions if we don't need them. + if (moveVector.y <= 0.0) { + collisionBox = currBoundingBox.expand(moveVector.x, moveVector.y, moveVector.z).expandUpwards(stepHeight); + } else { + collisionBox = currBoundingBox.expand(moveVector.x, Math.max(stepHeight, moveVector.y), moveVector.z); + } + } else { + collisionBox = currBoundingBox.expand(moveVector.x, moveVector.y, moveVector.z); + } + world.getCollisions(this, collisionBox, potentialCollisions, false); + + Vec3D limitedMoveVector = Entity.performCollisions(moveVector, currBoundingBox, potentialCollisions); + + if (stepHeight > 0.0 + && (this.onGround || (limitedMoveVector.y != moveVector.y && moveVector.y < 0.0)) + && (limitedMoveVector.x != moveVector.x || limitedMoveVector.z != moveVector.z)) { + Vec3D vec3d2 = Entity.performCollisions(new Vec3D(moveVector.x, stepHeight, moveVector.z), currBoundingBox, potentialCollisions); + Vec3D vec3d3 = Entity.performCollisions(new Vec3D(0.0, stepHeight, 0.0), currBoundingBox.expand(moveVector.x, 0.0, moveVector.z), potentialCollisions); + + if (vec3d3.y < stepHeight) { + Vec3D vec3d4 = Entity.performCollisions(new Vec3D(moveVector.x, 0.0D, moveVector.z), currBoundingBox.offset(vec3d3), potentialCollisions); + + if (Entity.getXZSquared(vec3d4) > Entity.getXZSquared(vec3d2)) { + vec3d2 = vec3d4; + } + } + + if (Entity.getXZSquared(vec3d2) > Entity.getXZSquared(limitedMoveVector)) { + return vec3d2.add(Entity.performCollisions(new Vec3D(0.0D, -vec3d2.y + moveVector.y, 0.0D), currBoundingBox.offset(vec3d2), potentialCollisions)); + } + + return limitedMoveVector; + } else { + return limitedMoveVector; + } + } finally { + com.tuinity.tuinity.util.CachedLists.returnTempCollisionList(potentialCollisions); + } + } + // Tuinity end - optimise entity movement + private Vec3D f(Vec3D vec3d) { AxisAlignedBB axisalignedbb = this.getBoundingBox(); VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this); @@ -885,6 +1059,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return vec3d1; } + public static double getXZSquared(Vec3D vec3d) { return Entity.b(vec3d); } // Tuinity - OBFHELPER public static double b(Vec3D vec3d) { return vec3d.x * vec3d.x + vec3d.z * vec3d.z; } @@ -1091,6 +1266,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke @Nullable public final AxisAlignedBB getCollisionBox(){return ay();} //Paper - OBFHELPER + @Nullable public final AxisAlignedBB getHardCollisionBox() { return this.ay(); } // Tuinity - OBFHELPER @Nullable public AxisAlignedBB ay() { return null; } @@ -1974,8 +2150,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } public final AxisAlignedBB getHardCollisionBox(Entity entity){ return j(entity);}//Paper - OBFHELPER - @Nullable - public AxisAlignedBB j(Entity entity) { + @Nullable public AxisAlignedBB getHardCollisionBoxWith(Entity entity) { return this.j(entity); } // Tuinity - OBFHELPER + @Nullable public AxisAlignedBB j(Entity entity) { // Tuinity - OBFHELPER return null; } @@ -3299,12 +3475,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return this.locBlock; } + public final Object posLock = new Object(); // Tuinity - log detailed entity tick information + public Vec3D getMot() { return this.mot; } public void setMot(Vec3D vec3d) { + synchronized (this.posLock) { // Tuinity this.mot = vec3d; + } // Tuinity } public void setMot(double d0, double d1, double d2) { @@ -3361,7 +3541,9 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // Paper end if (this.loc.x != d0 || this.loc.y != d1 || this.loc.z != d2) { + synchronized (this.posLock) { // Tuinity this.loc = new Vec3D(d0, d1, d2); + } // Tuinity int i = MathHelper.floor(d0); int j = MathHelper.floor(d1); int k = MathHelper.floor(d2); diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java index c388999d1..4545bd371 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -2835,7 +2835,11 @@ public abstract class EntityLiving extends Entity { return; } // Paper - end don't run getEntities if we're not going to use its result - List list = this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.a(this)); + // Tuinity start - reduce memory allocation from collideNearby + List list = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); + this.world.getEntities(this, this.getBoundingBox(), IEntitySelector.a(this), list); + try { + // Tuinity end - reduce memory allocation from collideNearby if (!list.isEmpty()) { // Paper - move up @@ -2864,6 +2868,9 @@ public abstract class EntityLiving extends Entity { this.C(entity); } } + } finally { // Tuinity start - reduce memory allocation from collideNearby + com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(list); + } // Tuinity end - reduce memory allocation from collideNearby } diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index f75c09d44..bfb931268 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -74,6 +74,7 @@ public class EntityTrackerEntry { public final void tick() { this.a(); } // Paper - OBFHELPER public void a() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity List list = this.tracker.getPassengers(); if (!list.equals(this.p)) { diff --git a/src/main/java/net/minecraft/server/Fluid.java b/src/main/java/net/minecraft/server/Fluid.java index 05fa52c0b..8ffc5db50 100644 --- a/src/main/java/net/minecraft/server/Fluid.java +++ b/src/main/java/net/minecraft/server/Fluid.java @@ -9,8 +9,12 @@ public final class Fluid extends IBlockDataHolder { public static final Codec a = a((Codec) IRegistry.FLUID, FluidType::h).stable(); + // Tuinity start + protected final boolean isEmpty; + // Tuinity end public Fluid(FluidType fluidtype, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { super(fluidtype, immutablemap, mapcodec); + this.isEmpty = fluidtype.b(); // Tuinity - moved from isEmpty() } public FluidType getType() { @@ -22,7 +26,7 @@ public final class Fluid extends IBlockDataHolder { } public boolean isEmpty() { - return this.getType().b(); + return this.isEmpty; // Tuinity - moved into constructor } public float getHeight(IBlockAccess iblockaccess, BlockPosition blockposition) { diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java index 068b92c5c..a43c4ca3e 100644 --- a/src/main/java/net/minecraft/server/HeightMap.java +++ b/src/main/java/net/minecraft/server/HeightMap.java @@ -19,7 +19,25 @@ public class HeightMap { private static final Predicate b = (iblockdata) -> { return iblockdata.getMaterial().isSolid(); }; - private final DataBits c = new DataBits(9, 256); + // Tuinity start + private final char[] heightmap = new char[16 * 16]; // Tuinity - replace with faster access + public DataBits toDataBits() { + final DataBits ret = new DataBits(9, 256); + + for (int i = 0, len = this.heightmap.length; i < len; ++i) { + ret.set(i, this.heightmap[i]); + } + + return ret; + } + + public void copyFrom(HeightMap other) { + if (other.heightmap.length != this.heightmap.length) { + throw new IllegalStateException("Heightmap lengths must match"); + } + System.arraycopy(other.heightmap, 0, this.heightmap, 0, this.heightmap.length); + } + // Tuinity end private final Predicate d; private final IChunkAccess e; @@ -101,24 +119,30 @@ public class HeightMap { } } + public final int get(int x, int z) { return this.a(x, z); } // Tuinity - OBFHELPER public int a(int i, int j) { return this.a(c(i, j)); } private int a(int i) { - return this.c.a(i); + return this.heightmap[i]; // Tuinity } private void a(int i, int j, int k) { - this.c.b(c(i, j), k); + this.heightmap[c(i, j)] = (char)k; // Tuinity } public void a(long[] along) { - System.arraycopy(along, 0, this.c.a(), 0, along.length); + // Tuinity start + final DataBits databits = new DataBits(9, 256, along); + for (int i = 0, len = this.heightmap.length; i < len; ++i) { + this.heightmap[i] = (char)databits.get(i); + } + // Tuinity end } public long[] a() { - return this.c.a(); + return this.toDataBits().a(); // Tuinity } private static int c(int i, int j) { @@ -137,7 +161,7 @@ public class HeightMap { private final String h; private final HeightMap.Use i; private final Predicate j; - private static final Map k = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { + private static final Map k = (Map) SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Tuinity - decompile fix HeightMap.Type[] aheightmap_type = values(); int i = aheightmap_type.length; @@ -149,7 +173,7 @@ public class HeightMap { }); - private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { + private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) { // Tuinity - decompile fix this.h = s; this.i = heightmap_use; this.j = predicate; diff --git a/src/main/java/net/minecraft/server/IBlockData.java b/src/main/java/net/minecraft/server/IBlockData.java index 10a5901db..911750476 100644 --- a/src/main/java/net/minecraft/server/IBlockData.java +++ b/src/main/java/net/minecraft/server/IBlockData.java @@ -8,6 +8,19 @@ public class IBlockData extends BlockBase.BlockData { public static final Codec b = a((Codec) IRegistry.BLOCK, Block::getBlockData).stable(); + + // Tuinity start - optimise getType calls + org.bukkit.Material cachedMaterial; + + public final org.bukkit.Material getBukkitMaterial() { + if (this.cachedMaterial == null) { + this.cachedMaterial = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getBlock()); + } + + return this.cachedMaterial; + } + // Tuinity end - optimise getType calls + public IBlockData(Block block, ImmutableMap, Comparable> immutablemap, MapCodec mapcodec) { super(block, immutablemap, mapcodec); } diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java index 582a5695b..5601088cd 100644 --- a/src/main/java/net/minecraft/server/IChunkLoader.java +++ b/src/main/java/net/minecraft/server/IChunkLoader.java @@ -21,7 +21,7 @@ public class IChunkLoader implements AutoCloseable { protected final RegionFileCache regionFileCache; public IChunkLoader(File file, DataFixer datafixer, boolean flag) { - this.regionFileCache = new RegionFileCache(file, flag); // Paper - nuke IOWorker + this.regionFileCache = new RegionFileCache(file, flag, true); // Paper - nuke IOWorker // Tuinity this.b = datafixer; // Paper - nuke IOWorker } diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java index 1cc40b1f0..3ce2f7497 100644 --- a/src/main/java/net/minecraft/server/ICollisionAccess.java +++ b/src/main/java/net/minecraft/server/ICollisionAccess.java @@ -46,6 +46,11 @@ public interface ICollisionAccess extends IBlockAccess { } default boolean b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { + // Tuinity start - allow overriding in WorldServer + return this.getCubes(entity, axisalignedbb, predicate); + } + default boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { + // Tuinity end - allow overriding in WorldServer try { if (entity != null) entity.collisionLoadChunks = true; // Paper return this.d(entity, axisalignedbb, predicate).allMatch(VoxelShape::isEmpty); } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java index 267a6baae..0edcb775e 100644 --- a/src/main/java/net/minecraft/server/IEntityAccess.java +++ b/src/main/java/net/minecraft/server/IEntityAccess.java @@ -69,6 +69,7 @@ public interface IEntityAccess { AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D); // Paper start + if (predicate == null) predicate = (e) -> true; // Tuinity - allow nullable Predicate effectivePredicate = predicate.and((entity1) -> { return entity == null || !entity.isSameVehicle(entity1); }); diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java index b98e60772..e0bbfe142 100644 --- a/src/main/java/net/minecraft/server/LightEngineStorage.java +++ b/src/main/java/net/minecraft/server/LightEngineStorage.java @@ -23,7 +23,8 @@ public abstract class LightEngineStorage> e protected final M f; protected final M updating; // Paper - diff on change, should be "updating" protected final LongSet g = new LongOpenHashSet(); protected final LongSet h = new LongOpenHashSet(); LongSet dirty = h; // Paper - OBFHELPER - protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap()); + protected final Long2ObjectOpenHashMap i_synchronized_map_real = new Long2ObjectOpenHashMap<>(); // Tuinity - store wrapped map, we need fastIterator + protected final Long2ObjectMap i = Long2ObjectMaps.synchronize(this.i_synchronized_map_real); // Tuinity - store wrapped map, we need fastIterator private final LongSet n = new LongOpenHashSet(); private final LongSet o = new LongOpenHashSet(); private final LongSet p = new LongOpenHashSet(); @@ -247,7 +248,7 @@ public abstract class LightEngineStorage> e this.p.clear(); this.j = false; - ObjectIterator objectiterator = this.i.long2ObjectEntrySet().iterator(); + ObjectIterator objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation Entry entry; long j; @@ -284,7 +285,7 @@ public abstract class LightEngineStorage> e } this.n.clear(); - objectiterator = this.i.long2ObjectEntrySet().iterator(); + objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation; while (objectiterator.hasNext()) { entry = (Entry) objectiterator.next(); diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java index e9cedbc8b..a29d54c77 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -48,6 +48,20 @@ public final class MCUtil { new ThreadFactoryBuilder().setNameFormat("Paper Object Cleaner").build() ); + // Tuinity start + private static org.bukkit.entity.HumanEntity[] EMPTY_HUMAN_ARRAY = new org.bukkit.entity.HumanEntity[0]; + public static void closeInventory(IInventory inventory, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { + List viewers = inventory.getViewers(); + if (viewers.isEmpty()) { + return; + } + + for (org.bukkit.entity.HumanEntity viewer : viewers.toArray(EMPTY_HUMAN_ARRAY)) { + viewer.closeInventory(reason); + } + } + // Tuinity end + public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 26f230a80..eb71fa6e3 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -986,7 +986,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant= MAX_CHUNK_EXEC_TIME) { + if (!moreTasks) { + lastMidTickExecuteFailure = currTime; + } + + // note: negative values reduce the time + long overuse = diff - MAX_CHUNK_EXEC_TIME; + if (overuse >= (10L * 1000L * 1000L)) { // 10ms + // make sure something like a GC or dumb plugin doesn't screw us over... + overuse = 10L * 1000L * 1000L; // 10ms + } + + double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; + long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); + + lastMidTickExecute = currTime + extraSleep; + return; + } + } + } finally { + co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); + } + } + // Tuinity end - execute chunk tasks mid tick + private void executeModerately() { this.executeAll(); java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L); @@ -1092,22 +1162,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { - midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick + // Tuinity - replace logic return !this.canOversleep(); }); isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); @@ -1260,6 +1316,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // Spigot - Spigot > // CraftBukkit - cb > vanilla! + return "Tuinity"; // Tuinity //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! } public CrashReport b(CrashReport crashreport) { diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java index d5cc2af83..327bb3e74 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -71,6 +71,39 @@ public class NetworkManager extends SimpleChannelInboundHandler> { EnumProtocol protocol; // Paper end + // Tuinity start - allow controlled flushing + volatile boolean canFlush = true; + private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); + private int flushPacketsStart; + private final Object flushLock = new Object(); + + void disableAutomaticFlush() { + synchronized (this.flushLock) { + this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false + this.canFlush = false; + } + } + + void enableAutomaticFlush() { + synchronized (this.flushLock) { + this.canFlush = true; + if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true + this.flush(); // only make the flush call if we need to + } + } + } + + private final void flush() { + if (this.channel.eventLoop().inEventLoop()) { + this.channel.flush(); + } else { + this.channel.eventLoop().execute(() -> { + this.channel.flush(); + }); + } + } + // Tuinity end - allow controlled flushing + public NetworkManager(EnumProtocolDirection enumprotocoldirection) { this.h = enumprotocoldirection; } @@ -217,7 +250,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { MCUtil.isMainThread() && packet.isReady() && this.packetQueue.isEmpty() && (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) ))) { - this.dispatchPacket(packet, genericfuturelistener); + this.writePacket(packet, genericfuturelistener, null); // Tuinity return; } // write the packets to the queue, then flush - antixray hooks there already @@ -243,6 +276,14 @@ public class NetworkManager extends SimpleChannelInboundHandler> { private void dispatchPacket(Packet packet, @Nullable GenericFutureListener> genericFutureListener) { this.b(packet, genericFutureListener); } // Paper - OBFHELPER private void b(Packet packet, @Nullable GenericFutureListener> genericfuturelistener) { + // Tuinity start - add flush parameter + this.writePacket(packet, genericfuturelistener, Boolean.TRUE); + } + private void writePacket(Packet packet, @Nullable GenericFutureListener> genericfuturelistener, Boolean flushConditional) { + this.packetWrites.getAndIncrement(); // must be befeore using canFlush + boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); + final boolean flush = effectiveFlush || packet instanceof PacketPlayOutKeepAlive || packet instanceof PacketPlayOutKickDisconnect; // no delay for certain packets + // Tuinity end - add flush parameter EnumProtocol enumprotocol = EnumProtocol.a(packet); EnumProtocol enumprotocol1 = (EnumProtocol) this.channel.attr(NetworkManager.c).get(); @@ -265,7 +306,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { try { // Paper end - ChannelFuture channelfuture = this.channel.writeAndFlush(packet); + ChannelFuture channelfuture = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter if (genericfuturelistener != null) { channelfuture.addListener(genericfuturelistener); @@ -297,7 +338,7 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } try { // Paper end - ChannelFuture channelfuture1 = this.channel.writeAndFlush(packet); + ChannelFuture channelfuture1 = (flush) ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter if (genericfuturelistener != null) { @@ -340,6 +381,8 @@ public class NetworkManager extends SimpleChannelInboundHandler> { } private boolean processQueue() { if (this.packetQueue.isEmpty()) return true; + final boolean needsFlush = this.canFlush; // Tuinity - make only one flush call per sendPacketQueue() call + boolean hasWrotePacket = false; // If we are on main, we are safe here in that nothing else should be processing queue off main anymore // But if we are not on main due to login/status, the parent is synchronized on packetQueue java.util.Iterator iterator = this.packetQueue.iterator(); @@ -347,16 +390,22 @@ public class NetworkManager extends SimpleChannelInboundHandler> { NetworkManager.QueuedPacket queued = iterator.next(); // poll -> peek // Fix NPE (Spigot bug caused by handleDisconnection()) - if (queued == null) { + if (false && queued == null) { // Tuinity - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here return true; } Packet packet = queued.getPacket(); if (!packet.isReady()) { + // Tuinity start - make only one flush call per sendPacketQueue() call + if (hasWrotePacket && (needsFlush || this.canFlush)) { + this.flush(); + } + // Tuinity end - make only one flush call per sendPacketQueue() call return false; } else { iterator.remove(); - this.dispatchPacket(packet, queued.getGenericFutureListener()); + this.writePacket(packet, queued.getGenericFutureListener(), (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Tuinity - make only one flush call per sendPacketQueue() call + hasWrotePacket = true; // Tuinity - make only one flush call per sendPacketQueue() call } } return true; diff --git a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java index 8335d0033..e0ee52409 100644 --- a/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java +++ b/src/main/java/net/minecraft/server/PacketPlayOutMapChunk.java @@ -19,7 +19,7 @@ public class PacketPlayOutMapChunk implements Packet { @Nullable private BiomeStorage e; private byte[] f; private byte[] getData() { return this.f; } // Paper - OBFHELPER - private List g; + private List g; private List getTileEntityData() { return this.g; } // Tuinity - OBFHELPER private boolean h; private boolean i; @@ -32,14 +32,16 @@ public class PacketPlayOutMapChunk implements Packet { // Paper start private final java.util.List extraPackets = new java.util.ArrayList<>(); - private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750); + private static final int TE_LIMIT = Integer.getInteger("tuinity.excessive-te-limit", 750); // Tuinity - handle oversized chunk data packets more robustly + private static final int TE_SPLIT_LIMIT = Math.max(4096 + 1, Integer.getInteger("tuinity.te-split-limit", 15_000)); // Tuinity - handle oversized chunk data packets more robustly + private boolean mustSplit; // Tuinity - handle oversized chunk data packets more robustly @Override public java.util.List getExtraPackets() { return extraPackets; } // Paper end - public PacketPlayOutMapChunk(Chunk chunk, int i, boolean flag) { + public PacketPlayOutMapChunk(Chunk chunk, int i, boolean flag) { final int chunkSectionBitSet = i; // Tuinity - handle oversized chunk data packets more robustly ChunkPacketInfo chunkPacketInfo = chunk.world.chunkPacketBlockController.getChunkPacketInfo(this, chunk, i); // Paper - Anti-Xray - Add chunk packet info ChunkCoordIntPair chunkcoordintpair = chunk.getPos(); @@ -48,29 +50,10 @@ public class PacketPlayOutMapChunk implements Packet { this.h = i == 65535; this.i = flag; this.d = new NBTTagCompound(); - Iterator iterator = chunk.f().iterator(); - - Entry entry; - - while (iterator.hasNext()) { - entry = (Entry) iterator.next(); - if (((HeightMap.Type) entry.getKey()).c()) { - this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a())); - } - } - - if (this.h) { - this.e = chunk.getBiomeIndex().b(); - } - - this.f = new byte[this.a(chunk, i)]; - // Paper start - Anti-Xray - Add chunk packet info - if (chunkPacketInfo != null) { - chunkPacketInfo.setData(this.getData()); - } - this.c = this.writeChunk(new PacketDataSerializer(this.k()), chunk, i, chunkPacketInfo); - // Paper end this.g = Lists.newArrayList(); + // Tuinity start - moved code up (moved declaration of Iterator + Entry up) + Iterator iterator; + Entry entry; iterator = chunk.getTileEntities().entrySet().iterator(); int totalTileEntities = 0; // Paper @@ -82,7 +65,15 @@ public class PacketPlayOutMapChunk implements Packet { if (this.f() || (i & 1 << j) != 0) { // Paper start - improve oversized chunk data packet handling - if (++totalTileEntities > TE_LIMIT) { + // Tuinity start - improve oversized chunk data packet handling + ++totalTileEntities; + if (totalTileEntities > TE_SPLIT_LIMIT) { + this.mustSplit = true; + this.getTileEntityData().clear(); + this.extraPackets.clear(); + break; + } + if (totalTileEntities > TE_LIMIT) { // Tuinity end PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket(); if (updatePacket != null) { this.extraPackets.add(updatePacket); @@ -96,7 +87,69 @@ public class PacketPlayOutMapChunk implements Packet { this.g.add(nbttagcompound); } } + iterator = chunk.f().iterator(); // Tuinity - moved declaration of Iterator and Entry up + // Tuinity - moved this all up + + while (iterator.hasNext()) { + entry = (Entry) iterator.next(); + if (((HeightMap.Type) entry.getKey()).c()) { + this.d.set(((HeightMap.Type) entry.getKey()).b(), new NBTTagLongArray(((HeightMap) entry.getValue()).a())); + } + } + + if (this.h) { + this.e = chunk.getBiomeIndex().b(); + } + + this.f = new byte[this.a(chunk, i)]; + // Paper start - Anti-Xray - Add chunk packet info + if (chunkPacketInfo != null) { + chunkPacketInfo.setData(this.getData()); + } + this.c = this.writeChunk(new PacketDataSerializer(this.k()), chunk, i, chunkPacketInfo); + // Paper end + // Tuinity start - move this all up +// this.g = Lists.newArrayList(); +// iterator = chunk.getTileEntities().entrySet().iterator(); +// int totalTileEntities = 0; // Paper +// +// while (iterator.hasNext()) { +// entry = (Entry) iterator.next(); +// BlockPosition blockposition = (BlockPosition) entry.getKey(); +// TileEntity tileentity = (TileEntity) entry.getValue(); +// int j = blockposition.getY() >> 4; +// +// if (this.f() || (i & 1 << j) != 0) { +// // Paper start - improve oversized chunk data packet handling +// if (++totalTileEntities > TE_LIMIT) { +// PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket(); +// if (updatePacket != null) { +// this.extraPackets.add(updatePacket); +// continue; +// } +// } +// // Paper end +// NBTTagCompound nbttagcompound = tileentity.b(); +// if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper +// +// this.g.add(nbttagcompound); +// } +// } + // Tuinity end - move this all up chunk.world.chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks + // Tuinity start - improve oversized chunk data packet handling + if (this.mustSplit) { + int chunkSectionBitSetCopy = chunkSectionBitSet; + for (int a = 0, len = Integer.bitCount(chunkSectionBitSet); a < len; ++a) { + int trailingBit = com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(chunkSectionBitSetCopy); + int sectionIndex = Integer.numberOfTrailingZeros(trailingBit); + chunkSectionBitSetCopy ^= trailingBit; // move on to the next + + if (chunk.getSections()[sectionIndex] != null) { + this.extraPackets.add(new PacketPlayOutMapChunk(chunk, trailingBit, false)); + } + } + } // Tuinity end - improve oversized chunk data packet handling } // Paper start - Async-Anti-Xray - Getter and Setter for the ready flag @@ -189,7 +242,7 @@ public class PacketPlayOutMapChunk implements Packet { for (int l = achunksection.length; k < l; ++k) { ChunkSection chunksection = achunksection[k]; - if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { + if ((!this.mustSplit && chunksection != Chunk.a) && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { // Tuinity - improve oversized chunk data packet handling j |= 1 << k; chunksection.writeChunkSection(packetdataserializer, chunkPacketInfo); // Paper - Anti-Xray - Add chunk packet info } @@ -206,7 +259,7 @@ public class PacketPlayOutMapChunk implements Packet { for (int l = achunksection.length; k < l; ++k) { ChunkSection chunksection = achunksection[k]; - if (chunksection != Chunk.a && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { + if ((!this.mustSplit && chunksection != Chunk.a) && (!this.f() || !chunksection.c()) && (i & 1 << k) != 0) { j += chunksection.j(); } } diff --git a/src/main/java/net/minecraft/server/PathfinderNormal.java b/src/main/java/net/minecraft/server/PathfinderNormal.java index d7f0df123..ec55785af 100644 --- a/src/main/java/net/minecraft/server/PathfinderNormal.java +++ b/src/main/java/net/minecraft/server/PathfinderNormal.java @@ -538,7 +538,7 @@ public class PathfinderNormal extends PathfinderAbstract { if (!iblockdata.a(iblockaccess, blockposition, PathMode.LAND)) { return PathType.BLOCKED; } else { - Fluid fluid = iblockaccess.getFluid(blockposition); + Fluid fluid = iblockdata.getFluid(); // Tuinity - remove another getType call return fluid.a((Tag) TagsFluid.WATER) ? PathType.WATER : (fluid.a((Tag) TagsFluid.LAVA) ? PathType.LAVA : PathType.OPEN); } diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java index a3bce8f13..a483ec0e2 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -494,6 +494,7 @@ public class PlayerChunk { // Paper end - per player view distance } + public final CompletableFuture> getOrCreateFuture(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { return this.a(chunkstatus, playerchunkmap); } // Tuinity - OBFHELPER public CompletableFuture> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) { int i = chunkstatus.c(); CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(i); @@ -549,6 +550,7 @@ public class PlayerChunk { } protected void a(PlayerChunkMap playerchunkmap) { + com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticket level update"); // Tuinity ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel); ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel); boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET; @@ -558,7 +560,8 @@ public class PlayerChunk { // CraftBukkit start // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. if (playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && !playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { - this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main + com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity Chunk chunk = (Chunk)either.left().orElse(null); if (chunk != null) { playerchunkmap.callbackExecutor.execute(() -> { @@ -623,7 +626,8 @@ public class PlayerChunk { if (!flag2 && flag3) { // Paper start - cache ticking ready status int expectCreateCount = ++this.fullChunkCreateCount; - this.fullChunkFuture = playerchunkmap.b(this); ensureMain(this.fullChunkFuture).thenAccept((either) -> { // Paper - ensure main + this.fullChunkFuture = playerchunkmap.b(this); this.fullChunkFuture.thenAccept((either) -> { // Paper - ensure main // Tuinity - always fired on main + com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full chunk future completion"); // Tuinity if (either.left().isPresent() && PlayerChunk.this.fullChunkCreateCount == expectCreateCount) { // note: Here is a very good place to add callbacks to logic waiting on this. Chunk fullChunk = either.left().get(); @@ -654,7 +658,8 @@ public class PlayerChunk { if (!flag4 && flag5) { // Paper start - cache ticking ready status - this.tickingFuture = playerchunkmap.a(this); ensureMain(this.tickingFuture).thenAccept((either) -> { // Paper - ensure main + this.tickingFuture = playerchunkmap.a(this); this.tickingFuture.thenAccept((either) -> { // Paper - ensure main // Tuinity - always completed on main + com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticking chunk future completion"); // Tuinity if (either.left().isPresent()) { // note: Here is a very good place to add callbacks to logic waiting on this. Chunk tickingChunk = either.left().get(); @@ -685,12 +690,20 @@ public class PlayerChunk { } // Paper start - cache ticking ready status - this.entityTickingFuture = playerchunkmap.b(this.location); ensureMain(this.entityTickingFuture).thenAccept((either) -> { // Paper ensureMain + this.entityTickingFuture = playerchunkmap.b(this.location); this.entityTickingFuture.thenAccept((either) -> { // Paper ensureMain // Tuinity - always completed on main + com.tuinity.tuinity.util.TickThread.ensureTickThread("Async entity ticking chunk future completion"); // Tuinity if (either.left().isPresent()) { // note: Here is a very good place to add callbacks to logic waiting on this. Chunk entityTickingChunk = either.left().get(); PlayerChunk.this.isEntityTickingReady = true; - + // Tuinity start - optimise chunk tick iteration + ChunkProviderServer chunkProvider = PlayerChunk.this.chunkMap.world.getChunkProvider(); + if (chunkProvider.isTickingChunks) { + chunkProvider.pendingEntityTickingChunkChanges.put(entityTickingChunk, true); + } else { + chunkProvider.entityTickingChunks.add(entityTickingChunk); + } + // Tuinity end - optimise chunk tick iteration @@ -702,6 +715,17 @@ public class PlayerChunk { if (flag6 && !flag7) { this.entityTickingFuture.complete(PlayerChunk.UNLOADED_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage + // Tuinity start - optimise chunk tick iteration + ChunkProviderServer chunkProvider = PlayerChunk.this.chunkMap.world.getChunkProvider(); + Chunk chunk = this.getFullChunkIfCached(); + if (chunk != null) { + if (chunkProvider.isTickingChunks) { + chunkProvider.pendingEntityTickingChunkChanges.put(chunk, false); + } else { + chunkProvider.entityTickingChunks.remove(chunk); + } + } + // Tuinity end - optimise chunk tick iteration this.entityTickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; } @@ -728,7 +752,8 @@ public class PlayerChunk { // CraftBukkit start // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. if (!playerchunk_state.isAtLeast(PlayerChunk.State.BORDER) && playerchunk_state1.isAtLeast(PlayerChunk.State.BORDER)) { - this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main + this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main + com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity Chunk chunk = (Chunk)either.left().orElse(null); if (chunk != null) { playerchunkmap.callbackExecutor.execute(() -> { diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index 5544254a6..ab751b48d 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -119,31 +119,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() public final CallbackExecutor callbackExecutor = new CallbackExecutor(); public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { - - // Paper start - replace impl with recursive safe multi entry queue - // it's possible to schedule multiple tasks currently, so it's vital we change this impl - // If we recurse into the executor again, we will append to another queue, ensuring task order consistency - private java.util.ArrayDeque queued = new java.util.ArrayDeque<>(); + // Tuinity start - revert paper's change + private Runnable queued; @Override public void execute(Runnable runnable) { AsyncCatcher.catchOp("Callback Executor execute"); - if (queued == null) { - queued = new java.util.ArrayDeque<>(); + if (queued != null) { + MinecraftServer.LOGGER.fatal("Failed to schedule runnable", new IllegalStateException("Already queued")); // Paper - make sure this is printed + throw new IllegalStateException("Already queued"); } - queued.add(runnable); + queued = runnable; } + // Tuinity end - revert paper's change @Override public void run() { AsyncCatcher.catchOp("Callback Executor run"); - if (queued == null) { - return; - } - java.util.ArrayDeque queue = queued; + // Tuinity start - revert paper's change + Runnable task = queued; queued = null; - Runnable task; - while ((task = queue.pollFirst()) != null) { + if (task != null) { + // Tuinity end - revert paper's change task.run(); } } @@ -198,6 +195,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // Paper end - no-tick view distance void addPlayerToDistanceMaps(EntityPlayer player) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity int chunkX = MCUtil.getChunkCoordinate(player.locX()); int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); // Note: players need to be explicitly added to distance maps before they can be updated @@ -228,6 +226,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } void removePlayerFromDistanceMaps(EntityPlayer player) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity // Paper start - use distance map to optimise tracker for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { this.playerEntityTrackerTrackMaps[i].remove(player); @@ -245,6 +244,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } void updateMaps(EntityPlayer player) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity int chunkX = MCUtil.getChunkCoordinate(player.locX()); int chunkZ = MCUtil.getChunkCoordinate(player.locZ()); // Note: players need to be explicitly added to distance maps before they can be updated @@ -746,6 +746,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @Nullable private PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Chunk holder update"); // Tuinity if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) { return playerchunk; } else { @@ -959,7 +960,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, chunkPos.x, chunkPos.z, - poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); + poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority if (!chunk.isNeedsSaving()) { return; @@ -993,7 +994,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { asyncSaveData = ChunkRegionLoader.getAsyncSaveData(this.world, chunk); } - this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY, + this.world.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, // Tuinity - use normal priority asyncSaveData, chunk); chunk.setLastSaved(this.world.getTime()); @@ -1048,6 +1049,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } protected boolean b() { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update visibleChunks off of the main thread"); // Tuinity if (!this.updatingChunksModified) { return false; } else { @@ -1233,7 +1235,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } // Paper end this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); - }); + }).thenComposeAsync((either) -> { // Tuinity start - force competion on the main thread + return CompletableFuture.completedFuture(either); + }, this.mainInvokingExecutor); + // Tuinity end - force competion on the main thread } protected void c(ChunkCoordIntPair chunkcoordintpair) { @@ -1485,6 +1490,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } public void setViewDistance(int i) { // Paper - public + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32 if (j != this.viewDistance) { @@ -1498,6 +1504,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { // Paper start - no-tick view distance public final void setNoTickViewDistance(int viewDistance) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32); this.noTickViewDistance = viewDistance; @@ -2024,23 +2031,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { private final void processTrackQueue() { this.world.timings.tracker1.startTiming(); try { - for (EntityTracker tracker : this.trackedEntities.values()) { - // update tracker entry - tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange()); + for (Chunk chunk : this.world.getChunkProvider().entityTickingChunks) { + Entity[] entities = chunk.entities.getRawData(); + for (int i = 0, len = chunk.entities.size(); i < len; ++i) { + Entity entity = entities[i]; + EntityTracker tracker = this.trackedEntities.get(entity.getId()); + if (tracker != null) { + tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange()); + tracker.trackerEntry.tick(); + } + } } } finally { this.world.timings.tracker1.stopTiming(); } - - - this.world.timings.tracker2.startTiming(); - try { - for (EntityTracker tracker : this.trackedEntities.values()) { - tracker.trackerEntry.tick(); - } - } finally { - this.world.timings.tracker2.stopTiming(); - } } // Paper end - optimised tracker diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index cf83059fe..37f55c66b 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -322,19 +322,24 @@ public class PlayerConnection implements PacketListenerPlayIn { if (entity != this.player && entity.getRidingPassenger() == this.player && entity == this.r) { WorldServer worldserver = this.player.getWorldServer(); - double d0 = entity.locX(); - double d1 = entity.locY(); - double d2 = entity.locZ(); - double d3 = packetplayinvehiclemove.getX(); - double d4 = packetplayinvehiclemove.getY(); - double d5 = packetplayinvehiclemove.getZ(); + double d0 = entity.locX();double fromX = d0; // Tuinity - OBFHELPER + double d1 = entity.locY();double fromY = d1; // Tuinity - OBFHELPER + double d2 = entity.locZ();double fromZ = d2; // Tuinity - OBFHELPER + double d3 = packetplayinvehiclemove.getX();double toX = d3; // Tuinity - OBFHELPER + double d4 = packetplayinvehiclemove.getY();double toY = d4; // Tuinity - OBFHELPER + double d5 = packetplayinvehiclemove.getZ();double toZ = d5; // Tuinity - OBFHELPER float f = packetplayinvehiclemove.getYaw(); float f1 = packetplayinvehiclemove.getPitch(); double d6 = d3 - this.s; double d7 = d4 - this.t; double d8 = d5 - this.u; double d9 = entity.getMot().g(); - double d10 = d6 * d6 + d7 * d7 + d8 * d8; + // Tuinity start - fix large move vectors killing the server + double currDeltaX = toX - fromX; + double currDeltaY = toY - fromY; + double currDeltaZ = toZ - fromZ; + double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); + // Tuinity end - fix large move vectors killing the server // CraftBukkit start - handle custom speeds and skipped ticks @@ -363,7 +368,9 @@ public class PlayerConnection implements PacketListenerPlayIn { speed *= 2f; // TODO: Get the speed of the vehicle instead of the player // Paper start - Prevent moving into unloaded chunks - if (player.world.paperConfig.preventMovingIntoUnloadedChunks && worldserver.getChunkIfLoadedImmediately((int) Math.floor(packetplayinvehiclemove.getX()) >> 4, (int) Math.floor(packetplayinvehiclemove.getZ()) >> 4) == null) { + if (player.world.paperConfig.preventMovingIntoUnloadedChunks // Tuinity - improve this check + && (!worldserver.areChunksLoadedForMove(this.player.getBoundingBoxAt(this.player.locX(), this.player.locY(), this.player.locZ()).expand(toX - this.player.locX(), toY - this.player.locY(), toZ - this.player.locZ()))) // Tuinity - improve this check + || !worldserver.areChunksLoadedForMove(entity.getBoundingBoxAt(entity.locX(), entity.locY(), entity.locZ()).expand(toX - entity.locX(), toY - entity.locY(), toZ - entity.locZ()))) { // Tuinity - improve this check this.networkManager.sendPacket(new PacketPlayOutVehicleMove(entity)); return; } @@ -976,7 +983,7 @@ public class PlayerConnection implements PacketListenerPlayIn { } if (this.teleportPos != null) { - if (this.e - this.A > 20) { + if (false && this.e - this.A > 20) { // Tuinity - this will greatly screw with clients with > 1000ms RTT this.A = this.e; this.a(this.teleportPos.x, this.teleportPos.y, this.teleportPos.z, this.player.yaw, this.player.pitch); } @@ -1000,7 +1007,7 @@ public class PlayerConnection implements PacketListenerPlayIn { double d2 = this.player.locZ(); double d3 = this.player.locY(); double d4 = packetplayinflying.a(this.player.locX());double toX = d4; // Paper - OBFHELPER - double d5 = packetplayinflying.b(this.player.locY()); + double d5 = packetplayinflying.b(this.player.locY());double toY = d5; // Tuinity - OBFHELPER double d6 = packetplayinflying.c(this.player.locZ());double toZ = d6; // Paper - OBFHELPER float f = packetplayinflying.a(this.player.yaw); float f1 = packetplayinflying.b(this.player.pitch); @@ -1008,7 +1015,12 @@ public class PlayerConnection implements PacketListenerPlayIn { double d8 = d5 - this.m; double d9 = d6 - this.n; double d10 = this.player.getMot().g(); - double d11 = d7 * d7 + d8 * d8 + d9 * d9; + // Tuinity start - fix large move vectors killing the server + double currDeltaX = toX - prevX; + double currDeltaY = toY - prevY; + double currDeltaZ = toZ - prevZ; + double d11 = Math.max(d7 * d7 + d8 * d8 + d9 * d9, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); + // Tuinity end - fix large move vectors killing the server if (this.player.isSleeping()) { if (d11 > 1.0D) { @@ -1041,7 +1053,7 @@ public class PlayerConnection implements PacketListenerPlayIn { speed = player.abilities.walkSpeed * 10f; } // Paper start - Prevent moving into unloaded chunks - if (player.world.paperConfig.preventMovingIntoUnloadedChunks && (this.player.locX() != toX || this.player.locZ() != toZ) && worldserver.getChunkIfLoadedImmediately((int) Math.floor(toX) >> 4, (int) Math.floor(toZ) >> 4) == null) { // Paper - use getIfLoadedImmediately + if (player.world.paperConfig.preventMovingIntoUnloadedChunks && !((WorldServer)this.player.world).areChunksLoadedForMove(this.player.getBoundingBoxAt(this.player.locX(), this.player.locY(), this.player.locZ()).expand(toX - this.player.locX(), toY - this.player.locY(), toZ - this.player.locZ()))) { // Paper - use getIfLoadedImmediately // Tuinity - improve this check this.internalTeleport(this.player.locX(), this.player.locY(), this.player.locZ(), this.player.yaw, this.player.pitch, Collections.emptySet()); return; } @@ -1097,6 +1109,7 @@ public class PlayerConnection implements PacketListenerPlayIn { } this.player.move(EnumMoveType.PLAYER, new Vec3D(d7, d8, d9)); + boolean didCollide = toX != this.player.locX() || toY != this.player.locY() || toZ != this.player.locZ(); // Tuinity - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... this.player.c(packetplayinflying.b()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move // Paper start - prevent position desync if (this.teleportPos != null) { @@ -1121,7 +1134,7 @@ public class PlayerConnection implements PacketListenerPlayIn { } this.player.setLocation(d4, d5, d6, f, f1); - if (!this.player.noclip && !this.player.isSleeping() && (flag1 && worldserver.getCubes(this.player, axisalignedbb) || this.a((IWorldReader) worldserver, axisalignedbb))) { + if (!this.player.noclip && !this.player.isSleeping() && (flag1 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb)))) { // Tuinity - optimise out the extra getCubes-like call most of the time this.a(d0, d1, d2, f, f1); } else { // CraftBukkit start - fire PlayerMoveEvent diff --git a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java index 7ea293f38..e698dd226 100644 --- a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java +++ b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java @@ -13,10 +13,30 @@ public class PlayerConnectionUtils { ensureMainThread(packet, t0, (IAsyncTaskHandler) worldserver.getMinecraftServer()); } + // Tuinity start - detailed watchdog information + private static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); + private static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); + + public static long getTotalProcessedPackets() { + return totalMainThreadPacketsProcessed.get(); + } + + public static java.util.List getCurrentPacketProcessors() { + java.util.List ret = new java.util.ArrayList<>(4); + for (PacketListener listener : packetProcessing) { + ret.add(listener); + } + + return ret; + } + // Tuinity end - detailed watchdog information + public static void ensureMainThread(Packet packet, T t0, IAsyncTaskHandler iasynctaskhandler) throws CancelledPacketHandleException { if (!iasynctaskhandler.isMainThread()) { Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings iasynctaskhandler.execute(() -> { + packetProcessing.push(t0); // Tuinity - detailed watchdog information + try { // Tuinity - detailed watchdog information if (MinecraftServer.getServer().hasStopped() || (t0 instanceof PlayerConnection && ((PlayerConnection) t0).processedDisconnect)) return; // CraftBukkit, MC-142590 if (t0.a().isConnected()) { try (Timing ignored = timing.startTiming()) { // Paper - timings @@ -40,6 +60,12 @@ public class PlayerConnectionUtils { } else { PlayerConnectionUtils.LOGGER.debug("Ignoring packet due to disconnection: " + packet); } + // Tuinity start - detailed watchdog information + } finally { + totalMainThreadPacketsProcessed.getAndIncrement(); + packetProcessing.pop(); + } + // Tuinity end - detailed watchdog information }); throw CancelledPacketHandleException.INSTANCE; diff --git a/src/main/java/net/minecraft/server/PlayerInteractManager.java b/src/main/java/net/minecraft/server/PlayerInteractManager.java index 6d192b274..f5de49e3e 100644 --- a/src/main/java/net/minecraft/server/PlayerInteractManager.java +++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java @@ -21,14 +21,29 @@ public class PlayerInteractManager { private EnumGamemode gamemode; private EnumGamemode e; private boolean f; - private int lastDigTick; + private int lastDigTick; private long lastDigTime; // Tuinity - lag compensate block breaking private BlockPosition h; private int currentTick; - private boolean j; + private boolean j; private final boolean hasDestroyedTooFast() { return this.j; } // Tuinity - OBFHELPER private BlockPosition k; - private int l; + private int l; private final int getHasDestroyedTooFastStartTick() { return this.l; } // Tuinity - OBFHELPER + private long hasDestroyedTooFastStartTime; // Tuinity - lag compensate block breaking private int m; + // Tuinity start - lag compensate block breaking + private int getTimeDiggingLagCompensate() { + int lagCompensated = (int)((System.nanoTime() - this.lastDigTime) / (50L * 1000L * 1000L)); + int tickDiff = this.currentTick - this.lastDigTick; + return (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to + } + + private int getTimeDiggingTooFastLagCompensate() { + int lagCompensated = (int)((System.nanoTime() - this.hasDestroyedTooFastStartTime) / (50L * 1000L * 1000L)); + int tickDiff = this.currentTick - this.getHasDestroyedTooFastStartTick(); + return (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to + } + // Tuinity end + public PlayerInteractManager(WorldServer worldserver) { this.gamemode = EnumGamemode.NOT_SET; this.e = EnumGamemode.NOT_SET; @@ -84,7 +99,7 @@ public class PlayerInteractManager { if (iblockdata == null || iblockdata.isAir()) { // Paper this.j = false; } else { - float f = this.a(iblockdata, this.k, this.l); + float f = this.updateBlockBreakAnimation(iblockdata, this.k, this.getTimeDiggingTooFastLagCompensate()); // Tuinity - lag compensate destroying blocks if (f >= 1.0F) { this.j = false; @@ -104,7 +119,7 @@ public class PlayerInteractManager { this.m = -1; this.f = false; } else { - this.a(iblockdata, this.h, this.lastDigTick); + this.updateBlockBreakAnimation(iblockdata, this.h, this.getTimeDiggingLagCompensate()); // Tuinity - lag compensate destroying blocks } } @@ -112,6 +127,12 @@ public class PlayerInteractManager { private float a(IBlockData iblockdata, BlockPosition blockposition, int i) { int j = this.currentTick - i; + // Tuinity start - change i (startTime) to totalTime + return this.updateBlockBreakAnimation(iblockdata, blockposition, j); + } + private float updateBlockBreakAnimation(IBlockData iblockdata, BlockPosition blockposition, int totalTime) { + int j = totalTime; + // Tuinity end float f = iblockdata.getDamage(this.player, this.player.world, blockposition) * (float) (j + 1); int k = (int) (f * 10.0F); @@ -179,7 +200,7 @@ public class PlayerInteractManager { return; } - this.lastDigTick = this.currentTick; + this.lastDigTick = this.currentTick; this.lastDigTime = System.nanoTime(); // Tuinity - lag compensate block breaking float f = 1.0F; iblockdata = this.world.getType(blockposition); @@ -232,12 +253,12 @@ public class PlayerInteractManager { int j = (int) (f * 10.0F); this.world.a(this.player.getId(), blockposition, j); - this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "actual start of destroying")); + if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "actual start of destroying")); // Tuinity - on lagging servers this can cause the client to think it's only just started to destroy a block when it already has/will this.m = j; } } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.STOP_DESTROY_BLOCK) { if (blockposition.equals(this.h)) { - int k = this.currentTick - this.lastDigTick; + int k = this.getTimeDiggingLagCompensate(); // Tuinity - lag compensate block breaking iblockdata = this.world.getType(blockposition); if (!iblockdata.isAir()) { @@ -254,12 +275,18 @@ public class PlayerInteractManager { this.f = false; this.j = true; this.k = blockposition; - this.l = this.lastDigTick; + this.l = this.lastDigTick; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Tuinity - lag compensate block breaking } } } + // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block + if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) { + this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); + } else { this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "stopped destroying")); + } + // Tuinity end - this can cause clients on a lagging server to think they're not currently destroying a block } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.ABORT_DESTROY_BLOCK) { this.f = false; if (!Objects.equals(this.h, blockposition) && !BlockPosition.ZERO.equals(this.h)) { @@ -271,7 +298,7 @@ public class PlayerInteractManager { } this.world.a(this.player.getId(), blockposition, -1); - this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "aborted destroying")); + if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, "aborted destroying")); // Tuinity - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying } } @@ -281,7 +308,13 @@ public class PlayerInteractManager { public void a(BlockPosition blockposition, PacketPlayInBlockDig.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype, String s) { if (this.breakBlock(blockposition)) { + // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block + if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) { + this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); + } else { this.player.playerConnection.sendPacket(new PacketPlayOutBlockBreak(blockposition, this.world.getType(blockposition), packetplayinblockdig_enumplayerdigtype, true, s)); + } + // Tuinity end - this can cause clients on a lagging server to think they're not currently destroying a block } else { this.player.playerConnection.sendPacket(new PacketPlayOutBlockChange(this.world, blockposition)); // CraftBukkit - SPIGOT-5196 } diff --git a/src/main/java/net/minecraft/server/ProtoChunk.java b/src/main/java/net/minecraft/server/ProtoChunk.java index 3b03c28ee..6ac9f437e 100644 --- a/src/main/java/net/minecraft/server/ProtoChunk.java +++ b/src/main/java/net/minecraft/server/ProtoChunk.java @@ -179,14 +179,11 @@ public class ProtoChunk implements IChunkAccess { lightengine.a(blockposition); } - EnumSet enumset = this.getChunkStatus().h(); + HeightMap.Type[] enumset = this.getChunkStatus().heightMaps; // Tuinity - reduce iterator creation EnumSet enumset1 = null; - Iterator iterator = enumset.iterator(); + // Tuinity - reduce iterator creation - HeightMap.Type heightmap_type; - - while (iterator.hasNext()) { - heightmap_type = (HeightMap.Type) iterator.next(); + for (HeightMap.Type heightmap_type : enumset) { // Tuinity - reduce iterator creation HeightMap heightmap = (HeightMap) this.f.get(heightmap_type); if (heightmap == null) { @@ -202,10 +199,9 @@ public class ProtoChunk implements IChunkAccess { HeightMap.a(this, enumset1); } - iterator = enumset.iterator(); - - while (iterator.hasNext()) { - heightmap_type = (HeightMap.Type) iterator.next(); + // Tuinity start - reduce iterator creation + for (HeightMap.Type heightmap_type : enumset) { + // Tuinity end - reduce iterator creation ((HeightMap) this.f.get(heightmap_type)).a(i & 15, j, k & 15, iblockdata); } diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java index 93797395c..6928b1730 100644 --- a/src/main/java/net/minecraft/server/RegionFile.java +++ b/src/main/java/net/minecraft/server/RegionFile.java @@ -28,14 +28,349 @@ public class RegionFile implements AutoCloseable { private static final Logger LOGGER = LogManager.getLogger(); private static final ByteBuffer b = ByteBuffer.allocateDirect(1); private final FileChannel dataFile; - private final java.nio.file.Path d; - private final RegionFileCompression e; + private final java.nio.file.Path d; private final java.nio.file.Path getContainingDataFolder() { return this.d; } // Tuinity - OBFHELPER + private final RegionFileCompression e; private final RegionFileCompression getRegionFileCompression() { return this.e; } // Tuinity - OBFHELPER private final ByteBuffer f; - private final IntBuffer g; - private final IntBuffer h; + private final IntBuffer g; private final IntBuffer getOffsets() { return this.g; } // Tuinity - OBFHELPER + private final IntBuffer h; private final IntBuffer getTimestamps() { return this.h; } // Tuinity - OBFHELPER private final RegionFileBitSet freeSectors; public final File file; + // Tuinity start - try to recover from RegionFile header corruption + private static long roundToSectors(long bytes) { + long sectors = bytes >>> 12; // 4096 = 2^12 + long remainingBytes = bytes & 4095; + long sign = -remainingBytes; // sign is 1 if nonzero + return sectors + (sign >>> 63); + } + + private static final NBTTagCompound OVERSIZED_COMPOUND = new NBTTagCompound(); + + private NBTTagCompound attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { + try { + if (chunkDataLength < 0) { + return null; + } + + long offset = sector * 4096L + 4L; // offset for chunk data + + if ((offset + chunkDataLength) > fileLength) { + return null; + } + + ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); + if (chunkDataLength != this.dataFile.read(chunkData, offset)) { + return null; + } + + ((java.nio.Buffer)chunkData).flip(); + + byte compressionType = chunkData.get(); + if (compressionType < 0) { // compressionType & 128 != 0 + // oversized chunk + return OVERSIZED_COMPOUND; + } + + RegionFileCompression compression = RegionFileCompression.getByType(compressionType); + if (compression == null) { + return null; + } + + InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); + + return NBTCompressedStreamTools.readNBT(new DataInputStream(new BufferedInputStream(input))); + } catch (Exception ex) { + return null; + } + } + + private int getLength(long sector) throws IOException { + ByteBuffer length = ByteBuffer.allocate(4); + if (4 != this.dataFile.read(length, sector * 4096L)) { + return -1; + } + + return length.getInt(0); + } + + private void backupRegionFile() { + File backup = new File(this.file.getParent(), this.file.getName() + "." + new java.util.Random().nextLong() + ".backup"); + this.backupRegionFile(backup); + } + + private void backupRegionFile(File to) { + try { + this.dataFile.force(true); + MinecraftServer.LOGGER.warn("Backing up regionfile \"" + this.file.getAbsolutePath() + "\" to " + to.getAbsolutePath()); + java.nio.file.Files.copy(this.file.toPath(), to.toPath()); + MinecraftServer.LOGGER.warn("Backed up the regionfile to " + to.getAbsolutePath()); + } catch (IOException ex) { + MinecraftServer.LOGGER.error("Failed to backup to " + to.getAbsolutePath(), ex); + } + } + + // note: only call for CHUNK regionfiles + void recalculateHeader() throws IOException { + if (!this.canRecalcHeader) { + return; + } + synchronized (this) { + MinecraftServer.LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.file.getAbsolutePath(), new Throwable()); + + // try to backup file so maybe it could be sent to us for further investigation + + this.backupRegionFile(); + NBTTagCompound[] compounds = new NBTTagCompound[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) + int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes + int[] sectorOffsets = new int[32 * 32]; // in sectors + boolean[] hasAikarOversized = new boolean[32 * 32]; + + long fileLength = this.dataFile.size(); + long totalSectors = roundToSectors(fileLength); + + // search the regionfile from start to finish for the most up-to-date chunk data + + for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip + int chunkDataLength = this.getLength(i); + NBTTagCompound compound = this.attemptRead(i, chunkDataLength, fileLength); + if (compound == null || compound == OVERSIZED_COMPOUND) { + continue; + } + + ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(compound); + int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); + + NBTTagCompound otherCompound = compounds[location]; + + if (otherCompound != null && ChunkRegionLoader.getLastWorldSaveTime(otherCompound) > ChunkRegionLoader.getLastWorldSaveTime(compound)) { + continue; // don't overwrite newer data. + } + + // aikar oversized? + File aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); + boolean isAikarOversized = false; + if (aikarOversizedFile.exists()) { + try { + NBTTagCompound aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); + if (ChunkRegionLoader.getLastWorldSaveTime(compound) == ChunkRegionLoader.getLastWorldSaveTime(aikarOversizedCompound)) { + // best we got for an id. hope it's good enough + isAikarOversized = true; + } + } catch (Exception ex) { + MinecraftServer.LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.file.getAbsolutePath() + ", oversized data for this chunk will be lost", ex); + // fall through, if we can't read aikar oversized we can't risk corrupting chunk data + } + } + + hasAikarOversized[location] = isAikarOversized; + compounds[location] = compound; + rawLengths[location] = chunkDataLength + 4; + sectorOffsets[location] = (int)i; + + int chunkSectorLength = (int)roundToSectors(rawLengths[location]); + i += chunkSectorLength; + --i; // gets incremented next iteration + } + + // forge style oversized data is already handled by the local search, and aikar data we just hope + // we get it right as aikar data has no identifiers we could use to try and find its corresponding + // local data compound + + java.nio.file.Path containingFolder = this.getContainingDataFolder(); + File[] regionFiles = containingFolder.toFile().listFiles(); + boolean[] oversized = new boolean[32 * 32]; + RegionFileCompression[] oversizedCompressionTypes = new RegionFileCompression[32 * 32]; + + if (regionFiles != null) { + ChunkCoordIntPair ourLowerLeftPosition = RegionFileCache.getRegionFileCoordinates(this.file); + + if (ourLowerLeftPosition == null) { + MinecraftServer.LOGGER.fatal("Unable to get chunk location of regionfile " + this.file.getAbsolutePath() + ", cannot recover oversized chunks"); + } else { + int lowerXBound = ourLowerLeftPosition.x; // inclusive + int lowerZBound = ourLowerLeftPosition.z; // inclusive + int upperXBound = lowerXBound + 32 - 1; // inclusive + int upperZBound = lowerZBound + 32 - 1; // inclusive + + // read mojang oversized data + for (File regionFile : regionFiles) { + ChunkCoordIntPair oversizedCoords = getOversizedChunkPair(regionFile); + if (oversizedCoords == null) { + continue; + } + + if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { + continue; // not in our regionfile + } + + // ensure oversized data is valid & is newer than data in the regionfile + + int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); + + byte[] chunkData; + try { + chunkData = Files.readAllBytes(regionFile.toPath()); + } catch (Exception ex) { + MinecraftServer.LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath(), ex); + continue; + } + + NBTTagCompound compound = null; + + // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them + RegionFileCompression compression = null; + for (RegionFileCompression compressionType : RegionFileCompression.getCompressionTypes().values()) { + try { + DataInputStream in = new DataInputStream(new BufferedInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData)))); // typical java + compound = NBTCompressedStreamTools.readNBT(in); + compression = compressionType; + break; // reaches here iff readNBT does not throw + } catch (Exception ex) { + continue; + } + } + + if (compound == null) { + MinecraftServer.LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", it's corrupt. Its data will be lost"); + continue; + } + + if (compounds[location] == null || ChunkRegionLoader.getLastWorldSaveTime(compound) > ChunkRegionLoader.getLastWorldSaveTime(compounds[location])) { + oversized[location] = true; + oversizedCompressionTypes[location] = compression; + } + } + } + } + + // now we need to calculate a new offset header + + int[] calculatedOffsets = new int[32 * 32]; + RegionFileBitSet newSectorAllocations = new RegionFileBitSet(); + newSectorAllocations.allocate(0, 2); // make space for header + + // allocate sectors for normal chunks + + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + + if (oversized[location]) { + continue; + } + + int rawLength = rawLengths[location]; // bytes + int sectorOffset = sectorOffsets[location]; // sectors + int sectorLength = (int)roundToSectors(rawLength); + + if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { + calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized + } else { + MinecraftServer.LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath() + ", chunk will be regenerated"); + } + } + } + + // allocate sectors for oversized chunks + + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + + if (!oversized[location]) { + continue; + } + + int sectorOffset = newSectorAllocations.allocateNewSpace(1); + int sectorLength = 1; + + try { + this.dataFile.write(this.getOversizedChunkHolderData(oversizedCompressionTypes[location]), sectorOffset * 4096); + // only allocate in the new offsets if the write succeeds + calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized + } catch (IOException ex) { + newSectorAllocations.free(sectorOffset, sectorLength); + MinecraftServer.LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath() + " will be regenerated"); + } + } + } + + // rewrite aikar oversized data + + this.oversizedCount = 0; + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + int isAikarOversized = hasAikarOversized[location] ? 1 : 0; + + this.oversizedCount += isAikarOversized; + this.oversized[location] = (byte)isAikarOversized; + } + } + + if (this.oversizedCount > 0) { + try { + this.writeOversizedMeta(); + } catch (Exception ex) { + MinecraftServer.LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.file.getAbsolutePath(), ex); + this.getOversizedMetaFile().delete(); + } + } else { + this.getOversizedMetaFile().delete(); + } + + this.freeSectors.copyFrom(newSectorAllocations); + + // before we overwrite the old sectors, print a summary of the chunks that got changed. + + MinecraftServer.LOGGER.info("Starting summary of changes for regionfile " + this.file.getAbsolutePath()); + + for (int chunkX = 0; chunkX < 32; ++chunkX) { + for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { + int location = chunkX | (chunkZ << 5); + + int oldOffset = this.getOffsets().get(location); + int newOffset = calculatedOffsets[location]; + + if (oldOffset == newOffset) { + continue; + } + + this.getOffsets().put(location, newOffset); // overwrite incorrect offset + + if (oldOffset == 0) { + // found lost data + MinecraftServer.LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.file.getAbsolutePath()); + } else if (newOffset == 0) { + MinecraftServer.LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.file.getAbsolutePath() + ", it will be regenerated"); + } else { + MinecraftServer.LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.file.getAbsolutePath()); + } + } + } + + MinecraftServer.LOGGER.info("End of change summary for regionfile " + this.file.getAbsolutePath()); + + // simply destroy the timestamp header, it's not used + + for (int i = 0; i < 32 * 32; ++i) { + this.getTimestamps().put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this + } + + // write new header + try { + this.flushHeader(); + this.dataFile.force(true); // try to ensure it goes through... + MinecraftServer.LOGGER.info("Successfully wrote new header to disk for regionfile " + this.file.getAbsolutePath()); + } catch (IOException ex) { + MinecraftServer.LOGGER.fatal("Failed to write new header to disk for regionfile " + this.file.getAbsolutePath(), ex); + } + } + } + + final boolean canRecalcHeader; // final forces compile fail on new constructor + // Tuinity end + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper // Paper start - Cache chunk status @@ -63,10 +398,21 @@ public class RegionFile implements AutoCloseable { // Paper end public RegionFile(File file, File file1, boolean flag) throws IOException { + // Tuinity start - add can recalc flag this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag); } + public RegionFile(File file, File file1, boolean flag, boolean canRecalcHeader) throws IOException { + this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag, canRecalcHeader); + // Tuinity end - add can recalc flag + } public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean flag) throws IOException { + // Tuinity start - add can recalc flag + this(java_nio_file_path, java_nio_file_path1, regionfilecompression, flag, false); + } + public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean flag, boolean canRecalcHeader) throws IOException { + this.canRecalcHeader = canRecalcHeader; + // Tuinity end - add can recalc flag this.file = java_nio_file_path.toFile(); // Paper this.f = ByteBuffer.allocateDirect(8192); initOversizedState(); @@ -95,12 +441,15 @@ public class RegionFile implements AutoCloseable { RegionFile.LOGGER.warn("Region file {} has truncated header: {}", java_nio_file_path, i); } - for (int j = 0; j < 1024; ++j) { + boolean needsHeaderRecalc = false; // Tuinity - recalculate header on header corruption + boolean hasBackedUp = false; // Tuinity - recalculate header on header corruption + + for (int j = 0; j < 1024; ++j) { // Tuinity - diff on change, we expect j to be the header location int k = this.g.get(j); if (k != 0) { - int l = b(k); - int i1 = a(k); + int l = b(k); // Tuinity - diff on change, we expect l to be offset in file + int i1 = a(k); // Tuinity - diff on change, we expect i1 to be sector length of region // Spigot start if (i1 == 255) { // We're maxed out, so we need to read the proper length from the section @@ -110,20 +459,87 @@ public class RegionFile implements AutoCloseable { } // Spigot end - this.freeSectors.a(l, i1); + // Tuinity start - recalculate header on header corruption + if (l < 0 || i1 < 0 || (l + i1) < 0) { + if (canRecalcHeader) { + MinecraftServer.LOGGER.error("Detected invalid header for regionfile " + this.file.getAbsolutePath() + "! Recalculating header..."); + needsHeaderRecalc = true; + break; + } else { + // location = chunkX | (chunkZ << 5); + MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() + + "! Cannot recalculate, removing local chunk (" + (j & 31) + "," + (j >>> 5) + ") from header"); + if (!hasBackedUp) { + hasBackedUp = true; + this.backupRegionFile(); + } + this.getTimestamps().put(j, 0); // be consistent, delete the timestamp too + this.getOffsets().put(j, 0); // delete the entry from header + continue; + } + } + boolean failedToAllocate = !this.freeSectors.tryAllocate(l, i1); + if (failedToAllocate && !canRecalcHeader) { + // location = chunkX | (chunkZ << 5); + MinecraftServer.LOGGER.fatal("Detected invalid header for regionfile " + this.file.getAbsolutePath() + + "! Cannot recalculate, removing local chunk (" + (j & 31) + "," + (j >>> 5) + ") from header"); + if (!hasBackedUp) { + hasBackedUp = true; + this.backupRegionFile(); + } + this.getTimestamps().put(j, 0); // be consistent, delete the timestamp too + this.getOffsets().put(j, 0); // delete the entry from header + continue; + } + needsHeaderRecalc |= failedToAllocate; + // Tuinity end - recalculate header on header corruption } } + + // Tuinity start - recalculate header on header corruption + // we move the recalc here so comparison to old header is correct when logging to console + if (needsHeaderRecalc) { // true if header gave us overlapping allocations + MinecraftServer.LOGGER.error("Recalculating regionfile " + this.file.getAbsolutePath() + ", header gave conflicting offsets & locations"); + this.recalculateHeader(); + } + // Tuinity end } } } + private final java.nio.file.Path getOversizedChunkPath(ChunkCoordIntPair chunkcoordintpair) { return this.e(chunkcoordintpair); } // Tuinity - OBFHELPER private java.nio.file.Path e(ChunkCoordIntPair chunkcoordintpair) { - String s = "c." + chunkcoordintpair.x + "." + chunkcoordintpair.z + ".mcc"; + String s = "c." + chunkcoordintpair.x + "." + chunkcoordintpair.z + ".mcc"; // Tuinity - diff on change return this.d.resolve(s); } + // Tuinity start + private static ChunkCoordIntPair getOversizedChunkPair(File file) { + String fileName = file.getName(); + + if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { + return null; + } + + String[] split = fileName.split("\\."); + + if (split.length != 4) { + return null; + } + + try { + int x = Integer.parseInt(split[1]); + int z = Integer.parseInt(split[2]); + + return new ChunkCoordIntPair(x, z); + } catch (NumberFormatException ex) { + return null; + } + } + // Tuinity end + @Nullable public synchronized DataInputStream getReadStream(ChunkCoordIntPair chunkCoordIntPair) throws IOException { return a(chunkCoordIntPair);} // Paper - OBFHELPER @Nullable public synchronized DataInputStream a(ChunkCoordIntPair chunkcoordintpair) throws IOException { @@ -147,6 +563,12 @@ public class RegionFile implements AutoCloseable { this.dataFile.read(bytebuffer, (long) (j * 4096)); ((java.nio.Buffer) bytebuffer).flip(); if (bytebuffer.remaining() < 5) { + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", chunkcoordintpair, l, bytebuffer.remaining()); return null; } else { @@ -155,6 +577,12 @@ public class RegionFile implements AutoCloseable { if (i1 == 0) { RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", chunkcoordintpair); + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end return null; } else { int j1 = i1 - 1; @@ -167,9 +595,21 @@ public class RegionFile implements AutoCloseable { return this.a(chunkcoordintpair, b(b0)); } else if (j1 > bytebuffer.remaining()) { RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", chunkcoordintpair, j1, bytebuffer.remaining()); + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end return null; } else if (j1 < 0) { RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, chunkcoordintpair); + // Tuinity start - recalculate header on regionfile corruption + if (this.canRecalcHeader) { + this.recalculateHeader(); + return this.getReadStream(chunkcoordintpair); + } + // Tuinity end return null; } else { return this.a(chunkcoordintpair, b0, a(bytebuffer, j1)); @@ -332,10 +772,15 @@ public class RegionFile implements AutoCloseable { } private ByteBuffer b() { + // Tuinity start - add compressionType param + return this.getOversizedChunkHolderData(this.getRegionFileCompression()); + } + private ByteBuffer getOversizedChunkHolderData(RegionFileCompression compressionType) { + // Tuinity end ByteBuffer bytebuffer = ByteBuffer.allocate(5); bytebuffer.putInt(1); - bytebuffer.put((byte) (this.e.a() | 128)); + bytebuffer.put((byte) (compressionType.a() | 128)); // Tuinity - replace with compressionType ((java.nio.Buffer) bytebuffer).flip(); return bytebuffer; } @@ -372,6 +817,7 @@ public class RegionFile implements AutoCloseable { }; } + private final void flushHeader() throws IOException { this.b(); } // Tuinity - OBFHELPER private void c() throws IOException { ((java.nio.Buffer) this.f).position(0); this.dataFile.write(this.f, 0L); diff --git a/src/main/java/net/minecraft/server/RegionFileBitSet.java b/src/main/java/net/minecraft/server/RegionFileBitSet.java index 1ebdf73cc..cfa3ecb03 100644 --- a/src/main/java/net/minecraft/server/RegionFileBitSet.java +++ b/src/main/java/net/minecraft/server/RegionFileBitSet.java @@ -4,18 +4,42 @@ import java.util.BitSet; public class RegionFileBitSet { - private final BitSet a = new BitSet(); + private final BitSet a = new BitSet(); private final BitSet getBitset() { return this.a; } // Tuinity - OBFHELPER public RegionFileBitSet() {} + public final void allocate(int from, int length) { this.a(from, length); } // Tuinity - OBFHELPER public void a(int i, int j) { this.a.set(i, i + j); } + public final void free(int from, int length) { this.b(from, length); } // Tuinity - OBFHELPER public void b(int i, int j) { this.a.clear(i, i + j); } + // Tuinity start + public final void copyFrom(RegionFileBitSet other) { + BitSet thisBitset = this.getBitset(); + BitSet otherBitset = other.getBitset(); + + for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { + thisBitset.set(i, otherBitset.get(i)); + } + } + + public final boolean tryAllocate(int from, int length) { + BitSet bitset = this.getBitset(); + int firstSet = bitset.nextSetBit(from); + if (firstSet > 0 && firstSet < (from + length)) { + return false; + } + bitset.set(from, from + length); + return true; + } + // Tuinity end + + public final int allocateNewSpace(final int requiredLength) { return this.a(requiredLength); } // Tuinity - OBFHELPER public int a(int i) { int j = 0; diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java index 867dc074b..60b4171a3 100644 --- a/src/main/java/net/minecraft/server/RegionFileCache.java +++ b/src/main/java/net/minecraft/server/RegionFileCache.java @@ -14,12 +14,43 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final public final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap(); private final File b; private final boolean c; + private final boolean isChunkData; // Tuinity RegionFileCache(File file, boolean flag) { + // Tuinity start - add isChunkData param + this(file, flag, false); + } + RegionFileCache(File file, boolean flag, boolean isChunkData) { + this.isChunkData = isChunkData; + // Tuinity end - add isChunkData param this.b = file; this.c = flag; } + // Tuinity start + public static ChunkCoordIntPair getRegionFileCoordinates(File file) { + String fileName = file.getName(); + if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { + return null; + } + + String[] split = fileName.split("\\."); + + if (split.length != 4) { + return null; + } + + try { + int x = Integer.parseInt(split[1]); + int z = Integer.parseInt(split[2]); + + return new ChunkCoordIntPair(x << 5, z << 5); + } catch (NumberFormatException ex) { + return null; + } + } + // Tuinity end + // Paper start public synchronized RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) { // Paper - synchronize for async io @@ -53,9 +84,9 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final this.b.mkdirs(); } - File file = new File(this.b, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca"); + File file = new File(this.b, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Tuinity - diff on change if (existingOnly && !file.exists()) return null; // CraftBukkit - RegionFile regionfile1 = new RegionFile(file, this.b, this.c); + RegionFile regionfile1 = new RegionFile(file, this.b, this.c, this.isChunkData); // Tuinity - allow for chunk regionfiles to regen header this.cache.putAndMoveToFirst(i, regionfile1); // Paper start @@ -144,6 +175,13 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final return null; } // CraftBukkit end + // Tuinity start - Add regionfile parameter + return this.readFromRegionFile(regionfile, chunkcoordintpair); + } + private NBTTagCompound readFromRegionFile(RegionFile regionfile, ChunkCoordIntPair chunkcoordintpair) throws IOException { + // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile + // if we decide to re-read + // Tuinity end try { // Paper DataInputStream datainputstream = regionfile.a(chunkcoordintpair); // Paper start @@ -159,6 +197,16 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final try { if (datainputstream != null) { nbttagcompound = NBTCompressedStreamTools.a(datainputstream); + // Tuinity start - recover from corrupt regionfile header + if (this.isChunkData) { + ChunkCoordIntPair chunkPos = ChunkRegionLoader.getChunkCoordinate(nbttagcompound); + if (!chunkPos.equals(chunkcoordintpair)) { + regionfile.recalculateHeader(); + regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. + return this.readFromRegionFile(regionfile, chunkcoordintpair); + } + } + // Tuinity end return nbttagcompound; } diff --git a/src/main/java/net/minecraft/server/RegionFileCompression.java b/src/main/java/net/minecraft/server/RegionFileCompression.java index 3382d678e..29137f495 100644 --- a/src/main/java/net/minecraft/server/RegionFileCompression.java +++ b/src/main/java/net/minecraft/server/RegionFileCompression.java @@ -13,7 +13,7 @@ import javax.annotation.Nullable; public class RegionFileCompression { - private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); + private static final Int2ObjectMap d = new Int2ObjectOpenHashMap(); static final Int2ObjectMap getCompressionTypes() { return RegionFileCompression.d; } // Tuinity - OBFHELPER public static final RegionFileCompression a = a(new RegionFileCompression(1, GZIPInputStream::new, GZIPOutputStream::new)); public static final RegionFileCompression b = a(new RegionFileCompression(2, InflaterInputStream::new, DeflaterOutputStream::new)); public static final RegionFileCompression c = a(new RegionFileCompression(3, (inputstream) -> { @@ -36,8 +36,8 @@ public class RegionFileCompression { return regionfilecompression; } - @Nullable - public static RegionFileCompression a(int i) { + @Nullable public static RegionFileCompression getByType(int type) { return RegionFileCompression.a(type); } // Tuinity - OBFHELPER + @Nullable public static RegionFileCompression a(int i) { // Tuinity - OBFHELPER return (RegionFileCompression) RegionFileCompression.d.get(i); } @@ -53,6 +53,7 @@ public class RegionFileCompression { return (OutputStream) this.g.wrap(outputstream); } + public final InputStream wrap(InputStream inputstream) throws IOException { return this.a(inputstream); } // Tuinity - OBFHELPER public InputStream a(InputStream inputstream) throws IOException { return (InputStream) this.f.wrap(inputstream); } diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java index e41cb8613..c19ffb925 100644 --- a/src/main/java/net/minecraft/server/Ticket.java +++ b/src/main/java/net/minecraft/server/Ticket.java @@ -5,17 +5,17 @@ import java.util.Objects; public final class Ticket implements Comparable> { private final TicketType a; - private final int b; + private int b; public final void setTicketLevel(final int value) { this.b = value; } // Tuinity - remove final, add set OBFHELPER public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER - private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER + private long d; public final long getCreationTick() { return this.d; } public final void setCreationTick(final long value) { this.d = value; } // Paper - OBFHELPER // Tuinity - OBFHELPER public int priority = 0; // Paper - public long delayUnloadBy; // Paper + boolean isCached; // Tuinity - delay chunk unloads, this defends against really stupid plugins protected Ticket(TicketType tickettype, int i, T t0) { this.a = tickettype; this.b = i; this.identifier = t0; - this.delayUnloadBy = tickettype.loadPeriod; // Paper + // Tuinity - delay chunk unloads } public int compareTo(Ticket ticket) { @@ -64,8 +64,9 @@ public final class Ticket implements Comparable> { this.d = i; } + protected final boolean isExpired(long time) { return this.b(time); } // Tuinity - OBFHELPER protected boolean b(long i) { - long j = delayUnloadBy; // Paper + long j = this.a.b(); // Tuinity - delay chunk unloads return j != 0L && i - this.d > j; } diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java index 5c789b25f..4657b05a4 100644 --- a/src/main/java/net/minecraft/server/TicketType.java +++ b/src/main/java/net/minecraft/server/TicketType.java @@ -26,7 +26,8 @@ public class TicketType { public static final TicketType ASYNC_LOAD = a("async_load", Long::compareTo); // Paper public static final TicketType PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper public static final TicketType URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper - public static final TicketType DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper + public static final TicketType DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads + public static final TicketType REQUIRED_LOAD = a("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail public static TicketType a(String s, Comparator comparator) { return new TicketType<>(s, comparator, 0L); diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 67fda8bd5..e1f1d6e33 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -12,7 +12,7 @@ import org.bukkit.inventory.InventoryHolder; import co.aikar.timings.MinecraftTimings; // Paper import co.aikar.timings.Timing; // Paper -public abstract class TileEntity implements KeyedObject { // Paper +public abstract class TileEntity implements KeyedObject, Cloneable { // Paper // Tuinity public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper // CraftBukkit start - data containers @@ -27,7 +27,7 @@ public abstract class TileEntity implements KeyedObject { // Paper protected BlockPosition position; protected boolean f; @Nullable - private IBlockData c; + private IBlockData c; protected final IBlockData getBlockDataCache() { return this.c; } public final void setBlockDataCache(final IBlockData value) { this.c = value; } // Tuinity - OBFHELPER private boolean g; public TileEntity(TileEntityTypes tileentitytypes) { @@ -35,6 +35,51 @@ public abstract class TileEntity implements KeyedObject { // Paper this.tileType = tileentitytypes; } + // Tuinity start - pushable TE's + public boolean isPushable() { + if (!com.tuinity.tuinity.config.TuinityConfig.pistonsCanPushTileEntities) { + return false; + } + IBlockData block = this.getBlock(); + if (this.isRemoved() || !this.tileType.isValidBlock(block.getBlock())) { + return false; + } + EnumPistonReaction reaction = block.getPushReaction(); + return reaction == EnumPistonReaction.NORMAL || reaction == EnumPistonReaction.PUSH_ONLY; + } + + @Override + protected final TileEntity clone() { + try { + return (TileEntity)super.clone(); + } catch (final Throwable thr) { + if (thr instanceof ThreadDeath) { + throw (ThreadDeath)thr; + } + throw new InternalError(thr); + } + } + + // this method presumes the old TE has been completely dropped from worldstate and has no ties to it anymore (this + // includes players interacting with them) + public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) { + final TileEntity copy = this.clone(); + + copy.world = world; + copy.position = newPos; + + // removed is manually set to false after placing the entity into the world. + copy.setBlockDataCache(blockData); + + return copy; + } + + // updates TE state to its new position + public void onPostPush() { + this.update(); + } + // Tuinity end - pushable TE's + // Paper start private String tileEntityKeyString = null; private MinecraftKey tileEntityKey = null; diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java index 2858ea1f3..453f1301b 100644 --- a/src/main/java/net/minecraft/server/TileEntityBeacon.java +++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java @@ -35,7 +35,7 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic @Nullable public IChatBaseComponent customName; public ChestLock chestLock; - private final IContainerProperties containerProperties; + private IContainerProperties containerProperties; // Tuinity - need non-final for `createCopyForPush` // CraftBukkit start - add fields and methods public PotionEffect getPrimaryEffect() { return (this.primaryEffect != null) ? CraftPotionUtil.toBukkit(new MobEffect(this.primaryEffect, getLevel(), getAmplification(), true, true)) : null; @@ -46,10 +46,10 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic } // CraftBukkit end - public TileEntityBeacon() { - super(TileEntityTypes.BEACON); - this.chestLock = ChestLock.a; - this.containerProperties = new IContainerProperties() { + // Tuinity start - pushable TE's + protected final IContainerProperties getNewContainerProperties() { + // moved from constructor - this should be re-copied if it changes + return new IContainerProperties() { @Override public int getProperty(int i) { switch (i) { @@ -90,6 +90,22 @@ public class TileEntityBeacon extends TileEntity implements ITileInventory, ITic }; } + @Override + public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) { + TileEntityBeacon copy = (TileEntityBeacon)super.createCopyForPush(world, oldPos, newPos, blockData); + + copy.containerProperties = copy.getNewContainerProperties(); // old properties retains reference to old te + + return copy; + } + // Tuinity end - pushable TE's + + public TileEntityBeacon() { + super(TileEntityTypes.BEACON); + this.chestLock = ChestLock.a; + this.containerProperties = this.getNewContainerProperties(); // Tuinity - move into function + } + @Override public void tick() { int i = this.position.getX(); diff --git a/src/main/java/net/minecraft/server/TileEntityBeehive.java b/src/main/java/net/minecraft/server/TileEntityBeehive.java index 6dc91d985..9da356de6 100644 --- a/src/main/java/net/minecraft/server/TileEntityBeehive.java +++ b/src/main/java/net/minecraft/server/TileEntityBeehive.java @@ -12,6 +12,13 @@ public class TileEntityBeehive extends TileEntity implements ITickable { public BlockPosition flowerPos = null; public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold + // Tuinity start - pushable TE's + @Override + public boolean isPushable() { + return false; // TODO until there is a good solution to making the already existing bees in the world re-acquire this position, this cannot be done. + } + // Tuinity end - pushable TE's + public TileEntityBeehive() { super(TileEntityTypes.BEEHIVE); } diff --git a/src/main/java/net/minecraft/server/TileEntityBrewingStand.java b/src/main/java/net/minecraft/server/TileEntityBrewingStand.java index 276eba954..e38a494d7 100644 --- a/src/main/java/net/minecraft/server/TileEntityBrewingStand.java +++ b/src/main/java/net/minecraft/server/TileEntityBrewingStand.java @@ -24,7 +24,7 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl private boolean[] j; private Item k; public int fuelLevel; - protected final IContainerProperties a; + protected IContainerProperties a; protected final void setContainerProperties(IContainerProperties value) { this.a = value; } // Tuinity - OBFHELPER // Tuinity - need non-final for `createCopyForPush` // CraftBukkit start - add fields and methods private int lastTick = MinecraftServer.currentTick; public List transaction = new java.util.ArrayList(); @@ -56,10 +56,10 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl } // CraftBukkit end - public TileEntityBrewingStand() { - super(TileEntityTypes.BREWING_STAND); - this.items = NonNullList.a(5, ItemStack.b); - this.a = new IContainerProperties() { + // Tuinity start - pushable TE's + protected final IContainerProperties getNewContainerProperties() { + // moved from constructor - this should be re-copied if it changes + return new IContainerProperties() { @Override public int getProperty(int i) { switch (i) { @@ -91,6 +91,22 @@ public class TileEntityBrewingStand extends TileEntityContainer implements IWorl }; } + @Override + public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) { + TileEntityBrewingStand copy = (TileEntityBrewingStand)super.createCopyForPush(world, oldPos, newPos, blockData); + + copy.setContainerProperties(copy.getNewContainerProperties()); // old properties retains reference to old te + + return copy; + } + // Tuinity end - pushable TE's + + public TileEntityBrewingStand() { + super(TileEntityTypes.BREWING_STAND); + this.items = NonNullList.a(5, ItemStack.b); + this.a = this.getNewContainerProperties(); + } + @Override protected IChatBaseComponent getContainerName() { return new ChatMessage("container.brewing"); diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java index f6f274389..f9a878bd0 100644 --- a/src/main/java/net/minecraft/server/TileEntityChest.java +++ b/src/main/java/net/minecraft/server/TileEntityChest.java @@ -45,6 +45,22 @@ public class TileEntityChest extends TileEntityLootable { // Paper - Remove ITic } // CraftBukkit end + // Tuinity start + @Override + public boolean isPushable() { + if (!super.isPushable()) { + return false; + } + // what should happen when a double chest is moved is generally just a mess to deal with in the current + // codebase. + IBlockData type = this.getBlock(); + if (type.getBlock() == Blocks.CHEST || type.getBlock() == Blocks.TRAPPED_CHEST) { + return type.get(BlockChest.getChestTypeEnum()) == BlockPropertyChestType.SINGLE; + } + return false; + } + // Tuinity end + protected TileEntityChest(TileEntityTypes tileentitytypes) { super(tileentitytypes); this.items = NonNullList.a(27, ItemStack.b); diff --git a/src/main/java/net/minecraft/server/TileEntityConduit.java b/src/main/java/net/minecraft/server/TileEntityConduit.java index ade830122..7e9470caa 100644 --- a/src/main/java/net/minecraft/server/TileEntityConduit.java +++ b/src/main/java/net/minecraft/server/TileEntityConduit.java @@ -16,15 +16,32 @@ public class TileEntityConduit extends TileEntity implements ITickable { private static final Block[] b = new Block[]{Blocks.PRISMARINE, Blocks.PRISMARINE_BRICKS, Blocks.SEA_LANTERN, Blocks.DARK_PRISMARINE}; public int a; private float c; - private boolean g; + private boolean g; private final void setActive(boolean value) { this.g = value; } // Tuinity - OBFHELPER private boolean h; - private final List i; + private final List i; private final List getPositionsActivating() { return this.i; } // Tuinity - OBFHELPER @Nullable private EntityLiving target; @Nullable private UUID k; private long l; + // Tuinity start - make TE's pushable + @Override + public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) { + final TileEntityConduit copy = (TileEntityConduit)super.createCopyForPush(world, oldPos, newPos, blockData); + + // the following states need to be re-calculated + copy.getPositionsActivating().clear(); + copy.setActive(false); + copy.target = null; + // also set our state because the copy and this share the same activating block list + this.setActive(false); + this.target = null; + + return copy; + } + // Tuinity end - make TE's pushable + public TileEntityConduit() { this(TileEntityTypes.CONDUIT); } diff --git a/src/main/java/net/minecraft/server/TileEntityFurnace.java b/src/main/java/net/minecraft/server/TileEntityFurnace.java index f4f50fb83..59aa2f8a7 100644 --- a/src/main/java/net/minecraft/server/TileEntityFurnace.java +++ b/src/main/java/net/minecraft/server/TileEntityFurnace.java @@ -32,14 +32,14 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I public double cookSpeedMultiplier = 1.0; // Paper - cook speed multiplier API public int cookTime; public int cookTimeTotal; - protected final IContainerProperties b; + protected IContainerProperties b; protected final void setContainerProperties(IContainerProperties value) { this.b = value; } // Tuinity - OBFHELPER // Tuinity - need non-final for `createCopyForPush` private final Object2IntOpenHashMap n; protected final Recipes c; - protected TileEntityFurnace(TileEntityTypes tileentitytypes, Recipes recipes) { - super(tileentitytypes); - this.items = NonNullList.a(3, ItemStack.b); - this.b = new IContainerProperties() { + // Tuinity start - pushable TE's + protected final IContainerProperties getNewContainerProperties() { + // moved from constructor - this should be re-copied if it changes + return new IContainerProperties() { @Override public int getProperty(int i) { switch (i) { @@ -79,7 +79,23 @@ public abstract class TileEntityFurnace extends TileEntityContainer implements I return 4; } }; + } + + @Override + public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) { + TileEntityFurnace copy = (TileEntityFurnace)super.createCopyForPush(world, oldPos, newPos, blockData); + + copy.setContainerProperties(copy.getNewContainerProperties()); // old properties retains reference to old te + + return copy; + } + // Tuinity end - pushable TE's + + protected TileEntityFurnace(TileEntityTypes tileentitytypes, Recipes recipes) { + super(tileentitytypes); + this.items = NonNullList.a(3, ItemStack.b); this.n = new Object2IntOpenHashMap(); + this.b = this.getNewContainerProperties(); this.c = recipes; } diff --git a/src/main/java/net/minecraft/server/TileEntityJukeBox.java b/src/main/java/net/minecraft/server/TileEntityJukeBox.java index 33c7dc56d..75eb1b8b2 100644 --- a/src/main/java/net/minecraft/server/TileEntityJukeBox.java +++ b/src/main/java/net/minecraft/server/TileEntityJukeBox.java @@ -4,6 +4,13 @@ public class TileEntityJukeBox extends TileEntity implements Clearable { private ItemStack a; + // Tuinity start - pushable TE's + @Override + public boolean isPushable() { + return false; // disabled due to buggy sound + } + // Tuinity end - pushable TE's + public TileEntityJukeBox() { super(TileEntityTypes.JUKEBOX); this.a = ItemStack.b; diff --git a/src/main/java/net/minecraft/server/TileEntityLectern.java b/src/main/java/net/minecraft/server/TileEntityLectern.java index b2ceb6c17..b955d5d66 100644 --- a/src/main/java/net/minecraft/server/TileEntityLectern.java +++ b/src/main/java/net/minecraft/server/TileEntityLectern.java @@ -17,7 +17,7 @@ import org.bukkit.inventory.InventoryHolder; public class TileEntityLectern extends TileEntity implements Clearable, ITileInventory, ICommandListener { // CraftBukkit - ICommandListener // CraftBukkit start - add fields and methods - public final IInventory inventory = new LecternInventory(); + public IInventory inventory = new LecternInventory(); // Tuinity - need non-final for `createCopyForPush` public class LecternInventory implements IInventory { public List transaction = new ArrayList<>(); @@ -137,29 +137,48 @@ public class TileEntityLectern extends TileEntity implements Clearable, ITileInv @Override public void clear() {} }; - private final IContainerProperties containerProperties = new IContainerProperties() { - @Override - public int getProperty(int i) { - return i == 0 ? TileEntityLectern.this.page : 0; - } + // Tuinity start - pushable TE's + private IContainerProperties containerProperties = this.getNewContainerProperties(); // Tuinity - need non-final for `createCopyForPush` + + protected final IContainerProperties getNewContainerProperties() { + return new IContainerProperties() { + @Override + public int getProperty(int i) { + return i == 0 ? TileEntityLectern.this.page : 0; + } + + @Override + public void setProperty(int i, int j) { + if (i == 0) { + TileEntityLectern.this.setPage(j); + } - @Override - public void setProperty(int i, int j) { - if (i == 0) { - TileEntityLectern.this.setPage(j); } - } + @Override + public int a() { + return 1; + } + }; + } + // Tuinity end - pushable TE's - @Override - public int a() { - return 1; - } - }; private ItemStack book; private int page; private int maxPage; + // Tuinity start - pushable TE's + @Override + public TileEntity createCopyForPush(WorldServer world, BlockPosition oldPos, BlockPosition newPos, IBlockData blockData) { + TileEntityLectern copy = (TileEntityLectern)super.createCopyForPush(world, oldPos, newPos, blockData); + + copy.inventory = copy.new LecternInventory(); + copy.containerProperties = copy.getNewContainerProperties(); // old properties retains reference to old te + + return copy; + } + // Tuinity end - pushable TE's + public TileEntityLectern() { super(TileEntityTypes.LECTERN); this.book = ItemStack.b; diff --git a/src/main/java/net/minecraft/server/TileEntityPiston.java b/src/main/java/net/minecraft/server/TileEntityPiston.java index e7b7e468f..38d0e841c 100644 --- a/src/main/java/net/minecraft/server/TileEntityPiston.java +++ b/src/main/java/net/minecraft/server/TileEntityPiston.java @@ -5,10 +5,10 @@ import java.util.List; public class TileEntityPiston extends TileEntity implements ITickable { - private IBlockData a; + private IBlockData a; protected final IBlockData getBlockData() { return this.a; } // Tuinity - OBFHELPER private EnumDirection b; private boolean c; - private boolean g; + private boolean g; protected final boolean isSource() { return this.g; } // Tuinity - OBFHELPER private static final ThreadLocal h = ThreadLocal.withInitial(() -> { return null; }); @@ -16,12 +16,27 @@ public class TileEntityPiston extends TileEntity implements ITickable { private float j; private long k; + // Tuinity start - pushable TE's + private TileEntity tileEntity; + + @Override + public boolean isPushable() { + return false; // fuck no. + } + // Tuinity end - pushable TE's + public TileEntityPiston() { super(TileEntityTypes.PISTON); } public TileEntityPiston(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1) { + // Tuinity start - add tileEntity parameter + this(iblockdata, enumdirection, flag, flag1, null); + } + public TileEntityPiston(IBlockData iblockdata, EnumDirection enumdirection, boolean flag, boolean flag1, TileEntity tileEntity) { this(); + this.tileEntity = tileEntity; + // Tuinity end - add tileEntity parameter this.a = iblockdata; this.b = enumdirection; this.c = flag; @@ -30,7 +45,7 @@ public class TileEntityPiston extends TileEntity implements ITickable { @Override public NBTTagCompound b() { - return this.save(new NBTTagCompound()); + return this.save(new NBTTagCompound(), false); // Tuinity - clients don't need the copied tile entity. } public boolean d() { @@ -238,7 +253,25 @@ public class TileEntityPiston extends TileEntity implements ITickable { iblockdata = Block.b(this.a, (GeneratorAccess) this.world, this.position); } - this.world.setTypeAndData(this.position, iblockdata, 3); + // Tuinity start - pushable TE's + if ((iblockdata.isAir() && !this.isSource()) && !this.getBlockData().isAir()) { + // if the block can't exist at the location anymore, we need to fire drops for it, as + // setTypeAndData wont. + + // careful - the previous pos is moving_piston, which wont fire drops. So we're safe from dupes. + // but the setAir should be before the drop. + this.world.setAir(this.position, false); + Block.dropItems(this.getBlockData(), this.world, this.position, null, null, ItemStack.NULL_ITEM); + } else { + // need to set to air before else the setTypeAndData call will create a new TE and override + // the old one + this.world.setTypeAndDataRaw(this.position, Blocks.AIR.getBlockData(), null); + this.world.setTypeAndData(this.position, iblockdata, 3, iblockdata.getBlock() == this.getBlockData().getBlock() ? this.tileEntity : null); + } + if (this.tileEntity != null && this.world.getType(this.position).getBlock() == this.getBlockData().getBlock()) { + this.tileEntity.onPostPush(); + } + // Tuinity end - pushable TE's this.world.a(this.position, iblockdata.getBlock(), this.position); } } @@ -263,7 +296,12 @@ public class TileEntityPiston extends TileEntity implements ITickable { iblockdata = (IBlockData) iblockdata.set(BlockProperties.C, false); } - this.world.setTypeAndData(this.position, iblockdata, 67); + // Tuinity start - pushable TE's + this.world.setTypeAndData(this.position, iblockdata, 67, this.tileEntity); + if (this.tileEntity != null && this.world.getType(this.position).getBlock() == this.getBlockData().getBlock()) { + this.tileEntity.onPostPush(); + } + // Tuinity end - pushable TE's this.world.a(this.position, iblockdata.getBlock(), this.position); } } @@ -290,16 +328,34 @@ public class TileEntityPiston extends TileEntity implements ITickable { this.j = this.i; this.c = nbttagcompound.getBoolean("extending"); this.g = nbttagcompound.getBoolean("source"); + // Tuinity start - pushable TE's + if (nbttagcompound.hasKey("Tuinity.tileEntity")) { + NBTTagCompound compound = nbttagcompound.getCompound("Tuinity.tileEntity"); + if (!compound.isEmpty()) { + this.tileEntity = TileEntity.create(this.getBlockData(), compound); + } + } + // Tuinity end - pushable TE's } @Override public NBTTagCompound save(NBTTagCompound nbttagcompound) { + // Tuinity start - add saveTile param + return this.save(nbttagcompound, true); + } + public NBTTagCompound save(NBTTagCompound nbttagcompound, boolean saveTile) { + // Tuinity end - add saveTile param super.save(nbttagcompound); nbttagcompound.set("blockState", GameProfileSerializer.a(this.a)); nbttagcompound.setInt("facing", this.b.c()); nbttagcompound.setFloat("progress", this.j); nbttagcompound.setBoolean("extending", this.c); nbttagcompound.setBoolean("source", this.g); + // Tuinity start - pushable TE's + if (saveTile && this.tileEntity != null) { + nbttagcompound.set("Tuinity.tileEntity", this.tileEntity.save(new NBTTagCompound())); + } + // Tuinity end - pushable TE's return nbttagcompound; } diff --git a/src/main/java/net/minecraft/server/UserCache.java b/src/main/java/net/minecraft/server/UserCache.java index a03839702..c68af7562 100644 --- a/src/main/java/net/minecraft/server/UserCache.java +++ b/src/main/java/net/minecraft/server/UserCache.java @@ -51,6 +51,10 @@ public class UserCache { private final File h; private static final TypeToken> i = new TypeToken>() { }; + // Tuinity start + protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); + protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); + // Tuinity end public UserCache(GameProfileRepository gameprofilerepository, File file) { this.g = gameprofilerepository; @@ -98,7 +102,7 @@ public class UserCache { this.a(gameprofile, (Date) null); } - private synchronized void a(GameProfile gameprofile, Date date) { // Paper - synchronize + private void a(GameProfile gameprofile, Date date) { // Paper - synchronize // Tuinity - allow better concurrency UUID uuid = gameprofile.getId(); if (date == null) { @@ -111,6 +115,7 @@ public class UserCache { UserCache.UserCacheEntry usercache_usercacheentry = new UserCache.UserCacheEntry(gameprofile, date); + try { this.stateLock.lock(); // Tuinity - allow better concurrency //if (this.e.containsKey(uuid)) { // Paper UserCache.UserCacheEntry usercache_usercacheentry1 = (UserCache.UserCacheEntry) this.e.get(uuid); if (usercache_usercacheentry1 != null) { // Paper @@ -122,12 +127,14 @@ public class UserCache { this.d.put(gameprofile.getName().toLowerCase(Locale.ROOT), usercache_usercacheentry); this.e.put(uuid, usercache_usercacheentry); this.f.addFirst(gameprofile); + } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.c(); // Spigot - skip saving if disabled } @Nullable - public synchronized GameProfile getProfile(String s) { // Paper - synchronize + public GameProfile getProfile(String s) { // Paper - synchronize // Tuinity start - allow better concurrency String s1 = s.toLowerCase(Locale.ROOT); + boolean stateLocked = true; try { this.stateLock.lock(); // Tuinity - allow better concurrency UserCache.UserCacheEntry usercache_usercacheentry = (UserCache.UserCacheEntry) this.d.get(s1); if (usercache_usercacheentry != null && (new Date()).getTime() >= usercache_usercacheentry.c.getTime()) { @@ -135,6 +142,7 @@ public class UserCache { this.d.remove(usercache_usercacheentry.a().getName().toLowerCase(Locale.ROOT)); this.f.remove(usercache_usercacheentry.a()); usercache_usercacheentry = null; + stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency } GameProfile gameprofile; @@ -143,8 +151,11 @@ public class UserCache { gameprofile = usercache_usercacheentry.a(); this.f.remove(gameprofile); this.f.addFirst(gameprofile); + stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency } else { + try { this.lookupLock.lock(); // Tuinity - allow better concurrency gameprofile = a(this.g, s); // Spigot - use correct case for offline players + } finally { this.lookupLock.unlock(); } // Tuinity - allow better concurrency if (gameprofile != null) { this.a(gameprofile); usercache_usercacheentry = (UserCache.UserCacheEntry) this.d.get(s1); @@ -153,6 +164,7 @@ public class UserCache { if( !org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly ) this.c(); // Spigot - skip saving if disabled return usercache_usercacheentry == null ? null : usercache_usercacheentry.a(); + } finally { if (stateLocked) { stateLocked = false; this.stateLock.unlock(); } } // Tuinity - allow better concurrency } // Paper start @@ -252,6 +264,7 @@ public class UserCache { } private List a(int i) { + try { this.stateLock.lock(); // Tuinity - allow better concurrency List list = Lists.newArrayList(); List list1 = Lists.newArrayList(Iterators.limit(this.f.iterator(), i)); Iterator iterator = list1.iterator(); @@ -266,6 +279,7 @@ public class UserCache { } return list; + } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency } class UserCacheEntry { diff --git a/src/main/java/net/minecraft/server/Vec3D.java b/src/main/java/net/minecraft/server/Vec3D.java index 3048ba008..84858ba39 100644 --- a/src/main/java/net/minecraft/server/Vec3D.java +++ b/src/main/java/net/minecraft/server/Vec3D.java @@ -4,7 +4,7 @@ import java.util.EnumSet; public class Vec3D implements IPosition { - public static final Vec3D a = new Vec3D(0.0D, 0.0D, 0.0D); + public static final Vec3D a = new Vec3D(0.0D, 0.0D, 0.0D); public static Vec3D getZeroVector() { return Vec3D.a; } // Tuinity - OBFHELPER public final double x; public final double y; public final double z; @@ -61,6 +61,7 @@ public class Vec3D implements IPosition { return this.add(-d0, -d1, -d2); } + public final Vec3D add(Vec3D vec3d) { return this.e(vec3d); } // Tuinity - OBFHELPER public Vec3D e(Vec3D vec3d) { return this.add(vec3d.x, vec3d.y, vec3d.z); } @@ -109,10 +110,12 @@ public class Vec3D implements IPosition { return new Vec3D(this.x * d0, this.y * d1, this.z * d2); } + public final double magnitude() { return this.f(); } // Tuinity - OBFHELPER public double f() { return (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } + public final double magnitudeSquared() { return this.g(); } // Tuinity - OBFHELPER public double g() { return this.x * this.x + this.y * this.y + this.z * this.z; } diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java index 303f6b095..c1e149f20 100644 --- a/src/main/java/net/minecraft/server/VillagePlace.java +++ b/src/main/java/net/minecraft/server/VillagePlace.java @@ -155,7 +155,7 @@ public class VillagePlace extends RegionFileSection { data = this.getData(chunkcoordintpair); } com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, - chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); + chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority } } // Paper end diff --git a/src/main/java/net/minecraft/server/VoxelShape.java b/src/main/java/net/minecraft/server/VoxelShape.java index c2b8c9820..3a98e242e 100644 --- a/src/main/java/net/minecraft/server/VoxelShape.java +++ b/src/main/java/net/minecraft/server/VoxelShape.java @@ -8,11 +8,11 @@ import javax.annotation.Nullable; public abstract class VoxelShape { - protected final VoxelShapeDiscrete a; + protected final VoxelShapeDiscrete a; public final VoxelShapeDiscrete getShape() { return this.a; } // Tuinity - OBFHELPER @Nullable private VoxelShape[] b; - VoxelShape(VoxelShapeDiscrete voxelshapediscrete) { + protected VoxelShape(VoxelShapeDiscrete voxelshapediscrete) { // Tuinity this.a = voxelshapediscrete; } @@ -51,6 +51,12 @@ public abstract class VoxelShape { return (VoxelShape) (this.isEmpty() ? VoxelShapes.a() : new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2))); } + // Tuinity start - optimise multi-aabb shapes + public boolean intersects(final AxisAlignedBB axisalingedbb) { + return VoxelShapes.applyOperation(this, new com.tuinity.tuinity.voxel.AABBVoxelShape(axisalingedbb), OperatorBoolean.AND); + } + // Tuinity end - optimise multi-aabb shapes + public VoxelShape c() { VoxelShape[] avoxelshape = new VoxelShape[]{VoxelShapes.a()}; @@ -70,6 +76,7 @@ public abstract class VoxelShape { }, true); } + public final List getBoundingBoxesRepresentation() { return this.d(); } // Tuinity - OBFHELPER public List d() { List list = Lists.newArrayList(); diff --git a/src/main/java/net/minecraft/server/VoxelShapeArray.java b/src/main/java/net/minecraft/server/VoxelShapeArray.java index caf297fe9..8d68c783f 100644 --- a/src/main/java/net/minecraft/server/VoxelShapeArray.java +++ b/src/main/java/net/minecraft/server/VoxelShapeArray.java @@ -3,6 +3,7 @@ package net.minecraft.server; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import it.unimi.dsi.fastutil.doubles.DoubleList; import java.util.Arrays; +import java.util.List; public final class VoxelShapeArray extends VoxelShape { @@ -10,11 +11,25 @@ public final class VoxelShapeArray extends VoxelShape { private final DoubleList c; private final DoubleList d; + // Tuinity start - optimise multi-aabb shapes + static final AxisAlignedBB[] EMPTY = new AxisAlignedBB[0]; + final AxisAlignedBB[] boundingBoxesRepresentation; + + final double offsetX; + final double offsetY; + final double offsetZ; + // Tuinity end - optimise multi-aabb shapes + protected VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, double[] adouble, double[] adouble1, double[] adouble2) { this(voxelshapediscrete, (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble, voxelshapediscrete.b() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble1, voxelshapediscrete.c() + 1)), (DoubleList) DoubleArrayList.wrap(Arrays.copyOf(adouble2, voxelshapediscrete.d() + 1))); } VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2) { + // Tuinity start - optimise multi-aabb shapes + this(voxelshapediscrete, doublelist, doublelist1, doublelist2, null, null, 0.0, 0.0, 0.0); + } + VoxelShapeArray(VoxelShapeDiscrete voxelshapediscrete, DoubleList doublelist, DoubleList doublelist1, DoubleList doublelist2, VoxelShapeArray original, AxisAlignedBB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) { + // Tuinity end - optimise multi-aabb shapes super(voxelshapediscrete); int i = voxelshapediscrete.b() + 1; int j = voxelshapediscrete.c() + 1; @@ -27,6 +42,18 @@ public final class VoxelShapeArray extends VoxelShape { } else { throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")); } + // Tuinity start - optimise multi-aabb shapes + this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.getBoundingBoxesRepresentation().toArray(EMPTY) : boundingBoxesRepresentation; // Tuinity - optimise multi-aabb shapes + if (original == null) { + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + } else { + this.offsetX = offsetX + original.offsetX; + this.offsetY = offsetY + original.offsetY; + this.offsetZ = offsetZ + original.offsetZ; + } + // Tuinity end - optimise multi-aabb shapes } @Override @@ -42,4 +69,49 @@ public final class VoxelShapeArray extends VoxelShape { throw new IllegalArgumentException(); } } + + // Tuinity start - optimise multi-aabb shapes + @Override + public VoxelShape a(double d0, double d1, double d2) { + if (this == VoxelShapes.getEmptyShape() || this.boundingBoxesRepresentation.length == 0) { + return this; + } + return new VoxelShapeArray(this.a, new DoubleListOffset(this.a(EnumDirection.EnumAxis.X), d0), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Y), d1), new DoubleListOffset(this.a(EnumDirection.EnumAxis.Z), d2), this, this.boundingBoxesRepresentation, d0, d1, d2); + } + + public final AxisAlignedBB[] getBoundingBoxesRepresentationRaw() { + return this.boundingBoxesRepresentation; + } + + public final double getOffsetX() { + return this.offsetX; + } + + public final double getOffsetY() { + return this.offsetY; + } + + public final double getOffsetZ() { + return this.offsetZ; + } + + public final boolean intersects(AxisAlignedBB axisalingedbb) { + double minX = axisalingedbb.minX - this.offsetX; + double maxX = axisalingedbb.maxX - this.offsetX; + double minY = axisalingedbb.minY - this.offsetY; + double maxY = axisalingedbb.maxY - this.offsetY; + double minZ = axisalingedbb.minZ - this.offsetZ; + double maxZ = axisalingedbb.maxZ - this.offsetZ; + + // this can be optimised by checking an "overall shape" + + for (AxisAlignedBB boundingBox : this.boundingBoxesRepresentation) { + if (boundingBox.intersects(minX, minY, minZ, maxX, maxY, maxZ)) { + return true; + } + } + + return false; + } + // Tuinity end - optimise multi-aabb shapes } diff --git a/src/main/java/net/minecraft/server/VoxelShapes.java b/src/main/java/net/minecraft/server/VoxelShapes.java index 1fa7061f7..52aee91d2 100644 --- a/src/main/java/net/minecraft/server/VoxelShapes.java +++ b/src/main/java/net/minecraft/server/VoxelShapes.java @@ -17,18 +17,81 @@ public final class VoxelShapes { voxelshapebitset.a(0, 0, 0, true, true); return new VoxelShapeCube(voxelshapebitset); - }); + }); public static final VoxelShape getFullUnoptimisedCube() { return VoxelShapes.b; } // Tuinity - OBFHELPER public static final VoxelShape a = create(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); + private static final VoxelShape c = new VoxelShapeArray(new VoxelShapeBitSet(0, 0, 0), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D}), new DoubleArrayList(new double[]{0.0D})); static final VoxelShape getEmptyShape() { return VoxelShapes.c; } // Tuinity - OBFHELPER + + // Tuinity start - optimise voxelshapes + public static boolean isEmpty(VoxelShape voxelshape) { + // helper function for determining empty shapes fast + return voxelshape == getEmptyShape() || voxelshape.isEmpty(); + } + // Tuinity end - optimise voxelshapes public static final VoxelShape empty() {return a();} // Paper - OBFHELPER public static VoxelShape a() { return VoxelShapes.c; } + static final com.tuinity.tuinity.voxel.AABBVoxelShape optimisedFullCube = new com.tuinity.tuinity.voxel.AABBVoxelShape(new AxisAlignedBB(0, 0, 0, 1.0, 1.0, 1.0)); // Tuinity - optimise voxelshape + + // Tuinity start - optimise voxelshapes + public static void addBoxesToIfIntersects(VoxelShape shape, AxisAlignedBB aabb, java.util.List list) { + if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { + com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape; + if (shapeCasted.aabb.intersects(aabb)) { + list.add(shapeCasted.aabb); + } + } else if (shape instanceof VoxelShapeArray) { + VoxelShapeArray shapeCasted = (VoxelShapeArray)shape; + double minX = aabb.minX - shapeCasted.offsetX; + double maxX = aabb.maxX - shapeCasted.offsetX; + double minY = aabb.minY - shapeCasted.offsetY; + double maxY = aabb.maxY - shapeCasted.offsetY; + double minZ = aabb.minZ - shapeCasted.offsetZ; + double maxZ = aabb.maxZ - shapeCasted.offsetZ; + + // this can be optimised by checking an "overall shape" + + for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) { + if (boundingBox.intersects(minX, minY, minZ, maxX, maxY, maxZ)) { + list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ)); + } + } + } else { + java.util.List boxes = shape.getBoundingBoxesRepresentation(); + for (int i = 0, len = boxes.size(); i < len; ++i) { + AxisAlignedBB box = boxes.get(i); + if (box.intersects(aabb)) { + list.add(box); + } + } + } + } + + public static void addBoxesTo(VoxelShape shape, java.util.List list) { + if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { + com.tuinity.tuinity.voxel.AABBVoxelShape shapeCasted = (com.tuinity.tuinity.voxel.AABBVoxelShape)shape; + list.add(shapeCasted.aabb); + } else if (shape instanceof VoxelShapeArray) { + VoxelShapeArray shapeCasted = (VoxelShapeArray)shape; + + for (AxisAlignedBB boundingBox : shapeCasted.boundingBoxesRepresentation) { + list.add(boundingBox.offset(shapeCasted.offsetX, shapeCasted.offsetY, shapeCasted.offsetZ)); + } + } else { + java.util.List boxes = shape.getBoundingBoxesRepresentation(); + for (int i = 0, len = boxes.size(); i < len; ++i) { + AxisAlignedBB box = boxes.get(i); + list.add(box); + } + } + } + // Tuinity end - optimise voxelshapes + public static final VoxelShape fullCube() {return b();} // Paper - OBFHELPER public static VoxelShape b() { - return VoxelShapes.b; + return VoxelShapes.optimisedFullCube; // Tuinity - optimise voxelshape } public static VoxelShape create(double d0, double d1, double d2, double d3, double d4, double d5) { @@ -67,7 +130,7 @@ public final class VoxelShapes { return new VoxelShapeCube(voxelshapebitset); } } else { - return new VoxelShapeArray(VoxelShapes.b.a, new double[]{axisalignedbb.minX, axisalignedbb.maxX}, new double[]{axisalignedbb.minY, axisalignedbb.maxY}, new double[]{axisalignedbb.minZ, axisalignedbb.maxZ}); + return new com.tuinity.tuinity.voxel.AABBVoxelShape(axisalignedbb); // Tuinity - optimise VoxelShapes for single AABB shapes } } @@ -132,6 +195,20 @@ public final class VoxelShapes { public static final boolean applyOperation(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { return VoxelShapes.c(voxelshape, voxelshape1, operatorboolean); } // Paper - OBFHELPER public static boolean c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { + // Tuinity start - optimise voxelshape + if (operatorboolean == OperatorBoolean.AND) { + if (voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { + return ((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb.intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb); + } else if (voxelshape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape1 instanceof VoxelShapeArray) { + return ((VoxelShapeArray)voxelshape1).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape).aabb); + } else if (voxelshape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && voxelshape instanceof VoxelShapeArray) { + return ((VoxelShapeArray)voxelshape).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)voxelshape1).aabb); + } + } + return abstract_c(voxelshape, voxelshape1, operatorboolean); + } + public static boolean abstract_c(VoxelShape voxelshape, VoxelShape voxelshape1, OperatorBoolean operatorboolean) { + // Tuinity end - optimise voxelshape if (operatorboolean.apply(false, false)) { throw (IllegalArgumentException) SystemUtils.c(new IllegalArgumentException()); } else if (voxelshape == voxelshape1) { @@ -314,6 +391,7 @@ public final class VoxelShapes { } } + public static boolean combinationOccludes(VoxelShape voxelshape, VoxelShape voxelshape1) { return b(voxelshape, voxelshape1); } // Tuinity - OBFHELPER public static boolean b(VoxelShape voxelshape, VoxelShape voxelshape1) { return voxelshape != b() && voxelshape1 != b() ? (voxelshape.isEmpty() && voxelshape1.isEmpty() ? false : !c(b(), b(voxelshape, voxelshape1, OperatorBoolean.OR), OperatorBoolean.ONLY_FIRST)) : true; } diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 5f81997db..4a956f368 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -94,6 +94,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config + public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPosition lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; @@ -121,6 +123,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig((((WorldDataServer)worlddatamutable).getName()), this.spigotConfig); // Paper this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config this.generator = gen; this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit @@ -397,13 +400,38 @@ public abstract class World implements GeneratorAccess, AutoCloseable { } } + // Tuinity start + // Does not affect TE. This simply just a raw set type - runs no logic. + final void setTypeAndDataRaw(final BlockPosition pos, final IBlockData blockData, final TileEntity tileEntity) { + this.getChunkAt(pos.getX() >> 4, pos.getZ() >> 4).setTypeAndDataRaw(pos, blockData); + if (tileEntity == null) { + this.removeTileEntity(pos); + } else { + this.setTileEntity(pos, tileEntity); + } + ((WorldServer)this).getChunkProvider().flagDirty(pos); + } + // Tuinity end + @Override public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { - return this.a(blockposition, iblockdata, i, 512); + // Tuinity start - add tileEntity parameter + return this.setTypeAndData(blockposition, iblockdata, i, null); + } + // Up to the caller to handle previous tile state. + public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i, TileEntity tileEntity) { + return this.setTypeAndData(blockposition, iblockdata, i, 512, tileEntity); + // Tuinity end - add tileEntity parameter } @Override public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) { + // Tuinity start - add tileEntity parameter + return this.setTypeAndData(blockposition, iblockdata, i, j, null); + } + public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i, int j, TileEntity tileEntity) { + // Tuinity end - add tileEntity parameter + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async set type call"); // Tuinity // CraftBukkit start - tree generation if (this.captureTreeGeneration) { // Paper start @@ -436,7 +464,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { } // CraftBukkit end - IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0); // CraftBukkit custom NO_PLACE flag + IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0, tileEntity); // CraftBukkit custom NO_PLACE flag // Tuinity - add tileEntity parameter this.chunkPacketBlockController.onBlockChange(this, blockposition, iblockdata, iblockdata1, i); // Paper - Anti-Xray if (iblockdata1 == null) { @@ -504,6 +532,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { // CraftBukkit start - Split off from above in order to directly send client and physic updates public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i, int j) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity IBlockData iblockdata = newBlock; IBlockData iblockdata1 = oldBlock; IBlockData iblockdata2 = actualBlock; @@ -941,6 +970,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { return; // Paper end } + MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick } // Paper start - Prevent armor stands from doing entity lookups @Override @@ -1036,7 +1066,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { while (iterator.hasNext()) { TileEntity tileentity1 = (TileEntity) iterator.next(); - if (tileentity1.getPosition().equals(blockposition)) { + if (tileentity != tileentity1 && tileentity1.getPosition().equals(blockposition)) { // Tuinity - don't remove us if we double set... tileentity1.an_(); iterator.remove(); } @@ -1125,6 +1155,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { // copied from below List list = Lists.newArrayList(); + // Tuinity start - add list parameter + return this.getHardCollidingEntities(entity, axisalignedbb, predicate, list); + } + public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate, List list) { + // Tuinity end - add list parameter int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D); int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D); @@ -1148,8 +1183,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable { @Override public List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { - this.getMethodProfiler().c("getEntities"); + // Tuinity start - add list parameter List list = Lists.newArrayList(); + return this.getEntities(entity, axisalignedbb, predicate, list); + } + public List getEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate, List list) { + // Tuinity end - add list parameter + this.getMethodProfiler().c("getEntities"); int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.maxX + 2.0D) / 16.0D); int k = MathHelper.floor((axisalignedbb.minZ - 2.0D) / 16.0D); diff --git a/src/main/java/net/minecraft/server/WorldBorder.java b/src/main/java/net/minecraft/server/WorldBorder.java index b651eb87b..5cba3b0e6 100644 --- a/src/main/java/net/minecraft/server/WorldBorder.java +++ b/src/main/java/net/minecraft/server/WorldBorder.java @@ -47,11 +47,43 @@ public class WorldBorder { return axisalignedbb.maxX > this.e() && axisalignedbb.minX < this.g() && axisalignedbb.maxZ > this.f() && axisalignedbb.minZ < this.h(); } + // Tuinity start - optimise collisions + // determines whether we are colliding with one of the wordborder faces. + public final boolean isCollidingOnBorderEdge(AxisAlignedBB boundingBox) { + return this.isCollidingOnBorderEdge(boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); + } + + public final boolean isCollidingOnBorderEdge(double boxMinX, double boxMaxX, double boxMinZ, double boxMaxZ) { + double minX = this.getMinX() - 1.0E-7; + double maxX = this.getMaxX() + 1.0E-7; + + double minZ = this.getMinZ() - 1.0E-7; + double maxZ = this.getMaxZ() + 1.0E-7; + + return + // First, check if the worldborder is enclosing the specified box. + // We check this first as it's most likely to fail. + !(minX < boxMinX && maxX > boxMaxX && minZ < boxMinZ && maxZ > boxMaxZ) + && + + // Now we verify if we're even intersecting. + (minX < boxMaxX && maxX > boxMinX && minZ < boxMaxZ && maxZ > boxMinZ) + && + + // Now verify that the worldborder isn't being enclosed. + // This is never expected to happen, but is left here to ensure our logic + // is right 100% of the time. + !(boxMinX < minX && boxMaxX > maxX && boxMinZ < minZ && boxMaxZ > maxZ) + ; + } + // Tuinity end - optimise collisions + public double a(Entity entity) { return this.b(entity.locX(), entity.locZ()); } - + public final VoxelShape asVoxelShape(){ return c();} // Paper - OBFHELPER + public final VoxelShape getCollisionShape() { return this.c(); } // Tuinity - OBFHELPER public VoxelShape c() { return this.j.m(); } @@ -67,18 +99,22 @@ public class WorldBorder { return Math.min(d6, d3); } + public final double getMinX() { return this.e(); } // Tuinity - OBFHELPER public double e() { return this.j.a(); } + public final double getMinZ() { return this.f(); } // Tuinity - OBFHELPER public double f() { return this.j.c(); } + public final double getMaxX() { return this.g(); } // Tuinity - OBFHELPER public double g() { return this.j.b(); } + public final double getMaxZ() { return this.h(); } // Tuinity - OBFHELPER public double h() { return this.j.d(); } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index 5ee9d3009..a7a63bb06 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -55,7 +55,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { public static final BlockPosition a = new BlockPosition(100, 50, 0); private static final Logger LOGGER = LogManager.getLogger(); - public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); + public final Int2ObjectMap entitiesById = new Int2ObjectLinkedOpenHashMap(); final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet entitiesForIteration = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(2048, 0.5f, 2048, 0.2); // Tuinity - make removing entities while ticking safe private final Map entitiesByUUID = Maps.newHashMap(); private final Queue entitiesToAdd = Queues.newArrayDeque(); public final List players = Lists.newArrayList(); // Paper - private -> public @@ -79,7 +79,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { private final PortalTravelAgent portalTravelAgent; private final TickListServer nextTickListBlock; private final TickListServer nextTickListFluid; - private final Set navigators; + private final Set navigators; final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigatorsForIteration = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(2048, 0.5f, 2048, 0.2); // Tuinity - make removing entities while ticking safe protected final PersistentRaid persistentRaid; private final ObjectLinkedOpenHashSet L; private boolean ticking; @@ -200,6 +200,100 @@ public class WorldServer extends World implements GeneratorAccessSeed { } // Paper end - rewrite ticklistserver + // Tuinity start + public final boolean areChunksLoadedForMove(AxisAlignedBB axisalignedbb) { + // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override + // ICollisionAccess methods for VoxelShapes) + // be more strict too, add a block (dumb plugins in move events?) + int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3; + int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3; + + int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3; + int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; + + int minChunkX = minBlockX >> 4; + int maxChunkX = maxBlockX >> 4; + + int minChunkZ = minBlockZ >> 4; + int maxChunkZ = maxBlockZ >> 4; + + ChunkProviderServer chunkProvider = this.getChunkProvider(); + + for (int cx = minChunkX; cx <= maxChunkX; ++cx) { + for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { + if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { + return false; + } + } + } + + return true; + } + + public final void loadChunksForMoveAsync(AxisAlignedBB axisalignedbb, double toX, double toZ, + java.util.function.Consumer> onLoad) { + if (Thread.currentThread() != this.serverThread) { + this.getChunkProvider().serverThreadQueue.execute(() -> { + this.loadChunksForMoveAsync(axisalignedbb, toX, toZ, onLoad); + }); + return; + } + List ret = new java.util.ArrayList<>(); + it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); + + int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 3; + int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 3; + + int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 3; + int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; + + int minChunkX = minBlockX >> 4; + int maxChunkX = maxBlockX >> 4; + + int minChunkZ = minBlockZ >> 4; + int maxChunkZ = maxBlockZ >> 4; + + ChunkProviderServer chunkProvider = this.getChunkProvider(); + + int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); + int[] loadedChunks = new int[1]; + + Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); + + java.util.function.Consumer consumer = (IChunkAccess chunk) -> { + if (chunk != null) { + int ticketLevel = Math.max(33, chunkProvider.playerChunkMap.getUpdatingChunk(chunk.getPos().pair()).getTicketLevel()); + ret.add(chunk); + ticketLevels.add(ticketLevel); + chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); + } + if (++loadedChunks[0] == requiredChunks) { + try { + onLoad.accept(java.util.Collections.unmodifiableList(ret)); + } finally { + for (int i = 0, len = ret.size(); i < len; ++i) { + ChunkCoordIntPair chunkPos = ret.get(i).getPos(); + int ticketLevel = ticketLevels.getInt(i); + + chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); + chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); + } + } + } + }; + + for (int cx = minChunkX; cx <= maxChunkX; ++cx) { + for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { + chunkProvider.getChunkAtAsynchronously(cx, cz, ChunkStatus.FULL, true, false, consumer); + } + } + } + // Tuinity end + + // Tuinity start - execute chunk tasks mid tick + long lastMidTickExecuteFailure; + // Tuinity end - execute chunk tasks mid tick + // Add env and gen to constructor, WorldData -> WorldDataServer public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey resourcekey, ResourceKey resourcekey1, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { super(iworlddataserver, resourcekey, resourcekey1, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor @@ -260,6 +354,349 @@ public class WorldServer extends World implements GeneratorAccessSeed { this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper } + // Tuinity start - optimise collision + public boolean collidesWithAnyBlockOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) { + if (entity != null) { + if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) { + return true; + } + } + + int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 1; + int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 1; + + int minBlockY = MathHelper.floor(axisalignedbb.minY - 1.0E-7D) - 1; + int maxBlockY = MathHelper.floor(axisalignedbb.maxY + 1.0E-7D) + 1; + + int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 1; + int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 1; + + + BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); + VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy + + // special cases: + if (minBlockY > 255 || maxBlockY < 0) { + // no point in checking + return false; + } + + int minYIterate = Math.max(0, minBlockY); + int maxYIterate = Math.min(255, maxBlockY); + + int minChunkX = minBlockX >> 4; + int maxChunkX = maxBlockX >> 4; + + int minChunkZ = minBlockZ >> 4; + int maxChunkZ = maxBlockZ >> 4; + + ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider; + // TODO special case single chunk? + + for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { + int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk + int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk + + for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { + int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk + int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk + + int chunkXGlobalPos = currChunkX << 4; + int chunkZGlobalPos = currChunkZ << 4; + Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); + + if (chunk == null) { + return true; + } + + ChunkSection[] sections = chunk.getSections(); + + // bound y + + for (int currY = minYIterate; currY <= maxYIterate; ++currY) { + ChunkSection section = sections[currY >>> 4]; + if (section == null || section.isFullOfAir()) { + // empty + // skip to next section + currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one + continue; + } + + DataPaletteBlock blocks = section.blockIds; + int blockKeyY = (currY & 15) << 8; + + int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0; + + for (int currX = minX; currX <= maxX; ++currX) { + int blockKeyXY = blockKeyY | currX; + int blockX = currX | chunkXGlobalPos; // world position + + int edgeCountXY; + if (blockX == minBlockX || blockX == maxBlockX) { + edgeCountXY = edgeCountY + 1; + } else { + edgeCountXY = edgeCountY; + } + + for (int currZ = minZ; currZ <= maxZ; ++currZ) { + int blockZ = currZ | chunkZGlobalPos; // world position + + int edgeCountFull; + if (blockZ == minBlockZ || blockZ == maxBlockZ) { + edgeCountFull = edgeCountXY + 1; + } else { + edgeCountFull = edgeCountXY; + } + + if (edgeCountFull == 3) { + continue; + } + + int blockKeyFull = blockKeyXY | (currZ << 4); + IBlockData blockData = blocks.rawGet(blockKeyFull); + + if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { + mutablePos.setValues(blockX, currY, blockZ); + VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); + if (voxelshape2 != VoxelShapes.getEmptyShape()) { + VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ); + + if (voxelshape3.intersects(axisalignedbb)) { + return true; + } + } + } + } + } + } + } + } + + return false; + } + + public final boolean hardCollidesWithAnyEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { + if (axisalignedbb.isEmpty()) { + return false; + } + AxisAlignedBB axisalignedbb1 = axisalignedbb.grow(1.0E-7D, 1.0E-7D, 1.0E-7D); + List entities = (entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb, predicate == null ? IEntitySelector.notSpectator() : predicate.and(IEntitySelector.notSpectator())) : this.getHardCollidingEntities(entity, axisalignedbb1, predicate); + + for (int i = 0, len = entities.size(); i < len; ++i) { + Entity otherEntity = entities.get(i); + + if (predicate != null && !predicate.test(otherEntity)) { + continue; + } + + if (entity != null) { + if (entity.isSameVehicle(otherEntity)) { + continue; + } + AxisAlignedBB hardCollisionBox = entity.getHardCollisionBoxWith(otherEntity); + if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) { + return true; + } + } + + AxisAlignedBB hardCollisionBox = otherEntity.getHardCollisionBox(); + + if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) { + return true; + } + } + + return false; + } + + public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { + return this.hasAnyCollisions(entity, axisalignedbb, true); + } + + public final boolean hasAnyCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, boolean loadChunks) { + return this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, loadChunks) || this.hardCollidesWithAnyEntities(entity, axisalignedbb, null); + } + + // Tuinity start - optimise collision + public void getCollisionsForBlocksOrWorldBorder(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, boolean loadChunks) { + if (entity != null) { + if (this.getWorldBorder().isCollidingOnBorderEdge(axisalignedbb)) { + VoxelShapes.addBoxesTo(this.getWorldBorder().getCollisionShape(), list); + } + } + + int minBlockX = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 1; + int maxBlockX = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 1; + + int minBlockY = MathHelper.floor(axisalignedbb.minY - 1.0E-7D) - 1; + int maxBlockY = MathHelper.floor(axisalignedbb.maxY + 1.0E-7D) + 1; + + int minBlockZ = MathHelper.floor(axisalignedbb.minZ - 1.0E-7D) - 1; + int maxBlockZ = MathHelper.floor(axisalignedbb.maxZ + 1.0E-7D) + 1; + + + BlockPosition.MutableBlockPosition mutablePos = new BlockPosition.MutableBlockPosition(); + VoxelShapeCollision collisionShape = entity == null ? VoxelShapeCollision.a() : VoxelShapeCollision.a(entity); // TODO make this lazy + + // special cases: + if (minBlockY > 255 || maxBlockY < 0) { + // no point in checking + return; + } + + int minYIterate = Math.max(0, minBlockY); + int maxYIterate = Math.min(255, maxBlockY); + + int minChunkX = minBlockX >> 4; + int maxChunkX = maxBlockX >> 4; + + int minChunkZ = minBlockZ >> 4; + int maxChunkZ = maxBlockZ >> 4; + + ChunkProviderServer chunkProvider = (ChunkProviderServer)this.chunkProvider; + // TODO special case single chunk? + + for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { + int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk + int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk + + for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { + int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk + int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk + + int chunkXGlobalPos = currChunkX << 4; + int chunkZGlobalPos = currChunkZ << 4; + Chunk chunk = loadChunks ? chunkProvider.getChunkAt(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); + + if (chunk == null) { + list.add(AxisAlignedBB.getBoxForChunk(currChunkX, currChunkZ)); + continue; + } + + ChunkSection[] sections = chunk.getSections(); + + // bound y + + for (int currY = minYIterate; currY <= maxYIterate; ++currY) { + ChunkSection section = sections[currY >>> 4]; + if (section == null || section.isFullOfAir()) { + // empty + // skip to next section + currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one + continue; + } + + DataPaletteBlock blocks = section.blockIds; + int blockKeyY = (currY & 15) << 8; + + int edgeCountY = (currY == minBlockY || currY == maxBlockY) ? 1 : 0; + + for (int currX = minX; currX <= maxX; ++currX) { + int blockKeyXY = blockKeyY | currX; + int blockX = currX | chunkXGlobalPos; // world position + + int edgeCountXY; + if (blockX == minBlockX || blockX == maxBlockX) { + edgeCountXY = edgeCountY + 1; + } else { + edgeCountXY = edgeCountY; + } + + for (int currZ = minZ; currZ <= maxZ; ++currZ) { + int blockZ = currZ | chunkZGlobalPos; // world position + + int edgeCountFull; + if (blockZ == minBlockZ || blockZ == maxBlockZ) { + edgeCountFull = edgeCountXY + 1; + } else { + edgeCountFull = edgeCountXY; + } + + if (edgeCountFull == 3) { + continue; + } + + int blockKeyFull = blockKeyXY | (currZ << 4); + IBlockData blockData = blocks.rawGet(blockKeyFull); + + if (!blockData.isAir() && (edgeCountFull != 1 || blockData.shapeExceedsCube()) && (edgeCountFull != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { + mutablePos.setValues(blockX, currY, blockZ); + VoxelShape voxelshape2 = blockData.getCollisionShape(this, mutablePos, collisionShape); + if (voxelshape2 != VoxelShapes.getEmptyShape()) { + VoxelShape voxelshape3 = voxelshape2.offset((double)blockX, (double)currY, (double)blockZ); + + VoxelShapes.addBoxesToIfIntersects(voxelshape3, axisalignedbb, list); + } + } + } + } + } + } + } + } + + public final void getEntityHardCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate, List list) { + if (axisalignedbb.isEmpty()) { + return; + } + AxisAlignedBB axisalignedbb1 = axisalignedbb.grow(1.0E-7D, 1.0E-7D, 1.0E-7D); + List entities = com.tuinity.tuinity.util.CachedLists.getTempGetEntitiesList(); + try { + if (entity != null && entity.hardCollides()) { + this.getEntities(entity, axisalignedbb, predicate == null ? IEntitySelector.notSpectator() : predicate.and(IEntitySelector.notSpectator()), entities); + } else { + this.getHardCollidingEntities(entity, axisalignedbb1, predicate, entities); + } + + for (int i = 0, len = entities.size(); i < len; ++i) { + Entity otherEntity = entities.get(i); + + if (predicate != null && !predicate.test(otherEntity)) { + continue; + } + + if (entity != null) { + if (entity.isSameVehicle(otherEntity)) { + continue; + } + AxisAlignedBB hardCollisionBox = entity.getHardCollisionBoxWith(otherEntity); + if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) { + list.add(hardCollisionBox); + } + } + + AxisAlignedBB hardCollisionBox = otherEntity.getHardCollisionBox(); + + if (hardCollisionBox != null && axisalignedbb1.intersects(hardCollisionBox)) { + list.add(hardCollisionBox); + } + } + } finally { + com.tuinity.tuinity.util.CachedLists.returnTempGetEntitiesList(entities); + } + } + + public final void getCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List list, boolean loadChunks) { + this.getCollisionsForBlocksOrWorldBorder(entity, axisalignedbb, list, loadChunks); + this.getEntityHardCollisions(entity, axisalignedbb, null, list); + } + + @Override + public boolean getCubes(Entity entity) { + return !this.hasAnyCollisions(entity, entity.getBoundingBox()); + } + + @Override + public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { + return !this.hasAnyCollisions(entity, axisalignedbb); + } + + @Override + public boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Predicate predicate) { + return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, predicate); + } + // Tuinity end - optimise collision + // CraftBukkit start @Override protected TileEntity getTileEntity(BlockPosition pos, boolean validate) { @@ -462,7 +899,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } timings.scheduledBlocks.stopTiming(); // Paper - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic gameprofilerfiller.exitEnter("raid"); this.timings.raids.startTiming(); // Paper - timings this.persistentRaid.a(); @@ -471,7 +908,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { timings.doSounds.startTiming(); // Spigot this.ah(); timings.doSounds.stopTiming(); // Spigot - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic this.ticking = false; gameprofilerfiller.exitEnter("entities"); boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players @@ -487,13 +924,12 @@ public class WorldServer extends World implements GeneratorAccessSeed { } this.tickingEntities = true; - ObjectIterator objectiterator = this.entitiesById.int2ObjectEntrySet().iterator(); + com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator objectiterator = this.entitiesForIteration.iterator(); // Tuinity org.spigotmc.ActivationRange.activateEntities(this); // Spigot timings.entityTick.startTiming(); // Spigot while (objectiterator.hasNext()) { - Entry entry = (Entry) objectiterator.next(); - Entity entity = (Entity) entry.getValue(); + Entity entity = (Entity) objectiterator.next(); // Tuinity Entity entity1 = entity.getVehicle(); /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed @@ -529,7 +965,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { gameprofilerfiller.enter("remove"); if (entity.dead) { this.removeEntityFromChunk(entity); - objectiterator.remove(); + this.entitiesById.remove(entity.getId()); // Tuinity this.unregisterEntity(entity); } @@ -537,6 +973,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } timings.entityTick.stopTiming(); // Spigot + objectiterator.finishedIterating(); // Tuinity this.tickingEntities = false; // Paper start for (java.lang.Runnable run : this.afterEntityTickingTasks) { @@ -548,7 +985,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } this.afterEntityTickingTasks.clear(); // Paper end - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic Entity entity2; @@ -558,7 +995,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } timings.tickEntities.stopTiming(); // Spigot - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic this.tickBlockEntities(); } @@ -802,7 +1239,26 @@ public class WorldServer extends World implements GeneratorAccessSeed { } + // Tuinity start - log detailed entity tick information + static final java.util.concurrent.ConcurrentLinkedDeque currentlyTickingEntities = new java.util.concurrent.ConcurrentLinkedDeque<>(); + + public static List getCurrentlyTickingEntities() { + List ret = Lists.newArrayListWithCapacity(4); + + for (Entity entity : currentlyTickingEntities) { + ret.add(entity); + } + + return ret; + } + // Tuinity end - log detailed entity tick information + public void entityJoinedWorld(Entity entity) { + // Tuinity start - log detailed entity tick information + com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); + try { + currentlyTickingEntities.push(entity); + // Tuinity end - log detailed entity tick information if (!(entity instanceof EntityHuman) && !this.getChunkProvider().a(entity)) { this.chunkCheck(entity); } else { @@ -848,6 +1304,11 @@ public class WorldServer extends World implements GeneratorAccessSeed { } // Paper - timings } + // Tuinity start - log detailed entity tick information + } finally { + currentlyTickingEntities.pop(); + } + // Tuinity end - log detailed entity tick information } public void a(Entity entity, Entity entity1) { @@ -1224,7 +1685,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { Entity entity = (Entity) iterator.next(); if (!(entity instanceof EntityPlayer)) { - if (this.tickingEntities) { + if (false && this.tickingEntities) { // Tuinity throw (IllegalStateException) SystemUtils.c(new IllegalStateException("Removing entity while ticking!")); } @@ -1252,6 +1713,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { public void unregisterEntity(Entity entity) { org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot + this.entitiesForIteration.remove(entity); // Tuinity // Paper start - fix entity registration issues if (entity instanceof EntityComplexPart) { // Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways @@ -1318,12 +1780,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { this.getScoreboard().a(entity); // CraftBukkit start - SPIGOT-5278 if (entity instanceof EntityDrowned) { - this.navigators.remove(((EntityDrowned) entity).navigationWater); - this.navigators.remove(((EntityDrowned) entity).navigationLand); + // Tuinity start + this.navigators.remove(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationWater); + this.navigators.remove(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.remove(((EntityDrowned) entity).navigationLand); + // Tuinity end } else // CraftBukkit end if (entity instanceof EntityInsentient) { - this.navigators.remove(((EntityInsentient) entity).getNavigation()); + // Tuinity start + this.navigators.remove(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.remove(((EntityInsentient) entity).getNavigation()); + // Tuinity end } new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid entity.valid = false; // CraftBukkit @@ -1339,7 +1805,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { return; } // Paper end - if (this.tickingEntities) { + if (false && this.tickingEntities) { // Tuinity if (!entity.isQueuedForRegister) { // Paper this.entitiesToAdd.add(entity); entity.isQueuedForRegister = true; // Paper @@ -1347,6 +1813,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } else { entity.isQueuedForRegister = false; // Paper this.entitiesById.put(entity.getId(), entity); + this.entitiesForIteration.add(entity); // Tuinity if (entity instanceof EntityEnderDragon) { EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity).eK(); int i = aentitycomplexpart.length; @@ -1355,6 +1822,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { EntityComplexPart entitycomplexpart = aentitycomplexpart[j]; this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart); + this.entitiesForIteration.add(entitycomplexpart); // Tuinity } } @@ -1379,12 +1847,16 @@ public class WorldServer extends World implements GeneratorAccessSeed { // this.getChunkProvider().addEntity(entity); // Paper - moved down below valid=true // CraftBukkit start - SPIGOT-5278 if (entity instanceof EntityDrowned) { - this.navigators.add(((EntityDrowned) entity).navigationWater); - this.navigators.add(((EntityDrowned) entity).navigationLand); + // Tuinity start + this.navigators.add(((EntityDrowned) entity).navigationWater); this.navigatorsForIteration.add(((EntityDrowned) entity).navigationWater); + this.navigators.add(((EntityDrowned) entity).navigationLand); this.navigatorsForIteration.add(((EntityDrowned) entity).navigationLand); + // Tuinity end } else // CraftBukkit end if (entity instanceof EntityInsentient) { - this.navigators.add(((EntityInsentient) entity).getNavigation()); + // Tuinity start + this.navigators.add(((EntityInsentient) entity).getNavigation()); this.navigatorsForIteration.add(((EntityInsentient) entity).getNavigation()); + // Tuinity end } entity.valid = true; // CraftBukkit this.getChunkProvider().addEntity(entity); // Paper - from above to be below valid=true @@ -1400,7 +1872,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { } public void removeEntity(Entity entity) { - if (this.tickingEntities) { + if (false && this.tickingEntities) { // Tuinity throw (IllegalStateException) SystemUtils.c(new IllegalStateException("Removing entity while ticking!")); } else { this.removeEntityFromChunk(entity); @@ -1502,7 +1974,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { if (VoxelShapes.c(voxelshape, voxelshape1, OperatorBoolean.NOT_SAME)) { boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper - Iterator iterator = this.navigators.iterator(); + // Tuinity start + com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.navigatorsForIteration.iterator(); + try { // Tuinity end while (iterator.hasNext()) { NavigationAbstract navigationabstract = (NavigationAbstract) iterator.next(); @@ -1511,6 +1985,9 @@ public class WorldServer extends World implements GeneratorAccessSeed { navigationabstract.b(blockposition); } } + } finally { // Tuinity start + iterator.finishedIterating(); + } // Tuinity end this.tickingEntities = wasTicking; // Paper } diff --git a/src/main/java/net/minecraft/server/WorldUpgrader.java b/src/main/java/net/minecraft/server/WorldUpgrader.java index 5ccdc0b87..888dae2d5 100644 --- a/src/main/java/net/minecraft/server/WorldUpgrader.java +++ b/src/main/java/net/minecraft/server/WorldUpgrader.java @@ -218,7 +218,7 @@ public class WorldUpgrader { int l = Integer.parseInt(matcher.group(2)) << 5; try { - RegionFile regionfile = new RegionFile(file2, file1, true); + RegionFile regionfile = new RegionFile(file2, file1, true, true); // Tuinity - allow for chunk regionfiles to regen header Throwable throwable = null; try { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java index 4ec53a54e..31c81b4b5 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java @@ -77,7 +77,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { public Material getBlockType(int x, int y, int z) { CraftChunk.validateChunkCoordinates(x, y, z); - return CraftMagicNumbers.getMaterial(blockids[y >> 4].a(x, y & 0xF, z).getBlock()); + return blockids[y >> 4].a(x, y & 0xF, z).getBukkitMaterial(); // Tuinity - optimise getType calls } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index d8103ab0d..ba4217109 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -231,7 +231,7 @@ import javax.annotation.Nullable; // Paper import javax.annotation.Nonnull; // Paper public final class CraftServer implements Server { - private final String serverName = "Paper"; // Paper + private final String serverName = "Tuinity"; // Paper // Tuinity private final String serverVersion; private final String bukkitVersion = Versioning.getBukkitVersion(); private final Logger logger = Logger.getLogger("Minecraft"); @@ -848,6 +848,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper + com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config for (WorldServer world : console.getWorlds()) { world.worldDataServer.setDifficulty(config.difficulty); world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); @@ -882,6 +883,7 @@ public final class CraftServer implements Server { } world.spigotConfig.init(); // Spigot world.paperConfig.init(); // Paper + world.tuinityConfig.init(); // Tuinity - Server Config } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper @@ -1821,7 +1823,10 @@ public final class CraftServer implements Server { @Override public boolean isPrimaryThread() { - return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario + // Tuinity start + final Thread currThread = Thread.currentThread(); + return currThread == console.serverThread || currThread instanceof com.tuinity.tuinity.util.TickThread || currThread.equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario + // Tuinity End } @Override @@ -2231,6 +2236,14 @@ public final class CraftServer implements Server { return com.destroystokyo.paper.PaperConfig.config; } + // Tuinity start - add config to timings report + @Override + public YamlConfiguration getTuinityConfig() + { + return com.tuinity.tuinity.config.TuinityConfig.config; + } + // Tuinity end - add config to timings report + @Override public void restart() { org.spigotmc.RestartCommand.restart(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 65b36a174..39decca9c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -336,6 +336,14 @@ public class CraftWorld implements World { this.generator = gen; environment = env; + + //Tuinity start - per world spawn limits + monsterSpawn = world.tuinityConfig.spawnLimitMonsters; + animalSpawn = world.tuinityConfig.spawnLimitAnimals; + waterAmbientSpawn = world.tuinityConfig.spawnLimitWaterAmbient; + waterAnimalSpawn = world.tuinityConfig.spawnLimitWaterAnimals; + ambientSpawn = world.tuinityConfig.spawnLimitAmbient; + //Tuinity end } @Override @@ -402,14 +410,7 @@ public class CraftWorld implements World { @Override public Chunk getChunkAt(int x, int z) { - // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it - net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z); - if (chunk == null) { - addTicket(x, z); - chunk = this.world.getChunkProvider().getChunkAt(x, z, true); - } - return chunk.bukkitChunk; - // Paper end + return this.world.getChunkProvider().getChunkAt(x, z, true).bukkitChunk; // Tuinity - revert paper diff } // Paper start @@ -492,6 +493,7 @@ public class CraftWorld implements World { org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot if (isChunkLoaded(x, z)) { world.getChunkProvider().removeTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 0, Unit.INSTANCE); // Paper + ((ChunkMapDistance)world.getChunkProvider().playerChunkMap.getChunkMapDistanceManager()).removeTickets(ChunkCoordIntPair.pair(x, z), TicketType.DELAYED_UNLOAD); // Tuinity - delay chunk unloads - let plugins override } return true; @@ -2541,7 +2543,7 @@ public class CraftWorld implements World { } return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null); - if (chunk != null) addTicket(x, z); // Paper + if (false && chunk != null) addTicket(x, z); // Paper // Tuinity - revert return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); }, MinecraftServer.getServer()); } diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index bac292e6d..b4e65963e 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -138,6 +138,13 @@ public class Main { .defaultsTo(new File("paper.yml")) .describedAs("Yml file"); // Paper end + // Tuinity Start - Server Config + acceptsAll(asList("tuinity", "tuinity-settings"), "File for tuinity settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("tuinity.yml")) + .describedAs("Yml file"); + /* Conctete End - Server Config */ // Paper start acceptsAll(asList("server-name"), "Name of the server") @@ -252,7 +259,7 @@ public class Main { if (buildDate.before(deadline.getTime())) { // Paper start - This is some stupid bullshit System.err.println("*** Warning, you've not updated in a while! ***"); - System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper + System.err.println("*** Please download a new build ***"); // Paper // Tuinity //System.err.println("*** Server will start in 20 seconds ***"); //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); // Paper End diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index 9b0e868f0..ee53060b1 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -207,7 +207,7 @@ public class CraftBlock implements Block { @Override public Material getType() { - return CraftMagicNumbers.getMaterial(world.getType(position).getBlock()); + return world.getType(position).getBukkitMaterial(); // Tuinity - optimise getType calls } @Override @@ -505,15 +505,30 @@ public class CraftBlock implements Block { return null; } - return Biome.valueOf(IRegistry.BIOME.getKey(base).getKey().toUpperCase(java.util.Locale.ENGLISH)); + return base.getBukkitBiome(); // Tuinity - optimise biome conversion } + private static final java.util.EnumMap BUKKIT_BIOME_TO_NMS_CACHE = new java.util.EnumMap<>(Biome.class); // Tuinity - optimise biome conversion + public static BiomeBase biomeToBiomeBase(Biome bio) { if (bio == null) { return null; } - return IRegistry.BIOME.get(new MinecraftKey(bio.name().toLowerCase(java.util.Locale.ENGLISH))); + // Tuinity start - optimise biome conversion + BiomeBase cached = BUKKIT_BIOME_TO_NMS_CACHE.get(bio); + + if (cached != null) { + return cached; + } + + BiomeBase ret = IRegistry.BIOME.get(new MinecraftKey(bio.name().toLowerCase(java.util.Locale.ENGLISH))); + synchronized (BUKKIT_BIOME_TO_NMS_CACHE) { + BUKKIT_BIOME_TO_NMS_CACHE.put(bio, ret); + } + + return ret; + // Tuinity end - optimise biome conversion } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java index 11aa2dc18..c51c43573 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -136,7 +136,7 @@ public class CraftBlockState implements BlockState { @Override public Material getType() { - return CraftMagicNumbers.getMaterial(data.getBlock()); + return data.getBukkitMaterial(); // Tuinity - optimise getType calls } public void setFlag(int flag) { diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java index bbded5671..980890153 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java @@ -44,7 +44,7 @@ public class CraftBlockData implements BlockData { @Override public Material getMaterial() { - return CraftMagicNumbers.getMaterial(state.getBlock()); + return state.getBukkitMaterial(); // Tuinity - optimise getType calls } public IBlockData getState() { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index d1df4e579..6f18a7898 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -495,27 +495,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { entity.setHeadRotation(yaw); } - @Override// Paper start - public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { - net.minecraft.server.PlayerChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().playerChunkMap; - java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); - - loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> { - net.minecraft.server.ChunkCoordIntPair pair = new net.minecraft.server.ChunkCoordIntPair(chunk.getX(), chunk.getZ()); - ((CraftWorld) loc.getWorld()).getHandle().getChunkProvider().addTicketAtLevel(net.minecraft.server.TicketType.POST_TELEPORT, pair, 31, 0); - net.minecraft.server.PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pair.pair()); - if (updatingChunk != null) { - return updatingChunk.getEntityTickingFuture(); - } else { - return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle())); - } - }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> { - future.completeExceptionally(ex); - return null; - }); - return future; - } - // Paper end + // Tuinity @Override public boolean teleport(Location location) { @@ -549,6 +529,37 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return true; } + // Tuinity start - implement teleportAsync better + @Override + public java.util.concurrent.CompletableFuture teleportAsync(Location location, TeleportCause cause) { + Preconditions.checkArgument(location != null, "location"); + location.checkFinite(); + Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. + + net.minecraft.server.WorldServer world = ((CraftWorld)locationClone.getWorld()).getHandle(); + java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); + + world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), location.getX(), location.getZ(), (list) -> { + net.minecraft.server.ChunkProviderServer chunkProviderServer = world.getChunkProvider(); + for (net.minecraft.server.IChunkAccess chunk : list) { + chunkProviderServer.addTicketAtLevel(net.minecraft.server.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); + } + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { + try { + ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE); + } catch (Throwable throwable) { + if (throwable instanceof ThreadDeath) { + throw (ThreadDeath)throwable; + } + ret.completeExceptionally(throwable); + } + }); + }); + + return ret; + } + // Tuinity end - implement teleportAsync better + @Override public boolean teleport(org.bukkit.entity.Entity destination) { return teleport(destination.getLocation()); diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java index 948a59217..ab43c97e8 100644 --- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java @@ -73,7 +73,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { @Override public Material getType(int x, int y, int z) { - return CraftMagicNumbers.getMaterial(getTypeId(x, y, z).getBlock()); + return getTypeId(x, y, z).getBukkitMaterial(); // Tuinity - optimise getType calls } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java index ca2be3060..2c5701376 100644 --- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java @@ -100,9 +100,18 @@ public final class CraftScoreboardManager implements ScoreboardManager { // CraftBukkit method public void getScoreboardScores(IScoreboardCriteria criteria, String name, Consumer consumer) { + // Tuinity start - add timings for scoreboard search + // plugins leaking scoreboards will make this very expensive, let server owners debug it easily + co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); + try { + // Tuinity end - add timings for scoreboard search for (CraftScoreboard scoreboard : scoreboards) { Scoreboard board = scoreboard.board; board.getObjectivesForCriteria(criteria, name, (score) -> consumer.accept(score)); } + } finally { // Tuinity start - add timings for scoreboard search + co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); + } + // Tuinity end - add timings for scoreboard search } } diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java index f72c13bed..50f855b93 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java +++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java @@ -119,6 +119,32 @@ public class UnsafeList extends AbstractList implements List, RandomAcc return indexOf(o) >= 0; } + // Tuinity start + protected transient int maxSize; + public void setSize(int size) { + if (this.maxSize < this.size) { + this.maxSize = this.size; + } + this.size = size; + } + + public void completeReset() { + if (this.data != null) { + Arrays.fill(this.data, 0, Math.max(this.size, this.maxSize), null); + } + this.size = 0; + this.maxSize = 0; + if (this.iterPool != null) { + for (Iterator temp : this.iterPool) { + if (temp == null) { + continue; + } + ((Itr)temp).valid = false; + } + } + } + // Tuinity end + @Override public void clear() { // Create new array to reset memory usage to initial capacity diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java index 674096cab..001b1e519 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java @@ -11,7 +11,7 @@ public final class Versioning { public static String getBukkitVersion() { String result = "Unknown-Version"; - InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.destroystokyo.paper/paper-api/pom.properties"); + InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.tuinity/tuinity-api/pom.properties"); // Tuinity Properties properties = new Properties(); if (stream != null) { diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java index 9f7d2ef93..c3ac1a46c 100644 --- a/src/main/java/org/spigotmc/AsyncCatcher.java +++ b/src/main/java/org/spigotmc/AsyncCatcher.java @@ -10,7 +10,7 @@ public class AsyncCatcher public static void catchOp(String reason) { - if ( enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) + if ( ( enabled || com.tuinity.tuinity.util.TickThread.STRICT_THREAD_CHECKS ) && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity { throw new IllegalStateException( "Asynchronous " + reason + "!" ); } diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java index 513c1041c..4d3109084 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -61,6 +61,84 @@ public class WatchdogThread extends Thread } } + // Tuinity start - log detailed tick information + private void dumpTickingInfo() { + Logger log = Bukkit.getServer().getLogger(); + + // ticking entities + for (net.minecraft.server.Entity entity : net.minecraft.server.WorldServer.getCurrentlyTickingEntities()) { + double posX, posY, posZ; + net.minecraft.server.Vec3D mot; + double moveStartX, moveStartY, moveStartZ; + net.minecraft.server.Vec3D moveVec; + synchronized (entity.posLock) { + posX = entity.locX(); + posY = entity.locY(); + posZ = entity.locZ(); + mot = entity.getMot(); + moveStartX = entity.getMoveStartX(); + moveStartY = entity.getMoveStartY(); + moveStartZ = entity.getMoveStartZ(); + moveVec = entity.getMoveVector(); + } + + String entityType = entity.getMinecraftKey().toString(); + java.util.UUID entityUUID = entity.getUniqueID(); + net.minecraft.server.World world = entity.getWorld(); + + log.log(Level.SEVERE, "Ticking entity: " + entityType); + log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); + log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); + if (moveVec != null) { + log.log(Level.SEVERE, "Move call information: "); + log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); + log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); + } + log.log(Level.SEVERE, "UUID: " + entityUUID); + } + + // packet processors + for (net.minecraft.server.PacketListener packetListener : net.minecraft.server.PlayerConnectionUtils.getCurrentPacketProcessors()) { + if (packetListener instanceof net.minecraft.server.PlayerConnection) { + net.minecraft.server.EntityPlayer player = ((net.minecraft.server.PlayerConnection)packetListener).player; + long totalPackets = net.minecraft.server.PlayerConnectionUtils.getTotalProcessedPackets(); + if (player == null) { + log.log(Level.SEVERE, "Handling packet for player connection (null player): " + packetListener); + log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); + } else { + // exclude velocity, this is set client side... Paper will also warn on high velocity set too + double posX, posY, posZ; + double moveStartX, moveStartY, moveStartZ; + net.minecraft.server.Vec3D moveVec; + synchronized (player.posLock) { + posX = player.locX(); + posY = player.locY(); + posZ = player.locZ(); + moveStartX = player.getMoveStartX(); + moveStartY = player.getMoveStartY(); + moveStartZ = player.getMoveStartZ(); + moveVec = player.getMoveVector(); + } + + java.util.UUID entityUUID = player.getUniqueID(); + net.minecraft.server.World world = player.getWorld(); + + log.log(Level.SEVERE, "Handling packet for player '" + player.getName() + "', UUID: " + entityUUID); + log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); + if (moveVec != null) { + log.log(Level.SEVERE, "Move call information: "); + log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); + log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); + } + log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); + } + } else { + log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); + } + } + } + // Tuinity end - log detailed tick information + @Override public void run() { @@ -117,6 +195,7 @@ public class WatchdogThread extends Thread log.log( Level.SEVERE, "------------------------------" ); log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper + this.dumpTickingInfo(); // Tuinity - log detailed tick information dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); log.log( Level.SEVERE, "------------------------------" ); //