From 09137fe67786a8988d86e3965fa49639c4792903 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Fri, 14 Dec 2018 21:53:58 -0800 Subject: [PATCH] Tuinity Server Changes --- pom.xml | 23 +- .../co/aikar/timings/MinecraftTimings.java | 2 + .../java/co/aikar/timings/TimingsExport.java | 3 +- .../paper/PaperVersionFetcher.java | 11 +- .../paper/server/ticklist/PaperTickList.java | 9 + .../chunk/SingleThreadChunkRegionManager.java | 159 ++++++ .../tuinity/tuinity/config/TuinityConfig.java | 277 ++++++++++ .../com/tuinity/tuinity/util/CachedLists.java | 53 ++ .../com/tuinity/tuinity/util/TickThread.java | 41 ++ .../IteratorSafeOrderedReferenceSet.java | 265 +++++++++ .../tuinity/tuinity/voxel/AABBVoxelShape.java | 246 +++++++++ .../net/minecraft/server/AxisAlignedBB.java | 115 ++++ .../java/net/minecraft/server/BiomeBase.java | 12 + .../java/net/minecraft/server/BlockChest.java | 4 +- .../net/minecraft/server/BlockPiston.java | 31 +- .../minecraft/server/BlockPistonMoving.java | 7 +- src/main/java/net/minecraft/server/Chunk.java | 37 ++ .../java/net/minecraft/server/ChunkMap.java | 1 + .../minecraft/server/ChunkMapDistance.java | 94 +++- .../minecraft/server/ChunkProviderServer.java | 278 ++++++++-- .../minecraft/server/ChunkRegionLoader.java | 12 +- .../net/minecraft/server/ChunkSection.java | 1 + .../net/minecraft/server/ChunkStatus.java | 4 +- .../minecraft/server/DataPaletteBlock.java | 1 + .../net/minecraft/server/DedicatedServer.java | 1 + src/main/java/net/minecraft/server/EULA.java | 2 +- .../java/net/minecraft/server/Entity.java | 204 ++++++- .../net/minecraft/server/EntityLiving.java | 9 +- .../minecraft/server/EntityTrackerEntry.java | 1 + .../java/net/minecraft/server/HeightMap.java | 5 +- .../java/net/minecraft/server/IBlockData.java | 12 + .../minecraft/server/ICollisionAccess.java | 39 +- .../minecraft/server/LightEngineStorage.java | 5 +- .../java/net/minecraft/server/MCUtil.java | 14 + .../net/minecraft/server/MinecraftServer.java | 110 +++- .../net/minecraft/server/NetworkManager.java | 59 +- .../server/PacketPlayOutMapChunk.java | 117 ++-- .../minecraft/server/PathfinderNormal.java | 13 +- .../net/minecraft/server/PlayerChunk.java | 37 +- .../net/minecraft/server/PlayerChunkMap.java | 64 ++- .../minecraft/server/PlayerConnection.java | 34 +- .../server/PlayerConnectionUtils.java | 26 + .../server/PlayerInteractManager.java | 53 +- .../java/net/minecraft/server/ProtoChunk.java | 16 +- .../java/net/minecraft/server/RegionFile.java | 468 +++++++++++++++- .../minecraft/server/RegionFileBitSet.java | 26 +- .../net/minecraft/server/RegionFileCache.java | 45 +- .../server/RegionFileCompression.java | 7 +- .../java/net/minecraft/server/Ticket.java | 11 +- .../java/net/minecraft/server/TicketType.java | 3 +- .../java/net/minecraft/server/TileEntity.java | 49 +- .../minecraft/server/TileEntityBeacon.java | 26 +- .../minecraft/server/TileEntityBeehive.java | 7 + .../server/TileEntityBrewingStand.java | 26 +- .../net/minecraft/server/TileEntityChest.java | 16 + .../minecraft/server/TileEntityConduit.java | 21 +- .../minecraft/server/TileEntityFurnace.java | 26 +- .../minecraft/server/TileEntityJukeBox.java | 7 + .../minecraft/server/TileEntityLectern.java | 51 +- .../minecraft/server/TileEntityPiston.java | 66 ++- src/main/java/net/minecraft/server/Vec3D.java | 5 +- .../net/minecraft/server/VillagePlace.java | 2 +- .../java/net/minecraft/server/VoxelShape.java | 11 +- .../net/minecraft/server/VoxelShapeArray.java | 72 +++ .../net/minecraft/server/VoxelShapes.java | 79 ++- src/main/java/net/minecraft/server/World.java | 41 +- .../net/minecraft/server/WorldBorder.java | 37 +- .../net/minecraft/server/WorldServer.java | 517 +++++++++++++++++- .../net/minecraft/server/WorldUpgrader.java | 2 +- .../craftbukkit/CraftChunkSnapshot.java | 2 +- .../org/bukkit/craftbukkit/CraftServer.java | 17 +- .../org/bukkit/craftbukkit/CraftWorld.java | 19 +- .../java/org/bukkit/craftbukkit/Main.java | 9 +- .../bukkit/craftbukkit/block/CraftBlock.java | 21 +- .../craftbukkit/block/CraftBlockState.java | 2 +- .../block/data/CraftBlockData.java | 2 +- .../craftbukkit/entity/CraftEntity.java | 31 ++ .../craftbukkit/generator/CraftChunkData.java | 2 +- .../scoreboard/CraftScoreboardManager.java | 9 + .../bukkit/craftbukkit/util/UnsafeList.java | 26 + .../bukkit/craftbukkit/util/Versioning.java | 2 +- src/main/java/org/spigotmc/AsyncCatcher.java | 2 +- .../java/org/spigotmc/WatchdogThread.java | 79 +++ 83 files changed, 3967 insertions(+), 384 deletions(-) create mode 100644 src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java create mode 100644 src/main/java/com/tuinity/tuinity/config/TuinityConfig.java create mode 100644 src/main/java/com/tuinity/tuinity/util/CachedLists.java create mode 100644 src/main/java/com/tuinity/tuinity/util/TickThread.java create mode 100644 src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java create mode 100644 src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java diff --git a/pom.xml b/pom.xml index e4c63bb76..66517f30f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,11 @@ - 4.0.0 - paper + tuinity jar 1.15.2-R0.1-SNAPSHOT - Paper - https://papermc.io - + Tuinity-Server + https://github.com/Spottedleaf/Tuinity UTF-8 @@ -18,16 +17,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 +163,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 a3b41ce5f..b09981e9b 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 ce653f6b4..a08bfb4b3 100644 --- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java +++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java @@ -189,6 +189,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; @@ -269,6 +270,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"); @@ -297,6 +299,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; @@ -414,6 +417,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; @@ -473,6 +477,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 } @@ -529,6 +534,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(); @@ -548,6 +554,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 @@ -579,6 +586,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); @@ -588,6 +596,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..1ae1fd750 --- /dev/null +++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java @@ -0,0 +1,277 @@ +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 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.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/AxisAlignedBB.java b/src/main/java/net/minecraft/server/AxisAlignedBB.java index 1a466e929..688c5b1bd 100644 --- a/src/main/java/net/minecraft/server/AxisAlignedBB.java +++ b/src/main/java/net/minecraft/server/AxisAlignedBB.java @@ -13,6 +13,118 @@ 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); + 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); @@ -181,6 +293,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); } @@ -189,6 +302,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 b(Vec3D vec3d) { return this.d(vec3d.x, vec3d.y, vec3d.z); } @@ -208,6 +322,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/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java index 0102a170d..ef6c85557 100644 --- a/src/main/java/net/minecraft/server/BiomeBase.java +++ b/src/main/java/net/minecraft/server/BiomeBase.java @@ -61,6 +61,18 @@ public abstract 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.h != null && biomebase_a.i != null) { this.n = biomebase_a.a; diff --git a/src/main/java/net/minecraft/server/BlockChest.java b/src/main/java/net/minecraft/server/BlockChest.java index 72fb92f7c..6ea29ffc0 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.ay; + public static final BlockStateEnum c = BlockProperties.ay; public static final BlockStateEnum getChestTypeEnum() { return BlockChest.c; } // Tuinity - OBFHELPER public static final BlockStateBoolean d = BlockProperties.C; 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.getBlock() != 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 f90ac88d3..8312ed779 100644 --- a/src/main/java/net/minecraft/server/BlockPiston.java +++ b/src/main/java/net/minecraft/server/BlockPiston.java @@ -279,8 +279,10 @@ public class BlockPiston extends BlockDirectional { } else if ((Boolean) iblockdata.get(BlockPiston.EXTENDED)) { return false; } - - return !block.isTileEntity(); + // Tuinity start - pushable TE's + TileEntity tileEntity; + return !block.isTileEntity() || ((tileEntity = world.getTileEntity(blockposition)) != null && tileEntity.isPushable()); + // Tuinity end - pushable TE's } else { return false; } @@ -377,7 +379,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 @@ -389,10 +391,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(), 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 --j; aiblockdata[j] = iblockdata1; diff --git a/src/main/java/net/minecraft/server/BlockPistonMoving.java b/src/main/java/net/minecraft/server/BlockPistonMoving.java index 809ee9f9a..805fc6b88 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/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java index d7beb47d9..5a360fdca 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -509,8 +509,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(); @@ -569,6 +596,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); @@ -576,6 +607,7 @@ public class Chunk implements IChunkAccess { } else { tileentity.invalidateBlockCache(); } + } // Tuinity - add tileEntity parameter } this.s = true; @@ -591,6 +623,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); @@ -660,6 +693,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; } @@ -917,6 +951,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); @@ -956,6 +991,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); @@ -986,6 +1022,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/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java index 55f9f4e6e..15f0f86a3 100644 --- a/src/main/java/net/minecraft/server/ChunkMap.java +++ b/src/main/java/net/minecraft/server/ChunkMap.java @@ -68,6 +68,7 @@ public abstract class ChunkMap extends LightEngineGraph { protected abstract int b(long i); + public final void update(long coordinate, int level, boolean increaseInLevel) { this.b(coordinate, level, increaseInLevel); } // Tuinity - OBFHELPER public void b(long i, int j, boolean flag) { this.a(ChunkCoordIntPair.a, i, j, flag); } diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java index a013753bd..26994b478 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 e = new ChunkMapDistance.a(); + private final ChunkMapDistance.a e = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.e; } // 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.e.b(entry.getLongKey(), a((ArraySetSorted) entry.getValue()), false); } @@ -84,6 +144,7 @@ public abstract class ChunkMapDistance { } + private static int getLowestTicketLevel(ArraySetSorted> arraysetsorted) { return a(arraysetsorted); } // Tuinity - OBFHELPER private static int a(ArraySetSorted> arraysetsorted) { AsyncCatcher.catchOp("ChunkMapDistance::getHighestTicketLevel"); // Paper return !arraysetsorted.isEmpty() ? ((Ticket) arraysetsorted.b()).b() : PlayerChunkMap.GOLDEN_TICKET + 1; @@ -98,6 +159,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"); this.g.a(); @@ -176,27 +238,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()) { @@ -347,6 +393,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); }); @@ -364,6 +411,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.u().pair(); ((ObjectSet) this.c.computeIfAbsent(i, (j) -> { @@ -374,6 +422,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.u().pair(); ObjectSet objectset = (ObjectSet) this.c.get(i); @@ -423,6 +472,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 75c22a3f4..24af59962 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -118,7 +118,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) { @@ -180,9 +180,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) { @@ -207,6 +207,163 @@ 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, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, ChunkGenerator chunkgenerator, int i, WorldLoadListener worldloadlistener, Supplier supplier) { this.world = worldserver; @@ -533,6 +690,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); @@ -551,9 +710,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(); @@ -564,12 +726,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)); } @@ -731,7 +901,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"); @@ -741,12 +911,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; @@ -822,11 +1002,12 @@ public class ChunkProviderServer extends IChunkProvider { this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings this.world.getMethodProfiler().exit(); // Paper - replaced by above - final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping - Optional optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left(); - - if (optional.isPresent()) { - Chunk chunk = (Chunk) optional.get(); + // 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 @@ -905,10 +1086,26 @@ 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 @@ -920,7 +1117,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 } @Override @@ -1046,44 +1261,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 1685237df..20b6b58bd 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -23,6 +23,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) { @@ -375,10 +383,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 4dcb111c7..2f56b12d9 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -95,6 +95,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 40ce30cdc..3ba8481de 100644 --- a/src/main/java/net/minecraft/server/ChunkStatus.java +++ b/src/main/java/net/minecraft/server/ChunkStatus.java @@ -103,7 +103,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); @@ -165,7 +165,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/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java index 81362de54..599ce8062 100644 --- a/src/main/java/net/minecraft/server/DataPaletteBlock.java +++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java @@ -164,6 +164,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 32cd645ab..1f1243ae8 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -194,6 +194,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((File) options.valueOf("tuinity-settings")); // Tuinity - Server Config this.setSpawnAnimals(dedicatedserverproperties.spawnAnimals); this.setSpawnNPCs(dedicatedserverproperties.spawnNpcs); diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java index cf00f35a5..e54730f09 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 e0ab058bf..37f854764 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -137,7 +137,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke public double E; public double F; public double G; - public float H; + public float H; public final float getStepHeight() { return this.H; } // Tuinity - OBFHELPER public boolean noclip; public float J; protected final Random random; @@ -211,6 +211,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. @@ -601,7 +609,44 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return this.world.getCubes(this, axisalignedbb) && !this.world.containsLiquid(axisalignedbb); } + // 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 + + boolean collidedOnSomething; // Tuinity - optimise collisions + boolean requiredRelaxedCollisionCheck; // Tuinity - optimise collisions + 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; + } + this.collidedOnSomething = false; // Tuinity - optimise collisions + try { + // Tuinity end - detailed watchdog information if (this.noclip) { this.a(this.getBoundingBox().b(vec3d)); this.recalcPosition(); @@ -629,7 +674,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke // Paper end vec3d = this.a(vec3d, enummovetype); - Vec3D vec3d1 = this.e(vec3d); + Vec3D vec3d1 = !this.requiredRelaxedCollisionCheck ? this.performCollision(vec3d) : this.performCollision(vec3d, 0.0625); // Tuinity - optimise collisions + this.collidedOnSomething = !vec3d.equals(vec3d1); // Tuinity - optimise collisions if (vec3d1.g() > 1.0E-7D) { this.a(this.getBoundingBox().b(vec3d1)); @@ -761,6 +807,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 ag() { @@ -841,6 +894,138 @@ 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) { + return this.performCollision(moveVector, 0.0); + } + Vec3D performCollision(Vec3D moveVector, double contractAmount) { + if (moveVector.getX() == 0.0 && moveVector.getY() == 0.0 && moveVector.getZ() == 0.0) { + return moveVector; + } + + WorldServer world = ((WorldServer)this.world); + AxisAlignedBB currBoundingBox = contractAmount == 0.0 ? this.getBoundingBox() : this.getBoundingBox().grow(-contractAmount); + + List potentialCollisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList(); + try { + // We take a fat guess that we actually need to do the step height checks. + // So we collect all of the AABB's we would ever need into one list, and use that list + // for every collision check we want. + AxisAlignedBB collisionBox; + double stepHeight = (double) this.getStepHeight(); + if ((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 e(Vec3D vec3d) { AxisAlignedBB axisalignedbb = this.getBoundingBox(); VoxelShapeCollision voxelshapecollision = VoxelShapeCollision.a(this); @@ -874,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; } @@ -1138,8 +1324,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } public final AxisAlignedBB getCollisionBox(){return au();} //Paper - OBFHELPER - @Nullable - public AxisAlignedBB au() { + @Nullable public final AxisAlignedBB getHardCollisionBox() { return this.au(); } // Tuinity - OBFHELPER + @Nullable public AxisAlignedBB au() { // Tuinity - OBFHELPER return null; } @@ -2057,8 +2243,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; } @@ -3388,12 +3574,16 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke return new Vec3D(this.locX, this.locY, this.locZ); } + 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) { @@ -3449,9 +3639,11 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke this.setBoundingBox(new AxisAlignedBB(d0 - (double) f, d1, d2 - (double) f, d0 + (double) f, d1 + (double) f1, d2 + (double) f)); } // Paper end + synchronized (this.posLock) { // Tuinity this.locX = d0; this.locY = d1; this.locZ = d2; + } // Tuinity } public void checkDespawn() {} diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java index 3fc2360a1..a245cfab6 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -2682,7 +2682,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 @@ -2711,6 +2715,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 5cc89c0cf..1bd703848 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -72,6 +72,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/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java index 29cb545a8..aa7339610 100644 --- a/src/main/java/net/minecraft/server/HeightMap.java +++ b/src/main/java/net/minecraft/server/HeightMap.java @@ -119,6 +119,7 @@ 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)); } @@ -154,7 +155,7 @@ public class HeightMap { private final String g; private final HeightMap.Use h; private final Predicate i; - private static final Map j = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> { + private static final Map j = SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Tuinity - decompile fix HeightMap.Type[] aheightmap_type = values(); int i = aheightmap_type.length; @@ -166,7 +167,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.g = s; this.h = heightmap_use; this.i = predicate; diff --git a/src/main/java/net/minecraft/server/IBlockData.java b/src/main/java/net/minecraft/server/IBlockData.java index b39554faf..41cac2741 100644 --- a/src/main/java/net/minecraft/server/IBlockData.java +++ b/src/main/java/net/minecraft/server/IBlockData.java @@ -26,6 +26,18 @@ public class IBlockData extends BlockDataAbstract implements private final boolean isTicking; // Paper private final boolean canOcclude; // Paper + // 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) { super(block, immutablemap); this.d = block.a(this); diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java index 63dd5e98b..2001c2a39 100644 --- a/src/main/java/net/minecraft/server/ICollisionAccess.java +++ b/src/main/java/net/minecraft/server/ICollisionAccess.java @@ -43,6 +43,11 @@ public interface ICollisionAccess extends IBlockAccess { } default boolean a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set set) { + // Tuinity start - allow overriding in WorldServer + return this.getCubes(entity, axisalignedbb, set); + } + default boolean getCubes(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set set) { + // Tuinity end - allow overriding in WorldServer try { if (entity != null) entity.collisionLoadChunks = true; // Paper // Paper start - reduce stream usage java.util.List blockCollisions = getBlockCollision(entity, axisalignedbb, true); @@ -96,10 +101,9 @@ public interface ICollisionAccess extends IBlockAccess { if (entity != null) { // Paper end //VoxelShape voxelshape1 = ICollisionAccess.this.getWorldBorder().a(); // Paper - only make if collides - boolean flag = !ICollisionAccess.this.getWorldBorder().isInBounds(entity.getBoundingBox().shrink(1.0E-7D)); // Paper - boolean flag1 = !ICollisionAccess.this.getWorldBorder().isInBounds(entity.getBoundingBox().g(1.0E-7D)); // Paper + // Tuinity - optimise voxelshapes - if (!flag && flag1) { + if (ICollisionAccess.this.getWorldBorder().isCollidingOnBorderEdge(entity.getBoundingBox())) { // Tuinity - optimise voxelshapes collisions.add(ICollisionAccess.this.getWorldBorder().a());// Paper if (returnFast) return collisions; } @@ -112,26 +116,15 @@ public interface ICollisionAccess extends IBlockAccess { int j2 = cursorposition.e(); if (j2 != 3) { - // Paper start - ensure we don't load chunks - //int k2 = k1 >> 4; - //int l2 = i2 >> 4; - boolean far = entity != null && MCUtil.distanceSq(entity.locX(), y, entity.locZ(), x, y, z) > 14; - blockposition_mutableblockposition.setValues(x, y, z); - - boolean isRegionLimited = ICollisionAccess.this instanceof RegionLimitedWorldAccess; - IBlockData iblockdata = isRegionLimited ? Blocks.VOID_AIR.getBlockData() : ((!far && entity instanceof EntityPlayer) || (entity != null && entity.collisionLoadChunks) - ? ICollisionAccess.this.getType(blockposition_mutableblockposition) - : ICollisionAccess.this.getTypeIfLoaded(blockposition_mutableblockposition) - ); - if (iblockdata == null) { - if (!(entity instanceof EntityPlayer) || entity.world.paperConfig.preventMovingIntoUnloadedChunks) { - collisions.add(VoxelShapes.of(far ? entity.getBoundingBox() : new AxisAlignedBB(new BlockPosition(x, y, z)))); - if (returnFast) return collisions; - } - } else { - //blockposition_mutableblockposition.d(k1, l1, i2); // moved up - //IBlockData iblockdata = iblockaccess.getType(blockposition_mutableblockposition); // moved up - // Paper end + // Tuinity start - revert to vanilla + int k2 = k1 >> 4; + int l2 = i2 >> 4; + IBlockAccess iblockaccess = ICollisionAccess.this.c(k2, l2); + + if (iblockaccess != null) { + blockposition_mutableblockposition.d(k1, l1, i2); + IBlockData iblockdata = iblockaccess.getType(blockposition_mutableblockposition); + // Tuinity end - revert to vanilla if (!iblockdata.isAir() && (j2 != 1 || iblockdata.f()) && (j2 != 2 || iblockdata.getBlock() == Blocks.MOVING_PISTON)) { // Paper - fast track air VoxelShape voxelshape2 = iblockdata.b((IBlockAccess) ICollisionAccess.this, blockposition_mutableblockposition, voxelshapecollision); diff --git a/src/main/java/net/minecraft/server/LightEngineStorage.java b/src/main/java/net/minecraft/server/LightEngineStorage.java index f2575fb69..89985a61e 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(); protected volatile boolean j; @@ -246,7 +247,7 @@ public abstract class LightEngineStorage> e this.o.clear(); this.j = false; - ObjectIterator> objectiterator = Long2ObjectMaps.fastIterator(this.i); // Paper + ObjectIterator objectiterator = this.i_synchronized_map_real.long2ObjectEntrySet().fastIterator(); // Tuinity - use fast iterator to reduce entry creation - remove paper diff, it's ineffective Entry entry; long j; diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java index 87d580021..973bdd25c 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 4ab657903..3c9392077 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -954,7 +954,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant worlds = (List)this.getWorlds(); // PaperWorldMap makes this a list. + for (int i = 0; i < worlds.size(); ++i) { + WorldServer world = worlds.get(i); + long currTime = System.nanoTime(); + if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) { + continue; + } + if (!world.getChunkProvider().runTasks()) { + // we need to back off if this fails + world.lastMidTickExecuteFailure = currTime; + } else { + executed = true; + } + } + + return executed; + } + + public final void executeMidTickTasks() { + org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution"); + long startTime = System.nanoTime(); + if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) { + // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed. + // so, backoff to prevent this + return; + } + + co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming(); + try { + for (;;) { + boolean moreTasks = this.tickMidTickTasks(); + long currTime = System.nanoTime(); + long diff = currTime - startTime; + + if (!moreTasks || diff >= 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); @@ -1061,22 +1133,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(); @@ -1233,6 +1291,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 32886109d..5578818a0 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -70,6 +70,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 9b608d738..bf24cb0d2 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 volatile boolean ready; // Paper - Async-Anti-Xray - Ready flag for the network manager @@ -31,14 +31,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) { + public PacketPlayOutMapChunk(Chunk chunk, int i) { 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(); @@ -46,31 +48,12 @@ public class PacketPlayOutMapChunk implements Packet { this.b = chunkcoordintpair.z; this.h = i == 65535; this.d = new NBTTagCompound(); - Iterator iterator = chunk.f().iterator(); - - Entry entry; - - while (iterator.hasNext()) { - entry = (Entry) iterator.next(); - if (((HeightMap.Type) entry.getKey()).b()) { - this.d.set(((HeightMap.Type) entry.getKey()).a(), 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.j()), chunk, i, chunkPacketInfo); - // Paper end + // Tuinity start - improve oversized chunk data packet handling + // move the TE code up here so we can decide whether to split before writing this.g = Lists.newArrayList(); - iterator = chunk.getTileEntities().entrySet().iterator(); - int totalTileEntities = 0; // Paper + Iterator iterator = chunk.getTileEntities().entrySet().iterator(); + Entry entry; + int totalTileEntities = 0; // Paper // Tuinity while (iterator.hasNext()) { entry = (Entry) iterator.next(); @@ -79,14 +62,23 @@ public class PacketPlayOutMapChunk implements Packet { int j = blockposition.getY() >> 4; if (this.f() || (i & 1 << j) != 0) { - // Paper start - improve oversized chunk data packet handling - if (++totalTileEntities > TE_LIMIT) { + // Paper start - send signs separately + // 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) { PacketPlayOutTileEntityData updatePacket = tileentity.getUpdatePacket(); if (updatePacket != null) { this.extraPackets.add(updatePacket); continue; } } + // Tuinity end // Paper end NBTTagCompound nbttagcompound = tileentity.b(); if (tileentity instanceof TileEntitySkull) { TileEntitySkull.sanitizeTileEntityUUID(nbttagcompound); } // Paper @@ -94,7 +86,70 @@ public class PacketPlayOutMapChunk implements Packet { this.g.add(nbttagcompound); } } + iterator = chunk.f().iterator(); + // Tuinity end + + while (iterator.hasNext()) { + entry = (Entry) iterator.next(); + if (((HeightMap.Type) entry.getKey()).b()) { + this.d.set(((HeightMap.Type) entry.getKey()).a(), 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.j()), chunk, i, chunkPacketInfo); + // Paper end + // Tuinity start - improve oversized chunk data packet handling + // move the TE code up here so we can decide whether to split before writing +// 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 - improve oversized chunk data packet handling 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)); + } + } + } // Tuinity end - improve oversized chunk data packet handling } // Paper start - Async-Anti-Xray - Getter and Setter for the ready flag @@ -185,7 +240,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 } @@ -202,7 +257,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 4240ca81c..61e4dbcd4 100644 --- a/src/main/java/net/minecraft/server/PathfinderNormal.java +++ b/src/main/java/net/minecraft/server/PathfinderNormal.java @@ -444,7 +444,11 @@ public class PathfinderNormal extends PathfinderAbstract { } protected static PathType c(IBlockAccess iblockaccess, int i, int j, int k) { - BlockPosition blockposition = new BlockPosition(i, j, k); + // Tuinity start - reduce blockpos allocation + BlockPosition.PooledBlockPosition blockposition = BlockPosition.PooledBlockPosition.acquire(); + try { + blockposition.setValues(i, j, k); + // Tuinity end - reduce blockpos allocation IBlockData iblockdata = iblockaccess.getTypeIfLoaded(blockposition); // Paper if (iblockdata == null) return PathType.BLOCKED; // Paper Block block = iblockdata.getBlock(); @@ -474,7 +478,7 @@ public class PathfinderNormal extends PathfinderAbstract { } else if (block instanceof BlockLeaves) { return PathType.LEAVES; } else if (!block.a(TagsBlock.FENCES) && !block.a(TagsBlock.WALLS) && (!(block instanceof BlockFenceGate) || (Boolean) iblockdata.get(BlockFenceGate.OPEN))) { - Fluid fluid = iblockaccess.getFluid(blockposition); + Fluid fluid = iblockdata.getFluid(); // Tuinity - optimise out world#getFluid return fluid.a(TagsFluid.WATER) ? PathType.WATER : (fluid.a(TagsFluid.LAVA) ? PathType.LAVA : (iblockdata.a(iblockaccess, blockposition, PathMode.LAND) ? PathType.OPEN : PathType.BLOCKED)); } else { @@ -483,5 +487,10 @@ public class PathfinderNormal extends PathfinderAbstract { } else { return PathType.TRAPDOOR; } + // Tuinity start - reduce blockpos allocation + } finally { + blockposition.close(); + } + // Tuinity end - reduce blockpos allocation } } diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java index 9c1be2e2d..7e24de37b 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -504,6 +504,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); @@ -559,6 +560,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; @@ -568,7 +570,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(() -> { @@ -633,7 +636,8 @@ public class PlayerChunk { if (!flag2 && flag3) { // Paper start - cache ticking ready status int expectCreateCount = ++this.fullChunkCreateCount; - this.fullChunkFuture = playerchunkmap.b(this); MCUtil.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(); @@ -664,7 +668,8 @@ public class PlayerChunk { if (!flag4 && flag5) { // Paper start - cache ticking ready status - this.tickingFuture = playerchunkmap.a(this); MCUtil.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(); @@ -695,12 +700,20 @@ public class PlayerChunk { } // Paper start - cache ticking ready status - this.entityTickingFuture = playerchunkmap.b(this.location); MCUtil.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 @@ -712,6 +725,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; } // Paper start - raise IO/load priority if priority changes, use our preferred priority @@ -737,7 +761,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 8abf276a3..13f5857ae 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -117,31 +117,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(); } } @@ -192,6 +189,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 @@ -222,6 +220,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); @@ -239,6 +238,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 @@ -722,6 +722,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 { @@ -935,7 +936,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; @@ -969,7 +970,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()); @@ -1024,6 +1025,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 { @@ -1201,7 +1203,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } // Paper end this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error - }); + }).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) { @@ -1429,6 +1434,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } public final 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) { @@ -1442,6 +1448,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; @@ -1971,23 +1978,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 3e5dea60f..1859cd296 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -323,19 +323,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 @@ -364,7 +369,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; } @@ -981,7 +988,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); @@ -989,7 +996,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) { @@ -1022,7 +1034,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; } diff --git a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java index eb3269e0e..d9c9d01ae 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 @@ -25,6 +45,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 6df843461..272c1f0ea 100644 --- a/src/main/java/net/minecraft/server/PlayerInteractManager.java +++ b/src/main/java/net/minecraft/server/PlayerInteractManager.java @@ -20,14 +20,29 @@ public class PlayerInteractManager { public EntityPlayer player; private EnumGamemode gamemode; private boolean e; - private int lastDigTick; + private int lastDigTick; private long lastDigTime; // Tuinity - lag compensate block breaking private BlockPosition g; private int currentTick; - private boolean i; + private boolean i; private final boolean hasDestroyedTooFast() { return this.i; } // Tuinity - OBFHELPER private BlockPosition j; - private int k; + private int k; private final int getHasDestroyedTooFastStartTick() { return this.k; } // Tuinity - OBFHELPER + private long hasDestroyedTooFastStartTime; // Tuinity - lag compensate block breaking private int l; + // 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.g = BlockPosition.ZERO; @@ -73,7 +88,7 @@ public class PlayerInteractManager { if (iblockdata == null || iblockdata.isAir()) { // Paper this.i = false; } else { - float f = this.a(iblockdata, this.j, this.k); + float f = this.updateBlockBreakAnimation(iblockdata, this.j, this.getTimeDiggingTooFastLagCompensate()); // Tuinity - lag compensate destroying blocks if (f >= 1.0F) { this.i = false; @@ -93,7 +108,7 @@ public class PlayerInteractManager { this.l = -1; this.e = false; } else { - this.a(iblockdata, this.g, this.lastDigTick); + this.updateBlockBreakAnimation(iblockdata, this.g, this.getTimeDiggingLagCompensate()); // Tuinity - lag compensate destroying blocks } } @@ -101,6 +116,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); @@ -174,7 +195,7 @@ public class PlayerInteractManager { } // this.world.douseFire((EntityHuman) null, blockposition, enumdirection); // CraftBukkit - Moved down - 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); @@ -229,12 +250,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.l = j; } } else if (packetplayinblockdig_enumplayerdigtype == PacketPlayInBlockDig.EnumPlayerDigType.STOP_DESTROY_BLOCK) { if (blockposition.equals(this.g)) { - int k = this.currentTick - this.lastDigTick; + int k = this.getTimeDiggingLagCompensate(); // Tuinity - lag compensate block breaking iblockdata = this.world.getType(blockposition); if (!iblockdata.isAir()) { @@ -251,12 +272,18 @@ public class PlayerInteractManager { this.e = false; this.i = true; this.j = blockposition; - this.k = this.lastDigTick; + this.k = 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.e = false; if (!Objects.equals(this.g, blockposition) && !BlockPosition.ZERO.equals(this.g)) { // Paper @@ -268,7 +295,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 } } @@ -278,7 +305,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 2eb14bbf8..2be0b0803 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 df728e2c0..a66b02baa 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) throws IOException { - this(file.toPath(), file1.toPath(), RegionFileCompression.b); + // Tuinity start - add header recalculation boolean + this(file, file1, false); + } + public RegionFile(File file, File file1, boolean canRecalcHeader) throws IOException { + this(file.toPath(), file1.toPath(), RegionFileCompression.b, canRecalcHeader); + // Tuinity end } public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression) throws IOException { + // Tuinity start - add header recalculation boolean + this(java_nio_file_path, java_nio_file_path1, regionfilecompression, false); + } + public RegionFile(java.nio.file.Path java_nio_file_path, java.nio.file.Path java_nio_file_path1, RegionFileCompression regionfilecompression, boolean canRecalcHeader) throws IOException { + this.canRecalcHeader = canRecalcHeader; + // Tuinity end this.file = java_nio_file_path.toFile(); // Paper this.f = ByteBuffer.allocateDirect(8192); initOversizedState(); @@ -90,12 +436,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 @@ -105,20 +454,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 { @@ -142,6 +558,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 { @@ -150,6 +572,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; @@ -162,9 +590,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)); @@ -323,10 +763,15 @@ public class RegionFile implements AutoCloseable { } private ByteBuffer a() { + // 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; } @@ -363,6 +808,7 @@ public class RegionFile implements AutoCloseable { }; } + private final void flushHeader() throws IOException { this.b(); } // Tuinity - OBFHELPER private void b() 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 72118a7dc..3eebeee4c 100644 --- a/src/main/java/net/minecraft/server/RegionFileCache.java +++ b/src/main/java/net/minecraft/server/RegionFileCache.java @@ -18,6 +18,30 @@ public class RegionFileCache implements AutoCloseable { // Paper - no final this.b = file; } + // 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 @@ -51,9 +75,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); + RegionFile regionfile1 = new RegionFile(file, this.b, this instanceof IChunkLoader); // Tuinity - allow for chunk regionfiles to regen header this.cache.putAndMoveToFirst(i, regionfile1); // Paper start @@ -142,6 +166,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 @@ -157,6 +188,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 instanceof IChunkLoader) { + 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 c79aa4a80..36916459c 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 a8e64dfda..652ad383a 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 df2d6c3b0..9780ee07b 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 417152d16..42374183b 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 441157cf7..438d14dd2 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.a); - 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.a); + this.a = this.getNewContainerProperties(); + } + @Override protected IChatBaseComponent getContainerName() { return new ChatMessage("container.brewing", new Object[0]); diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java index 9a5f2da8c..50a8d59da 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.a); diff --git a/src/main/java/net/minecraft/server/TileEntityConduit.java b/src/main/java/net/minecraft/server/TileEntityConduit.java index 07f265b29..34c191d76 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 d5432bfeb..7d50b7056 100644 --- a/src/main/java/net/minecraft/server/TileEntityFurnace.java +++ b/src/main/java/net/minecraft/server/TileEntityFurnace.java @@ -30,14 +30,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 Map n; protected final Recipes c; - protected TileEntityFurnace(TileEntityTypes tileentitytypes, Recipes recipes) { - super(tileentitytypes); - this.items = NonNullList.a(3, ItemStack.a); - 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) { @@ -77,6 +77,22 @@ 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.a); + this.b = this.getNewContainerProperties(); this.n = Maps.newHashMap(); this.c = recipes; } diff --git a/src/main/java/net/minecraft/server/TileEntityJukeBox.java b/src/main/java/net/minecraft/server/TileEntityJukeBox.java index d66d9ff18..470f31083 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.a; diff --git a/src/main/java/net/minecraft/server/TileEntityLectern.java b/src/main/java/net/minecraft/server/TileEntityLectern.java index 6c2b48bdb..c3b854b6a 100644 --- a/src/main/java/net/minecraft/server/TileEntityLectern.java +++ b/src/main/java/net/minecraft/server/TileEntityLectern.java @@ -16,7 +16,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<>(); @@ -136,29 +136,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.a; diff --git a/src/main/java/net/minecraft/server/TileEntityPiston.java b/src/main/java/net/minecraft/server/TileEntityPiston.java index d700e8281..bd0ebacf7 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() { @@ -257,7 +272,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); } } @@ -282,7 +315,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); } } @@ -309,16 +347,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(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.b()); 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/Vec3D.java b/src/main/java/net/minecraft/server/Vec3D.java index 0c7f094e5..c2e4b5e8d 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; @@ -49,6 +49,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); } @@ -93,10 +94,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 1a5ec6152..5b52b380e 100644 --- a/src/main/java/net/minecraft/server/VillagePlace.java +++ b/src/main/java/net/minecraft/server/VillagePlace.java @@ -150,7 +150,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 0f95bcbcc..cb47d466c 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 4b3e632a8..5e24ce485 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,14 @@ 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 && 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); + } + 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) { diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 032b7acee..6ae9ec627 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -88,6 +88,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; @@ -137,6 +139,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.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(worlddata.getName()); // Tuinity - Server Config this.generator = gen; if (dimensionmanager.world == null) dimensionmanager.world = (WorldServer) this; // Paper this.world = new CraftWorld((WorldServer) this, gen, env); @@ -338,8 +341,28 @@ 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) { + // 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) { + // 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 @@ -372,7 +395,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) { @@ -440,6 +463,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) { + com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity IBlockData iblockdata = newBlock; IBlockData iblockdata1 = oldBlock; IBlockData iblockdata2 = actualBlock; @@ -888,6 +912,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 @@ -1108,7 +1133,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.ab_(); iterator.remove(); } @@ -1193,6 +1218,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable { public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb) { // copied from below List list = Lists.newArrayList(); + // Tuinity start - add list parameter + return this.getHardCollidingEntities(entity, axisalignedbb, list); + } + public List getHardCollidingEntities(@Nullable Entity entity, AxisAlignedBB axisalignedbb, 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); @@ -1216,8 +1246,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 535d08ffb..079a73196 100644 --- a/src/main/java/net/minecraft/server/WorldBorder.java +++ b/src/main/java/net/minecraft/server/WorldBorder.java @@ -45,12 +45,43 @@ public class WorldBorder { return axisalignedbb.maxX > this.c() && axisalignedbb.minX < this.e() && axisalignedbb.maxZ > this.d() && axisalignedbb.minZ < this.f(); } + // 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 a();} // Paper - OBFHELPER - + public final VoxelShape getCollisionShape() { return this.a(); } // Tuinity - OBFHELPER public VoxelShape a() { return this.i.m(); } @@ -66,18 +97,22 @@ public class WorldBorder { return Math.min(d6, d3); } + public final double getMinX() { return this.c(); } // Tuinity - OBFHELPER public double c() { return this.i.a(); } + public final double getMinZ() { return this.d(); } // Tuinity - OBFHELPER public double d() { return this.i.c(); } + public final double getMaxX() { return this.e(); } // Tuinity - OBFHELPER public double e() { return this.i.b(); } + public final double getMaxZ() { return this.f(); } // Tuinity - OBFHELPER public double f() { return this.i.d(); } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index 46e261b65..24cd10c96 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 { private static final Logger LOGGER = LogManager.getLogger(); private final List globalEntityList = Lists.newArrayList(); - 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 { 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 I; private boolean ticking; @@ -190,6 +190,100 @@ public class WorldServer extends World { } // 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 public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { super(worlddata, dimensionmanager, executor, (world, worldprovider) -> { // Paper - pass executor down @@ -249,6 +343,349 @@ public class WorldServer extends World { 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.f()) && (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 Set set) { + 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) : this.getHardCollidingEntities(entity, axisalignedbb1); + + for (int i = 0, len = entities.size(); i < len; ++i) { + Entity otherEntity = entities.get(i); + + if (set != null && set.contains(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.f()) && (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 Set set, 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, IEntitySelector.notSpectator(), entities); + } else { + this.getHardCollidingEntities(entity, axisalignedbb1, entities); + } + + for (int i = 0, len = entities.size(); i < len; ++i) { + Entity otherEntity = entities.get(i); + + if (set != null && set.contains(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, Set set) { + return !this.collidesWithAnyBlockOrWorldBorder(entity, axisalignedbb, true) && !this.hardCollidesWithAnyEntities(entity, axisalignedbb, set); + } + // Tuinity end - optimise collision + // CraftBukkit start @Override protected TileEntity getTileEntity(BlockPosition pos, boolean validate) { @@ -446,7 +883,7 @@ public class WorldServer extends World { } timings.scheduledBlocks.stopTiming(); // Spigot - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic gameprofilerfiller.exitEnter("raid"); this.timings.raids.startTiming(); // Paper - timings this.persistentRaid.a(); @@ -459,7 +896,7 @@ public class WorldServer extends World { timings.doSounds.startTiming(); // Spigot this.ad(); 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 @@ -493,14 +930,13 @@ public class WorldServer extends World { gameprofilerfiller.exitEnter("regular"); 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 TimingHistory.entityTicks += this.globalEntityList.size(); // Paper while (objectiterator.hasNext()) { - Entry entry = (Entry) objectiterator.next(); - Entity entity1 = (Entity) entry.getValue(); + Entity entity1 = (Entity) objectiterator.next(); // Tuinity Entity entity2 = entity1.getVehicle(); /* CraftBukkit start - We prevent spawning in general, so this butchering is not needed @@ -536,7 +972,7 @@ public class WorldServer extends World { gameprofilerfiller.enter("remove"); if (entity1.dead) { this.removeEntityFromChunk(entity1); - objectiterator.remove(); + this.entitiesById.remove(entity1.getId()); // Tuinity this.unregisterEntity(entity1); } @@ -544,6 +980,7 @@ public class WorldServer extends World { } timings.entityTick.stopTiming(); // Spigot + objectiterator.finishedIterating(); // Tuinity this.tickingEntities = false; // Paper start for (java.lang.Runnable run : this.afterEntityTickingTasks) { @@ -555,7 +992,7 @@ public class WorldServer extends World { } this.afterEntityTickingTasks.clear(); // Paper end - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic try (co.aikar.timings.Timing ignored = this.timings.newEntities.startTiming()) { // Paper - timings while ((entity = (Entity) this.entitiesToAdd.poll()) != null) { @@ -566,7 +1003,7 @@ public class WorldServer extends World { gameprofilerfiller.exit(); timings.tickEntities.stopTiming(); // Spigot - this.getMinecraftServer().midTickLoadChunks(); // Paper + // Tuinity - replace logic this.tickBlockEntities(); } @@ -780,7 +1217,26 @@ public class WorldServer extends World { } + // 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)) { ++TimingHistory.entityTicks; // Paper - timings // Spigot start @@ -825,6 +1281,11 @@ public class WorldServer extends World { } // 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) { @@ -1340,7 +1801,7 @@ public class WorldServer extends World { 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!")); } @@ -1368,6 +1829,7 @@ public class WorldServer extends World { 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 @@ -1434,12 +1896,16 @@ public class WorldServer extends World { 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 @@ -1455,7 +1921,7 @@ public class WorldServer extends World { return; } // Paper end - if (this.tickingEntities) { + if (false && this.tickingEntities) { // Tuinity if (!entity.isQueuedForRegister) { // Paper this.entitiesToAdd.add(entity); entity.isQueuedForRegister = true; // Paper @@ -1463,6 +1929,7 @@ public class WorldServer extends World { } else { entity.isQueuedForRegister = false; // Paper this.entitiesById.put(entity.getId(), entity); + this.entitiesForIteration.add(entity); // Tuinity if (entity instanceof EntityEnderDragon) { EntityComplexPart[] aentitycomplexpart = ((EntityEnderDragon) entity).eo(); int i = aentitycomplexpart.length; @@ -1471,6 +1938,7 @@ public class WorldServer extends World { EntityComplexPart entitycomplexpart = aentitycomplexpart[j]; this.entitiesById.put(entitycomplexpart.getId(), entitycomplexpart); + this.entitiesForIteration.add(entitycomplexpart); // Tuinity } } @@ -1495,12 +1963,16 @@ public class WorldServer extends World { // 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 @@ -1516,7 +1988,7 @@ public class WorldServer extends World { } 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); @@ -1618,7 +2090,9 @@ public class WorldServer extends World { 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(); @@ -1627,6 +2101,9 @@ public class WorldServer extends World { 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 3030c347e..76f0f258e 100644 --- a/src/main/java/net/minecraft/server/WorldUpgrader.java +++ b/src/main/java/net/minecraft/server/WorldUpgrader.java @@ -220,7 +220,7 @@ public class WorldUpgrader { int l = Integer.parseInt(matcher.group(2)) << 5; try { - RegionFile regionfile = new RegionFile(file2, file1); + RegionFile regionfile = new RegionFile(file2, file1, 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 b909af3d6..3c8166149 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java @@ -75,7 +75,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 568aefdf6..760752eae 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -205,7 +205,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"); @@ -815,6 +815,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.worldData.setDifficulty(config.difficulty); world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals); @@ -843,6 +844,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 @@ -1747,7 +1749,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 @@ -2134,6 +2139,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 f4a1be34f..1ee5ce50b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -333,6 +333,13 @@ public class CraftWorld implements World { this.generator = gen; environment = env; + + //Tuinity start - per world spawn limits + monsterSpawn = world.tuinityConfig.spawnLimitMonsters; + animalSpawn = world.tuinityConfig.spawnLimitAnimals; + waterAnimalSpawn = world.tuinityConfig.spawnLimitWaterAnimals; + ambientSpawn = world.tuinityConfig.spawnLimitAmbient; + //Tuinity end } @Override @@ -399,14 +406,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 @@ -490,6 +490,7 @@ public class CraftWorld implements World { net.minecraft.server.IChunkAccess chunk = world.getChunkProvider().getChunkAtIfLoadedImmediately(x, z); // Paper if (chunk != null) { world.getChunkProvider().removeTicket(TicketType.PLUGIN, chunk.getPos(), 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; @@ -2505,7 +2506,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 d6e5d014c..90a2f8fea 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 a5f981cd0..197c2ffab 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 @@ -500,15 +500,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 d3017db1b..8eaed6bfd 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -139,7 +139,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 a0746a169..adba4a8a2 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 d16d3fe58..5a7b714cc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -519,6 +519,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 bb18740eb..b048ec8ea 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..7c1ef532d 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.getWorldData().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.getWorldData().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, "------------------------------" ); // -- 2.26.2