diff --git a/.github/renovate.json b/.github/renovate.json index 526af5a60..d0e59d791 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -20,7 +20,14 @@ "net.fabricmc.fabric-api:fabric-api", "com.github.luben:zstd-jni", "org.jetbrains.kotlin.jvm", - "log4j" + "log4j", + "org.apache.logging.log4j:log4j-api", + "org.apache.logging.log4j:log4j-bom", + "org.apache.logging.log4j:log4j-slf4j-impl", + "org.apache.logging.log4j:log4j-core", + "org.bstats:bstats-sponge", + "org.spongepowered:spongeapi", + "org.yaml:snakeyaml" ], "labels": ["Renovate"], "rebaseWhen": "conflicted", diff --git a/build.gradle.kts b/build.gradle.kts index 3f6169bb7..e441c1336 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.7.1") +var rootVersion by extra("2.7.2") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") diff --git a/buildSrc/src/main/kotlin/AdapterConfig.kt b/buildSrc/src/main/kotlin/AdapterConfig.kt index 6cad58e3b..60f9a5733 100644 --- a/buildSrc/src/main/kotlin/AdapterConfig.kt +++ b/buildSrc/src/main/kotlin/AdapterConfig.kt @@ -15,7 +15,6 @@ fun Project.applyPaperweightAdapterConfiguration() { dependencies { "implementation"(project(":worldedit-bukkit")) - "implementation"(platform("com.intellectualsites.bom:bom-newest:1.33")) } tasks.named("assemble") { diff --git a/buildSrc/src/main/kotlin/CommonJavaConfig.kt b/buildSrc/src/main/kotlin/CommonJavaConfig.kt index 73389ce83..549a26037 100644 --- a/buildSrc/src/main/kotlin/CommonJavaConfig.kt +++ b/buildSrc/src/main/kotlin/CommonJavaConfig.kt @@ -40,12 +40,11 @@ fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, banSlf4j: Boolean dependencies { "compileOnly"("com.google.code.findbugs:jsr305:3.0.2") - "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.9.2") - "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.9.2") - "testImplementation"("org.mockito:mockito-core:5.1.1") - "testImplementation"("org.mockito:mockito-junit-jupiter:5.1.1") - "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.9.2") - "implementation"(platform("com.intellectualsites.bom:bom-newest:1.33")) + "testImplementation"("org.junit.jupiter:junit-jupiter-api:5.10.0") + "testImplementation"("org.junit.jupiter:junit-jupiter-params:5.10.0") + "testImplementation"("org.mockito:mockito-core:5.4.0") + "testImplementation"("org.mockito:mockito-junit-jupiter:5.4.0") + "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:5.10.0") } // Java 8 turns on doclint which we fail diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3f463bc9c..b73ec8509 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,11 @@ [versions] # Minecraft expectations +paper = "1.20.1-R0.1-SNAPSHOT" fastutil = "8.5.9" guava = "31.1-jre" log4j = "2.19.0" +gson = "2.10" +snakeyaml = "2.0" # Plugins dummypermscompat = "1.10" @@ -11,13 +14,16 @@ mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.99.5.7" +towny = "0.99.5.16" +plotsquared = "7.0.0" # Third party bstats = "3.0.2" sparsebitset = "1.2" parallelgzip = "1.0.5" adventure = "4.14.0" +adventure-bukkit = "4.3.0" +checkerqual = "3.38.0" truezip = "6.8.4" auto-value = "1.10.2" findbugs = "3.0.2" @@ -29,22 +35,32 @@ jlibnoise = "1.0.0" jchronic = "0.2.4a" lz4-java = "1.8.0" lz4-stream = "1.0.0" +commons-cli = "1.5.0" +paperlib = "1.0.8" +paster = "1.1.5" +vault = "1.7.1" +serverlib = "2.3.4" ## Internal text-adapter = "3.0.6" text = "3.0.4" piston = "0.5.7" # Tests -mockito = "5.4.0" +mockito = "5.5.0" # Gradle plugins pluginyml = "0.6.0" +minotaur = "2.8.4" [libraries] # Minecraft expectations +paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" } log4jBom = { group = "org.apache.logging.log4j", name = "log4j-bom", version.ref = "log4j" } +log4jApi = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeyaml" } # Plugins dummypermscompat = { group = "com.sk89q", name = "dummypermscompat", version.ref = "dummypermscompat" } @@ -54,9 +70,12 @@ griefprevention = { group = "com.github.TechFortress", name = "GriefPrevention", griefdefender = { group = "com.griefdefender", name = "api", version.ref = "griefdefender" } residence = { group = "com.bekvon.bukkit.residence", name = "Residence", version.ref = "residence" } towny = { group = "com.palmergames.bukkit.towny", name = "towny", version.ref = "towny" } +plotSquaredCore = { group = "com.intellectualsites.plotsquared", name = "plotsquared-core", version.ref = "plotsquared" } +plotSquaredBukkit = { group = "com.intellectualsites.plotsquared", name = "plotsquared-bukkit", version.ref = "plotsquared" } # Third Party bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } +bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" } sparsebitset = { group = "com.zaxxer", name = "SparseBitSet", version.ref = "sparsebitset" } parallelgzip = { group = "org.anarres", name = "parallelgzip", version.ref = "parallelgzip" } adventureNbt = { group = "net.kyori", name = "adventure-nbt", version.ref = "adventure" } @@ -73,6 +92,15 @@ jlibnoise = { group = "com.sk89q.lib", name = "jlibnoise", version.ref = "jlibno jchronic = { group = "com.sk89q", name = "jchronic", version.ref = "jchronic" } lz4Java = { group = "org.lz4", name = "lz4-java", version.ref = "lz4-java" } lz4JavaStream = { group = "net.jpountz", name = "lz4-java-stream", version.ref = "lz4-stream" } +commonsCli = { group = "commons-cli", name = "commons-cli", version.ref = "commons-cli" } +paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" } +adventureApi = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" } +adventureMiniMessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" } +adventureBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventure-bukkit" } +paster = { group = "com.intellectualsites.paster", name = "Paster", version.ref = "paster" } +vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" } +serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.ref = "serverlib" } +checkerqual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerqual" } # Internal ## Text @@ -94,3 +122,4 @@ log4jCore = { group = "org.apache.logging.log4j", name = "log4j-core", version.r [plugins] pluginyml = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" } +minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 033e24c4c..7f93135c4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d5f..ac72c34e8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index fcb6fca14..0adc8e1a5 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts index 940dfcb0b..24c121c3f 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts @@ -22,5 +22,5 @@ configurations.all { dependencies { the().paperDevBundle("1.17.1-R0.1-20220414.034903-210") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java index 688a84a14..0854fc7ad 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java @@ -85,11 +85,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final MethodHandle methodGetVisibleChunk; - private static final int CHUNKSECTION_BASE; - private static final int CHUNKSECTION_SHIFT; - private static final Field fieldLock; - private static final long fieldLockOffset; private static final Field fieldGameEventDispatcherSections; private static final MethodHandle methodremoveBlockEntityTicker; @@ -127,15 +123,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { getVisibleChunkIfPresent.setAccessible(true); methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent); - Unsafe unsafe = ReflectionUtils.getUnsafe(); if (!PaperLib.isPaper()) { - fieldLock = PalettedContainer.class.getDeclaredField(Refraction.pickName("lock", "m")); - fieldLockOffset = unsafe.objectFieldOffset(fieldLock); + fieldLock.setAccessible(true); } else { // in paper, the used methods are synchronized properly fieldLock = null; - fieldLockOffset = -1; } fieldGameEventDispatcherSections = LevelChunk.class.getDeclaredField(Refraction.pickName( @@ -152,13 +145,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); fieldRemove.setAccessible(true); - - CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); - int scale = unsafe.arrayIndexScale(LevelChunkSection[].class); - if ((scale & (scale - 1)) != 0) { - throw new Error("data type scale not a power of two"); - } - CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (RuntimeException e) { throw e; } catch (Throwable rethrow) { @@ -173,9 +159,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { LevelChunkSection value, int layer ) { - long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; if (layer >= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -190,14 +175,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - Semaphore currentLock = (Semaphore) unsafe.getObject(blocks, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(blocks); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(blocks, fieldLockOffset, newLock); + fieldLock.set(blocks, newLock); return newLock; } } catch (Throwable e) { diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java index 3d9694af9..63d93156b 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/regen/PaperweightRegen.java @@ -184,9 +184,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -332,7 +329,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts index aaa4643d2..f7f40ce66 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts @@ -13,5 +13,5 @@ repositories { dependencies { // https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ the().paperDevBundle("1.18.2-R0.1-20220920.010157-167") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java index dbf7f88f3..1075694b4 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java @@ -92,14 +92,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final MethodHandle methodGetVisibleChunk; - private static final int CHUNKSECTION_BASE; - private static final int CHUNKSECTION_SHIFT; - private static final Field fieldThreadingDetector; - private static final long fieldThreadingDetectorOffset; - private static final Field fieldLock; - private static final long fieldLockOffset; private static final MethodHandle methodRemoveGameEventListener; private static final MethodHandle methodremoveTickingBlockEntity; @@ -136,20 +130,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { getVisibleChunkIfPresent.setAccessible(true); methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent); - Unsafe unsafe = ReflectionUtils.getUnsafe(); if (!PaperLib.isPaper()) { fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f")); - fieldThreadingDetectorOffset = unsafe.objectFieldOffset(fieldThreadingDetector); - + fieldThreadingDetector.setAccessible(true); fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c")); - fieldLockOffset = unsafe.objectFieldOffset(fieldLock); + fieldLock.setAccessible(true); } else { // in paper, the used methods are synchronized properly fieldThreadingDetector = null; - fieldThreadingDetectorOffset = -1; - fieldLock = null; - fieldLockOffset = -1; } Method removeGameEventListener = LevelChunk.class.getDeclaredMethod( @@ -169,13 +158,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); fieldRemove.setAccessible(true); - - CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); - int scale = unsafe.arrayIndexScale(LevelChunkSection[].class); - if ((scale & (scale - 1)) != 0) { - throw new Error("data type scale not a power of two"); - } - CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (RuntimeException e) { throw e; } catch (Throwable rethrow) { @@ -190,9 +172,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { LevelChunkSection value, int layer ) { - long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; if (layer >= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -207,19 +188,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( - blocks, - fieldThreadingDetectorOffset - ); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); synchronized (currentThreadingDetector) { - Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); + fieldLock.set(currentThreadingDetector, newLock); return newLock; } } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java index 51bd22bdb..8403d531d 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/regen/PaperweightRegen.java @@ -178,9 +178,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -345,7 +342,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts index 5e1f312bc..df27ae5d6 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts @@ -12,5 +12,5 @@ repositories { dependencies { the().paperDevBundle("1.19.4-R0.1-20230608.201059-104") - compileOnly("io.papermc:paperlib") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java index d351bbacd..16210db38 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightPlatformAdapter.java @@ -99,14 +99,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { private static final MethodHandle methodGetVisibleChunk; - private static final int CHUNKSECTION_BASE; - private static final int CHUNKSECTION_SHIFT; - private static final Field fieldThreadingDetector; - private static final long fieldThreadingDetectorOffset; - private static final Field fieldLock; - private static final long fieldLockOffset; private static final MethodHandle methodRemoveGameEventListener; private static final MethodHandle methodremoveTickingBlockEntity; @@ -149,20 +143,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { getVisibleChunkIfPresent.setAccessible(true); methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent); - Unsafe unsafe = ReflectionUtils.getUnsafe(); if (!PaperLib.isPaper()) { fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f")); - fieldThreadingDetectorOffset = unsafe.objectFieldOffset(fieldThreadingDetector); - + fieldThreadingDetector.setAccessible(true); fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c")); - fieldLockOffset = unsafe.objectFieldOffset(fieldLock); + fieldLock.setAccessible(true); } else { // in paper, the used methods are synchronized properly fieldThreadingDetector = null; - fieldThreadingDetectorOffset = -1; - fieldLock = null; - fieldLockOffset = -1; } Method removeGameEventListener = LevelChunk.class.getDeclaredMethod( @@ -185,12 +174,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q")); fieldRemove.setAccessible(true); - CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); - int scale = unsafe.arrayIndexScale(LevelChunkSection[].class); - if ((scale & (scale - 1)) != 0) { - throw new Error("data type scale not a power of two"); - } - CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); boolean chunkRewrite; try { ServerLevel.class.getDeclaredMethod("getEntityLookup"); @@ -226,9 +209,8 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { LevelChunkSection value, int layer ) { - long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; if (layer >= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -243,19 +225,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( - blocks, - fieldThreadingDetectorOffset - ); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); synchronized (currentThreadingDetector) { - Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); + fieldLock.set(currentThreadingDetector, newLock); return newLock; } } diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java index 4f22e8734..7e2f3eaee 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/regen/PaperweightRegen.java @@ -192,9 +192,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -372,7 +369,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts index cf13348ff..20bff7359 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_20/build.gradle.kts @@ -12,6 +12,6 @@ repositories { dependencies { // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ - the().paperDevBundle("1.20.1-R0.1-20230623.105806-29") - compileOnly("io.papermc:paperlib") + the().paperDevBundle("1.20.1-R0.1-20230916.212543-167") + compileOnly(libs.paperlib) } diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java index e495a078c..428176c5f 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightAdapter.java @@ -637,7 +637,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter= 0 && layer < sections.length) { - return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); + return ReflectionUtils.compareAndSet(sections, expected, value, layer); } return false; } @@ -266,19 +248,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } try { synchronized (section) { - Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); - ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( - blocks, - fieldThreadingDetectorOffset - ); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); synchronized (currentThreadingDetector) { - Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); - unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); + fieldLock.set(currentThreadingDetector, newLock); return newLock; } } diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java index b5d5a2733..99e001837 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/regen/PaperweightRegen.java @@ -192,9 +192,6 @@ public class PaperweightRegen extends Regenerator super.chunkStati.put(new ChunkStatusWrap(s), c)); + chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c)); return true; } @@ -373,7 +370,7 @@ public class PaperweightRegen extends Regenerator processChunk(Long xz, List accessibleChunks) { + public CompletableFuture processChunk(List accessibleChunks) { return chunkStatus.generate( Runnable::run, // TODO revisit, we might profit from this somehow? freshWorld, diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 1e3bbf4a1..4f7211db5 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -3,7 +3,7 @@ import io.papermc.paperweight.userdev.attribute.Obfuscation plugins { `java-library` - id("com.modrinth.minotaur") version "2.8.3" + alias(libs.plugins.minotaur) } project.description = "Bukkit" @@ -74,19 +74,19 @@ dependencies { implementation(libs.fastutil) // Platform expectations - compileOnly("io.papermc.paper:paper-api") { + compileOnly(libs.paper) { exclude("junit", "junit") exclude(group = "org.slf4j", module = "slf4j-api") } // Logging - localImplementation("org.apache.logging.log4j:log4j-api") + localImplementation(libs.log4jApi) localImplementation(libs.log4jBom) { because("Spigot provides Log4J (sort of, not in API, implicitly part of server)") } // Plugins - compileOnly("com.github.MilkBowl:VaultAPI") { isTransitive = false } + compileOnly(libs.vault) { isTransitive = false } compileOnly(libs.dummypermscompat) { exclude("com.github.MilkBowl", "VaultAPI") } @@ -101,26 +101,26 @@ dependencies { compileOnly(libs.griefdefender) { isTransitive = false } compileOnly(libs.residence) { isTransitive = false } compileOnly(libs.towny) { isTransitive = false } - compileOnly("com.intellectualsites.plotsquared:plotsquared-bukkit") { isTransitive = false } - compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false } + compileOnly(libs.plotSquaredBukkit) { isTransitive = false } + compileOnly(libs.plotSquaredCore) { isTransitive = false } // Third party - implementation("io.papermc:paperlib") - implementation("org.bstats:bstats-bukkit") { isTransitive = false } + implementation(libs.paperlib) + implementation(libs.bstatsBukkit) { isTransitive = false } implementation(libs.bstatsBase) { isTransitive = false } - implementation("dev.notmyfault.serverlib:ServerLib") - implementation("com.intellectualsites.paster:Paster") { isTransitive = false } + implementation(libs.serverlib) + implementation(libs.paster) { isTransitive = false } api(libs.lz4Java) { isTransitive = false } api(libs.sparsebitset) { isTransitive = false } api(libs.parallelgzip) { isTransitive = false } - compileOnly("net.kyori:adventure-api") - compileOnlyApi("org.checkerframework:checker-qual") + compileOnly(libs.adventureApi) + compileOnlyApi(libs.checkerqual) // Tests testImplementation(libs.mockito) - testImplementation("net.kyori:adventure-api") - testImplementation("org.checkerframework:checker-qual") - testImplementation("io.papermc.paper:paper-api") { isTransitive = true } + testImplementation(libs.adventureApi) + testImplementation(libs.checkerqual) + testImplementation(libs.paper) { isTransitive = true } } tasks.named("processResources") { @@ -174,7 +174,7 @@ tasks.named("shadowJar") { include(dependency("it.unimi.dsi:fastutil")) } relocate("org.incendo.serverlib", "com.fastasyncworldedit.serverlib") { - include(dependency("dev.notmyfault.serverlib:ServerLib:2.3.1")) + include(dependency("dev.notmyfault.serverlib:ServerLib:2.3.4")) } relocate("com.intellectualsites.paster", "com.fastasyncworldedit.paster") { include(dependency("com.intellectualsites.paster:Paster")) diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java index 7c00ebb2b..d0d9bb652 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java @@ -22,14 +22,14 @@ import com.sk89q.worldedit.world.block.BaseBlock; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; import org.apache.logging.log4j.Logger; -import org.bukkit.World; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; import org.bukkit.generator.WorldInfo; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -42,7 +42,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Function; -import java.util.stream.Collectors; /** * Represents an abstract regeneration handler. @@ -62,7 +61,7 @@ public abstract class Regenerator chunkStati = new LinkedHashMap<>(); + protected final Map chunkStatuses = new LinkedHashMap<>(); private final Long2ObjectLinkedOpenHashMap protoChunks = new Long2ObjectLinkedOpenHashMap<>(); private final Long2ObjectOpenHashMap chunks = new Long2ObjectOpenHashMap<>(); protected boolean generateConcurrent = true; @@ -85,19 +84,19 @@ public abstract class Regenerator> chunkCoordsForRadius = new Int2ObjectOpenHashMap<>(); - chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { + Int2ObjectOpenHashMap chunkCoordsForRadius = new Int2ObjectOpenHashMap<>(); + chunkStatuses.keySet().stream().mapToInt(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { if (radius == -1) { //ignore ChunkStatus.EMPTY return; } @@ -186,19 +185,19 @@ public abstract class Regenerator>> worldlimits = new Int2ObjectOpenHashMap<>(); - chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { + Int2ObjectOpenHashMap>> worldLimits = new Int2ObjectOpenHashMap<>(); + chunkStatuses.keySet().stream().mapToInt(ChunkStatusWrapper::requiredNeighborChunkRadius0).distinct().forEach(radius -> { if (radius == -1) { //ignore ChunkStatus.EMPTY return; } Long2ObjectOpenHashMap> map = new Long2ObjectOpenHashMap<>(); - for (Long xz : chunkCoordsForRadius.get(radius)) { + for (long xz : chunkCoordsForRadius.get(radius)) { int x = MathMan.unpairIntX(xz); int z = MathMan.unpairIntY(xz); List l = new ArrayList<>((radius + 1 + radius) * (radius + 1 + radius)); @@ -209,80 +208,63 @@ public abstract class Regenerator entry : chunkStati.entrySet()) { + for (Map.Entry entry : chunkStatuses.entrySet()) { ChunkStatus chunkStatus = entry.getKey(); int radius = chunkStatus.requiredNeighborChunkRadius0(); - List coords = chunkCoordsForRadius.get(radius); + long[] coords = chunkCoordsForRadius.get(radius); + Long2ObjectOpenHashMap> limitsForRadius = worldLimits.get(radius); if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) { - SequentialTasks>> tasks = getChunkStatusTaskRows(coords, radius); - for (ConcurrentTasks> para : tasks) { + SequentialTasks> tasks = getChunkStatusTaskRows(coords, radius); + for (ConcurrentTasks para : tasks) { List scheduled = new ArrayList<>(tasks.size()); - for (SequentialTasks row : para) { + for (LongList row : para) { scheduled.add(() -> { - for (Long xz : row) { - chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + for (long xz : row) { + chunkStatus.processChunkSave(xz, limitsForRadius.get(xz)); } }); } - try { - List> futures = new ArrayList<>(); - scheduled.forEach(task -> futures.add(executor.submit(task))); - for (Future future : futures) { - future.get(); - } - } catch (Exception e) { - e.printStackTrace(); - } + runAndWait(scheduled); } } else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) { // every chunk can be processed individually - List scheduled = new ArrayList<>(coords.size()); + List scheduled = new ArrayList<>(coords.length); for (long xz : coords) { - scheduled.add(() -> { - chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); - }); - } - try { - List> futures = new ArrayList<>(); - scheduled.forEach(task -> futures.add(executor.submit(task))); - for (Future future : futures) { - future.get(); - } - } catch (Exception e) { - e.printStackTrace(); + scheduled.add(() -> chunkStatus.processChunkSave(xz, limitsForRadius.get(xz))); } + runAndWait(scheduled); } else { // Concurrency.NONE or generateConcurrent == false // run sequential but submit to different thread // running regen on the main thread otherwise triggers async-only events on the main thread executor.submit(() -> { for (long xz : coords) { - chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + chunkStatus.processChunkSave(xz, limitsForRadius.get(xz)); } }).get(); // wait until finished this step } } //convert to proper chunks - for (Long xz : chunkCoordsForRadius.get(0)) { + for (long xz : chunkCoordsForRadius.get(0)) { ProtoChunk proto = protoChunks.get(xz); chunks.put(xz, createChunk(proto)); } //final chunkstatus ChunkStatus FULL = getFullChunkStatus(); - for (Long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0! + for (long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0! Chunk chunk = chunks.get(xz); - FULL.processChunkSave(xz, Arrays.asList(chunk)); + FULL.processChunkSave(xz, List.of(chunk)); } //populate List populators = getBlockPopulators(); - for (Long xz : chunkCoordsForRadius.get(0)) { + for (long xz : chunkCoordsForRadius.get(0)) { int x = MathMan.unpairIntX(xz); int z = MathMan.unpairIntY(xz); @@ -302,6 +284,18 @@ public abstract class Regenerator tasks) { + try { + List> futures = new ArrayList<>(); + tasks.forEach(task -> futures.add(executor.submit(task))); + for (Future future : futures) { + future.get(); + } + } catch (Exception e) { + LOGGER.catching(e); + } + } + private void copyToWorld() { //Setting Blocks boolean genbiomes = options.shouldRegenBiomes(); @@ -437,7 +431,7 @@ public abstract class Regenerator initSourceQueueCache(); //algorithms - private List getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks + private long[] getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks BlockVector3 oldMin = region.getMinimumPoint(); BlockVector3 newMin = BlockVector3.at( (oldMin.getX() >> 4 << 4) - border * 16, @@ -455,76 +449,79 @@ public abstract class Regenerator MathMan.pairInt(c.getX(), c.getZ())) - .collect(Collectors.toList()); + .mapToLong(c -> MathMan.pairInt(c.getX(), c.getZ())) + .toArray(); } /** * Creates a list of chunkcoord rows that may be executed concurrently * - * @param allcoords the coords that should be sorted into rows, must be sorted by z and x + * @param allCoords the coords that should be sorted into rows, must be sorted by z and x * @param requiredNeighborChunkRadius the radius of neighbor chunks that may not be written to concurrently (ChunkStatus * .requiredNeighborRadius) * @return a list of chunkcoords rows that may be executed concurrently */ - private SequentialTasks>> getChunkStatusTaskRows( - List allcoords, + private SequentialTasks> getChunkStatusTaskRows( + long[] allCoords, int requiredNeighborChunkRadius ) { - int requiredneighbors = Math.max(0, requiredNeighborChunkRadius); + int requiredNeighbors = Math.max(0, requiredNeighborChunkRadius); - int minx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(0)); - int maxx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(allcoords.size() - 1)); - int minz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(0)); - int maxz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(allcoords.size() - 1)); - SequentialTasks>> tasks; - if (maxz - minz > maxx - minx) { - int numlists = Math.min(requiredneighbors * 2 + 1, maxx - minx + 1); + final int coordsCount = allCoords.length; + long first = coordsCount == 0 ? 0 : allCoords[0]; + long last = coordsCount == 0 ? 0 : allCoords[coordsCount - 1]; + int minX = MathMan.unpairIntX(first); + int maxX = MathMan.unpairIntX(last); + int minZ = MathMan.unpairIntY(first); + int maxZ = MathMan.unpairIntY(last); + SequentialTasks> tasks; + if (maxZ - minZ > maxX - minX) { + int numlists = Math.min(requiredNeighbors * 2 + 1, maxX - minX + 1); - Int2ObjectOpenHashMap> byx = new Int2ObjectOpenHashMap(); - int expectedListLength = (allcoords.size() + 1) / (maxx - minx); + Int2ObjectOpenHashMap byX = new Int2ObjectOpenHashMap<>(); + int expectedListLength = (coordsCount + 1) / (maxX - minX); //init lists - for (int i = minx; i <= maxx; i++) { - byx.put(i, new SequentialTasks(expectedListLength)); + for (int i = minX; i <= maxX; i++) { + byX.put(i, new LongArrayList(expectedListLength)); } //sort into lists by x coord - for (Long xz : allcoords) { - byx.get(MathMan.unpairIntX(xz)).add(xz); + for (long allCoord : allCoords) { + byX.get(MathMan.unpairIntX(allCoord)).add(allCoord); } //create parallel tasks - tasks = new SequentialTasks(numlists); + tasks = new SequentialTasks<>(numlists); for (int offset = 0; offset < numlists; offset++) { - ConcurrentTasks> para = new ConcurrentTasks((maxz - minz + 1) / numlists + 1); - for (int i = 0; minx + i * numlists + offset <= maxx; i++) { - para.add(byx.get(minx + i * numlists + offset)); + ConcurrentTasks para = new ConcurrentTasks<>((maxZ - minZ + 1) / numlists + 1); + for (int i = 0; minX + i * numlists + offset <= maxX; i++) { + para.add(byX.get(minX + i * numlists + offset)); } tasks.add(para); } } else { - int numlists = Math.min(requiredneighbors * 2 + 1, maxz - minz + 1); + int numlists = Math.min(requiredNeighbors * 2 + 1, maxZ - minZ + 1); - Int2ObjectOpenHashMap> byz = new Int2ObjectOpenHashMap(); - int expectedListLength = (allcoords.size() + 1) / (maxz - minz); + Int2ObjectOpenHashMap byZ = new Int2ObjectOpenHashMap<>(); + int expectedListLength = (coordsCount + 1) / (maxZ - minZ); //init lists - for (int i = minz; i <= maxz; i++) { - byz.put(i, new SequentialTasks(expectedListLength)); + for (int i = minZ; i <= maxZ; i++) { + byZ.put(i, new LongArrayList(expectedListLength)); } //sort into lists by x coord - for (Long xz : allcoords) { - byz.get(MathMan.unpairIntY(xz)).add(xz); + for (long allCoord : allCoords) { + byZ.get(MathMan.unpairIntY(allCoord)).add(allCoord); } //create parallel tasks - tasks = new SequentialTasks(numlists); + tasks = new SequentialTasks<>(numlists); for (int offset = 0; offset < numlists; offset++) { - ConcurrentTasks> para = new ConcurrentTasks((maxx - minx + 1) / numlists + 1); - for (int i = 0; minz + i * numlists + offset <= maxz; i++) { - para.add(byz.get(minz + i * numlists + offset)); + ConcurrentTasks para = new ConcurrentTasks<>((maxX - minX + 1) / numlists + 1); + for (int i = 0; minZ + i * numlists + offset <= maxZ; i++) { + para.add(byZ.get(minZ + i * numlists + offset)); } tasks.add(para); } @@ -576,15 +573,14 @@ public abstract class Regenerator processChunk(Long xz, List accessibleChunks); + public abstract CompletableFuture processChunk(List accessibleChunks); - void processChunkSave(Long xz, List accessibleChunks) { + void processChunkSave(long xz, List accessibleChunks) { try { - processChunk(xz, accessibleChunks).get(); + processChunk(accessibleChunks).get(); } catch (Exception e) { LOGGER.error( "Error while running " + name() + " on chunk " + MathMan.unpairIntX(xz) + "/" + MathMan.unpairIntY(xz), @@ -597,16 +593,16 @@ public abstract class Regenerator extends Tasks { - public SequentialTasks(int expectedsize) { - super(expectedsize); + public SequentialTasks(int expectedSize) { + super(expectedSize); } } public static class ConcurrentTasks extends Tasks { - public ConcurrentTasks(int expectedsize) { - super(expectedsize); + public ConcurrentTasks(int expectedSize) { + super(expectedSize); } } @@ -615,8 +611,8 @@ public abstract class Regenerator tasks; - public Tasks(int expectedsize) { - tasks = new ArrayList(expectedsize); + public Tasks(int expectedSize) { + tasks = new ArrayList<>(expectedSize); } public void add(T task) { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java index cd98105f3..a969167af 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java @@ -25,7 +25,6 @@ import com.sk89q.worldedit.util.YAMLConfiguration; import com.sk89q.worldedit.util.report.Unreported; import org.apache.logging.log4j.LogManager; -import java.io.File; import java.nio.file.Path; /** diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 8278142b6..d35ea9a28 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -42,6 +42,7 @@ import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.lifecycle.Lifecycled; import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.registry.Registries; +import io.papermc.lib.PaperLib; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Server; @@ -258,6 +259,14 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser return SUPPORTED_SIDE_EFFECTS; } + @Override + public long getTickCount() { + if (PaperLib.isPaper()) { + return Bukkit.getCurrentTick(); + } + return super.getTickCount(); + } + public void unregisterCommands() { dynamicCommands.unregisterCommands(); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java index 06f8f81ed..8c9db1c3e 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.internal.event.InteractionDebouncer; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.World; @@ -55,6 +56,7 @@ import java.util.Optional; public class WorldEditListener implements Listener { private final WorldEditPlugin plugin; + private final InteractionDebouncer debouncer; /** * Construct the object. @@ -63,6 +65,7 @@ public class WorldEditListener implements Listener { */ public WorldEditListener(WorldEditPlugin plugin) { this.plugin = plugin; + debouncer = new InteractionDebouncer(plugin.getInternalPlatform()); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -128,62 +131,58 @@ public class WorldEditListener implements Listener { */ @EventHandler public void onPlayerInteract(PlayerInteractEvent event) { - if (!plugin.getInternalPlatform().isHookingEvents()) { - return; - } - - if (event.useItemInHand() == Result.DENY) { - return; - } - - if (event.getHand() == EquipmentSlot.OFF_HAND) { + if (!plugin.getInternalPlatform().isHookingEvents() + || event.useItemInHand() == Result.DENY + || event.getHand() == EquipmentSlot.OFF_HAND + || event.getAction() == Action.PHYSICAL) { return; } final Player player = plugin.wrapPlayer(event.getPlayer()); + + if (event.getAction() != Action.LEFT_CLICK_BLOCK) { + Optional previousResult = debouncer.getDuplicateInteractionResult(player); + if (previousResult.isPresent()) { + if (previousResult.get()) { + event.setCancelled(true); + } + return; + } + } + final World world = player.getWorld(); final WorldEdit we = plugin.getWorldEdit(); final Direction direction = BukkitAdapter.adapt(event.getBlockFace()); + final Block clickedBlock = event.getClickedBlock(); + final Location pos = clickedBlock == null ? null : new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); - Action action = event.getAction(); - if (action == Action.LEFT_CLICK_BLOCK) { - final Block clickedBlock = event.getClickedBlock(); - final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); - - if (we.handleBlockLeftClick(player, pos, direction)) { - event.setCancelled(true); - } - - if (we.handleArmSwing(player)) { - event.setCancelled(true); - } - - } else if (action == Action.LEFT_CLICK_AIR) { - - if (we.handleArmSwing(player)) { - event.setCancelled(true); - } - - } else if (action == Action.RIGHT_CLICK_BLOCK) { - final Block clickedBlock = event.getClickedBlock(); - final Location pos = new Location(world, clickedBlock.getX(), clickedBlock.getY(), clickedBlock.getZ()); - - if (we.handleBlockRightClick(player, pos, direction)) { - event.setCancelled(true); - } - - if (we.handleRightClick(player)) { - event.setCancelled(true); - } - } else if (action == Action.RIGHT_CLICK_AIR) { - if (we.handleRightClick(player)) { - event.setCancelled(true); - } + boolean result = false; + switch (event.getAction()) { + case LEFT_CLICK_BLOCK: + result = we.handleBlockLeftClick(player, pos, direction) || we.handleArmSwing(player); + break; + case LEFT_CLICK_AIR: + result = we.handleArmSwing(player); + break; + case RIGHT_CLICK_BLOCK: + result = we.handleBlockRightClick(player, pos, direction) || we.handleRightClick(player); + break; + case RIGHT_CLICK_AIR: + result = we.handleRightClick(player); + break; + default: + break; + } + debouncer.setLastInteraction(player, result); + if (result) { + event.setCancelled(true); } } @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { + debouncer.clearInteraction(plugin.wrapPlayer(event.getPlayer())); + plugin.getWorldEdit().getEventBus().post(new SessionIdleEvent(new BukkitPlayer.SessionKeyImpl(event.getPlayer()))); } diff --git a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java index 981812e1b..052c772d5 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java +++ b/worldedit-bukkit/src/test/java/com/sk89q/wepif/TestOfflinePermissible.java @@ -36,6 +36,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.annotation.Nonnull; +import java.time.Duration; +import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -142,6 +144,11 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible { return false; } + @Override + public boolean isConnected() { + return false; + } + @Override public String getName() { return "Tester"; @@ -161,6 +168,24 @@ public class TestOfflinePermissible implements OfflinePlayer, Permissible { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public > @Nullable E ban( + @Nullable final String reason, + @Nullable final Instant expires, + @Nullable final String source + ) { + return null; + } + + @Override + public > @Nullable E ban( + @Nullable final String reason, + @Nullable final Duration duration, + @Nullable final String source + ) { + return null; + } + @Override public @Nullable BanEntry ban( @Nullable final String reason, diff --git a/worldedit-cli/build.gradle.kts b/worldedit-cli/build.gradle.kts index ff0c9b5c1..6f0335e0e 100644 --- a/worldedit-cli/build.gradle.kts +++ b/worldedit-cli/build.gradle.kts @@ -27,16 +27,16 @@ dependencies { // Minecraft expectations annotationProcessor(libs.guava) - implementation("com.google.guava:guava") - implementation("com.google.code.gson:gson") + implementation(libs.guava) + implementation(libs.gson) // Logging implementation(libs.log4jBom) { because("We control Log4J on this platform") } - implementation("org.apache.logging.log4j:log4j-api") + implementation(libs.log4jApi) implementation(libs.log4jCore) - implementation("commons-cli:commons-cli:1.5.0") + implementation(libs.commonsCli) api(libs.parallelgzip) { isTransitive = false } api(libs.lz4Java) } diff --git a/worldedit-core/build.gradle.kts b/worldedit-core/build.gradle.kts index 4e624bd3b..a58230b2c 100644 --- a/worldedit-core/build.gradle.kts +++ b/worldedit-core/build.gradle.kts @@ -11,7 +11,7 @@ applyPlatformAndCoreConfiguration() dependencies { constraints { - implementation("org.yaml:snakeyaml") { + implementation(libs.snakeyaml) { version { strictly("2.0") } because("Bukkit provides SnakeYaml") } @@ -24,17 +24,17 @@ dependencies { // Minecraft expectations implementation(libs.fastutil) - implementation("com.google.guava:guava") - implementation("com.google.code.gson:gson") + implementation(libs.guava) + implementation(libs.gson) // Platform expectations - implementation("org.yaml:snakeyaml") + implementation(libs.snakeyaml) // Logging - implementation("org.apache.logging.log4j:log4j-api") + implementation(libs.log4jApi) // Plugins - compileOnly("com.intellectualsites.plotsquared:plotsquared-core") { isTransitive = false } + compileOnly(libs.plotSquaredCore) { isTransitive = false } // ensure this is on the classpath for the AP annotationProcessor(libs.guava) @@ -45,11 +45,11 @@ dependencies { compileOnly(libs.truezip) implementation(libs.findbugs) implementation(libs.rhino) - compileOnly("net.kyori:adventure-api") + compileOnly(libs.adventureApi) compileOnlyApi(libs.adventureNbt) - compileOnlyApi("net.kyori:adventure-text-minimessage") + compileOnlyApi(libs.adventureMiniMessage) implementation(libs.zstd) { isTransitive = false } - compileOnly("com.intellectualsites.paster:Paster") + compileOnly(libs.paster) compileOnly(libs.lz4Java) { isTransitive = false } compileOnly(libs.sparsebitset) compileOnly(libs.parallelgzip) { isTransitive = false } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index 174d59f37..d0aeb5058 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -400,6 +400,7 @@ public class Settings extends Config { "of a waterlogged fence). For blocking/remapping of all occurrences of a property like waterlogged, see", "remap-properties below.", "To generate a blank list, substitute the default content with a set of square brackets [] instead.", + "The 'worldedit.anyblock' permission is not considered here.", "Example block property blocking:", " - \"minecraft:conduit[waterlogged=true]\"", " - \"minecraft:piston[extended=false,facing=west]\"", diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ReflectionUtils.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ReflectionUtils.java index 607117535..7b8ffcd2c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ReflectionUtils.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ReflectionUtils.java @@ -4,19 +4,19 @@ import sun.misc.Unsafe; import javax.annotation.Nonnull; import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Comparator; /** * This is an internal class not meant to be used outside the FAWE internals. */ public class ReflectionUtils { + private static final VarHandle REFERENCE_ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(Object[].class); private static Unsafe UNSAFE; static { @@ -33,6 +33,21 @@ public class ReflectionUtils { return t.isInstance(o) ? t.cast(o) : null; } + /** + * Performs CAS on the array element at the given index. + * + * @param array the array in which to compare and set the value + * @param expectedValue the value expected to be at the index + * @param newValue the new value to be set at the index if the expected value matches + * @param index the index at which to compare and set the value + * @param the type of elements in the array + * @return true if the value at the index was successfully updated to the new value, false otherwise + * @see VarHandle#compareAndSet(Object...) + */ + public static boolean compareAndSet(T[] array, T expectedValue, T newValue, int index) { + return REFERENCE_ARRAY_HANDLE.compareAndSet(array, index, expectedValue, newValue); + } + public static void setAccessibleNonFinal(Field field) { // let's make the field accessible field.setAccessible(true); diff --git a/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java b/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java index 146428c3e..f50bd991f 100644 --- a/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java +++ b/worldedit-core/src/main/java/com/sk89q/util/yaml/YAMLProcessor.java @@ -98,6 +98,8 @@ public class YAMLProcessor extends YAMLNode { LoaderOptions loaderOptions = new LoaderOptions(); try { + int yamlAliasLimit = Integer.getInteger("worldedit.yaml.aliasLimit", 50); + loaderOptions.setMaxAliasesForCollections(yamlAliasLimit); // 64 MB default int yamlCodePointLimit = Integer.getInteger("worldedit.yaml.codePointLimit", 64 * 1024 * 1024); loaderOptions.setCodePointLimit(yamlCodePointLimit); @@ -105,7 +107,7 @@ public class YAMLProcessor extends YAMLNode { // pre-1.32 snakeyaml } - yaml = new Yaml(new SafeConstructor(new LoaderOptions()), representer, dumperOptions, loaderOptions); + yaml = new Yaml(new SafeConstructor(loaderOptions), representer, dumperOptions, loaderOptions); this.file = file; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java index 533fe054d..c8f908b4c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlatform.java @@ -55,4 +55,9 @@ public abstract class AbstractPlatform implements Platform { return null; } + @Override + public long getTickCount() { + return System.nanoTime() / 50_000_000; + } + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java index 822e7a3f8..f20f3ce9b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java @@ -216,6 +216,14 @@ public interface Platform extends Keyed { */ Set getSupportedSideEffects(); + /** + * Get the number of ticks since the server started. + * On some platforms this value may be an approximation based on the JVM run time. + * + * @return The number of ticks since the server started. + */ + long getTickCount(); + //FAWE start /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java new file mode 100644 index 000000000..b33570cd9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/event/InteractionDebouncer.java @@ -0,0 +1,69 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.event; + +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.util.Identifiable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public class InteractionDebouncer { + private final Platform platform; + private final Map lastInteractions = new HashMap<>(); + + public InteractionDebouncer(Platform platform) { + this.platform = platform; + } + + public void clearInteraction(Identifiable player) { + lastInteractions.remove(player.getUniqueId()); + } + + public void setLastInteraction(Identifiable player, boolean result) { + lastInteractions.put(player.getUniqueId(), new Interaction(platform.getTickCount(), result)); + } + + public Optional getDuplicateInteractionResult(Identifiable player) { + Interaction last = lastInteractions.get(player.getUniqueId()); + if (last == null) { + return Optional.empty(); + } + + long now = platform.getTickCount(); + if (now - last.tick <= 1) { + return Optional.of(last.result); + } + + return Optional.empty(); + } + + private static class Interaction { + public final long tick; + public final boolean result; + + public Interaction(long tick, boolean result) { + this.tick = tick; + this.result = result; + } + } +} diff --git a/worldedit-sponge/build.gradle.kts b/worldedit-sponge/build.gradle.kts index aba91acea..3c6783886 100644 --- a/worldedit-sponge/build.gradle.kts +++ b/worldedit-sponge/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { }) api("org.apache.logging.log4j:log4j-api") api("org.bstats:bstats-sponge:1.7") - testImplementation("org.mockito:mockito-core:5.4.0") + testImplementation("org.mockito:mockito-core:5.5.0") } <<<<<<< HEAD