diff --git a/.github/workflows/announce-release-on-discord.yml b/.github/workflows/announce-release-on-discord.yml index 810366540..77fa89732 100644 --- a/.github/workflows/announce-release-on-discord.yml +++ b/.github/workflows/announce-release-on-discord.yml @@ -13,7 +13,7 @@ jobs: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_USERNAME: FastAsyncWorldEdit Release DISCORD_AVATAR: https://raw.githubusercontent.com/IntellectualSites/Assets/main/plugins/FastAsyncWorldEdit/FastAsyncWorldEdit.png - uses: Ilshidur/action-discord@0c4b27844ba47cb1c7bee539c8eead5284ce9fa9 # ratchet:Ilshidur/action-discord@0.3.2 + uses: Ilshidur/action-discord@0.3.2 with: args: | "<@&525015715300900875> <@&706463154804097105> <@&671372968462516240>" diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 60c938e66..11f0d1d5d 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -16,6 +16,12 @@ jobs: uses: actions/setup-java@v3 with: distribution: temurin + cache: gradle java-version: 17 - name: Build on ${{ matrix.os }} - run: ./gradlew clean build + run: ./gradlew build -s + - name: Archive artifacts + uses: actions/upload-artifact@v3 + with: + name: FastAsyncWorldEdit-SNAPSHOT + path: worldedit-bukkit/build/libs/FastAsyncWorldEdit-Bukkit-*.jar diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3c5eb079f..f4287607f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,5 +1,7 @@ name: "CodeQL" on: + push: + branches: [main] pull_request: # The branches below must be a subset of the branches above branches: [main] @@ -18,6 +20,12 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: temurin + cache: gradle + java-version: 17 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: diff --git a/.gitignore b/.gitignore index 14ba6416a..0a9d6020d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ worldedit-core/src/main/resources/lang/* /worldedit-core/.factorypath .DS_Store +### Run server ignore +run-* diff --git a/Jenkinsfile b/Jenkinsfile index ec2813783..61000e9fb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,14 +4,13 @@ pipeline { disableConcurrentBuilds() } stages { - stage('Set JDK 17') { - steps { - tool name: 'Temurin-17.0.6+10', type: 'jdk' - } - } stage('Build') { steps { - sh './gradlew clean build' + withEnv([ + "PATH+JAVA=${tool 'Temurin-17.0.7_7'}/bin" + ]) { + sh './gradlew clean build' + } } } stage('Archive artifacts') { diff --git a/build.gradle.kts b/build.gradle.kts index 5bbde35da..f79edd650 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ import xyz.jpenilla.runpaper.task.RunServer plugins { id("io.github.gradle-nexus.publish-plugin") version "1.3.0" - id("xyz.jpenilla.run-paper") version "2.0.1" + id("xyz.jpenilla.run-paper") version "2.1.0" } if (!File("$rootDir/.git").exists()) { @@ -34,7 +34,7 @@ logger.lifecycle(""" ******************************************* """) -var rootVersion by extra("2.6.0") +var rootVersion by extra("2.6.3") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -105,7 +105,7 @@ tasks { } nexusPublishing { - repositories { + this.repositories { sonatype { nexusUrl.set(URI.create("https://s01.oss.sonatype.org/service/local/")) snapshotRepositoryUrl.set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/")) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 15237a86e..de4d0df68 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -22,9 +22,9 @@ val properties = Properties().also { props -> dependencies { implementation(gradleApi()) - implementation("org.ajoberstar.grgit:grgit-gradle:5.0.0") - implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2") - implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.3") + implementation("org.ajoberstar.grgit:grgit-gradle:5.2.0") + implementation("com.github.johnrengelman:shadow:8.1.1") + implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.5") } kotlin { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d450d7ad9..892d1c7cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,12 +6,12 @@ log4j = "2.19.0" # Plugins dummypermscompat = "1.10" -worldguard-bukkit = "7.0.7" +worldguard-bukkit = "7.0.8" mapmanager = "1.8.0-SNAPSHOT" griefprevention = "16.18.1" griefdefender = "2.1.0-SNAPSHOT" residence = "4.5._13.1" -towny = "0.98.4.18" +towny = "0.99.1.2" # Third party bstats = "3.0.2" @@ -23,7 +23,7 @@ auto-value = "1.10.1" findbugs = "3.0.2" rhino-runtime = "1.7.14" zstd-jni = "1.4.8-1" # Not latest as it can be difficult to obtain latest ZSTD libs -antlr4 = "4.12.0" +antlr4 = "4.13.0" json-simple = "1.1.1" jlibnoise = "1.0.0" jchronic = "0.2.4a" @@ -36,7 +36,7 @@ text = "3.0.4" piston = "0.5.7" # Tests -mockito = "5.2.0" +mockito = "5.3.1" # Gradle plugins pluginyml = "0.5.3" @@ -54,7 +54,7 @@ mapmanager = { group = "com.github.InventivetalentDev", name = "MapManager", ver griefprevention = { group = "com.github.TechFortress", name = "GriefPrevention", version.ref = "griefprevention" } griefdefender = { group = "com.griefdefender", name = "api", version.ref = "griefdefender" } residence = { group = "com.bekvon.bukkit.residence", name = "Residence", version.ref = "residence" } -towny = { group = "com.github.TownyAdvanced", name = "Towny", version.ref = "towny" } +towny = { group = "com.palmergames.bukkit.towny", name = "towny", version.ref = "towny" } # Third Party bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa..c1962a79e 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 508322917..37aef8d3f 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-7.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..aeb74cbb4 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +141,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +149,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in 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 977b4af10..940dfcb0b 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_17_1/build.gradle.kts @@ -1,3 +1,5 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + applyPaperweightAdapterConfiguration() plugins { @@ -19,6 +21,6 @@ configurations.all { dependencies { - paperDevBundle("1.17.1-R0.1-20220414.034903-210") + the().paperDevBundle("1.17.1-R0.1-20220414.034903-210") compileOnly("io.papermc: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/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java index d90fc50c8..64cf2c9d6 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java @@ -103,6 +103,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.OptionalInt; import java.util.Set; import java.util.function.Supplier; @@ -658,10 +659,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements .registryAccess() .ownedRegistryOrThrow( Registry.BIOME_REGISTRY); - return biomeRegistry.stream() - .map(biomeRegistry::getKey) - .map(CraftNamespacedKey::fromMinecraft) - .collect(Collectors.toList()); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (ResourceLocation key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java index 5554fe25b..09fa8f7a5 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java @@ -445,10 +445,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << layer; - // Changes may still be written to chunk SET - char[] tmp = set.load(layerNo); - char[] setArr = new char[4096]; - System.arraycopy(tmp, 0, setArr, 0, 4096); + char[] setArr = set.load(layerNo); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. 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 3ea9df730..69ab5fe9b 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 @@ -29,7 +29,9 @@ import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; import net.minecraft.util.BitStorage; +import net.minecraft.util.Unit; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelAccessor; @@ -209,10 +211,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } // Avoid "async" methods from the main thread. @@ -230,6 +234,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + // Ensure chunk is definitely loaded before applying a ticket + net.minecraft.server.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addRegionTicket(TicketType.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + } + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; try { 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 2f8b75130..aaa4643d2 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_18_2/build.gradle.kts @@ -1,3 +1,5 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + plugins { java } @@ -10,6 +12,6 @@ repositories { dependencies { // https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ - paperDevBundle("1.18.2-R0.1-20220920.010157-167") + the().paperDevBundle("1.18.2-R0.1-20220920.010157-167") compileOnly("io.papermc:paperlib") } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java index 25f93844a..e2481e72b 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java @@ -656,10 +656,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements .registryAccess() .ownedRegistryOrThrow( Registry.BIOME_REGISTRY); - return biomeRegistry.stream() - .map(biomeRegistry::getKey).filter(Objects::nonNull) - .map(CraftNamespacedKey::fromMinecraft) - .collect(Collectors.toList()); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (ResourceLocation key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java index f225bd441..06a74cfd1 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java @@ -491,9 +491,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] tmp = set.load(layerNo); - char[] setArr = new char[4096]; - System.arraycopy(tmp, 0, setArr, 0, 4096); + char[] setArr = set.load(layerNo); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. 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 9de310b96..8e982a84e 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 @@ -29,9 +29,11 @@ import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; import net.minecraft.util.BitStorage; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; +import net.minecraft.util.Unit; import net.minecraft.util.ZeroBitStorage; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; @@ -237,10 +239,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } // Avoid "async" methods from the main thread. @@ -258,6 +262,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + // Ensure chunk is definitely loaded before applying a ticket + net.minecraft.server.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addRegionTicket(TicketType.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + } + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; try { diff --git a/worldedit-bukkit/adapters/adapter-1_19/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_19/build.gradle.kts index b8c797ee4..cfb2cfe03 100644 --- a/worldedit-bukkit/adapters/adapter-1_19/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_19/build.gradle.kts @@ -1,3 +1,5 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + plugins { java } @@ -9,6 +11,6 @@ repositories { } dependencies { - paperDevBundle("1.19.2-R0.1-20221206.184705-189") + the().paperDevBundle("1.19.2-R0.1-20221206.184705-189") compileOnly("io.papermc:paperlib") } diff --git a/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightFaweAdapter.java index a2a593b5e..b9aa9cf7f 100644 --- a/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightFaweAdapter.java @@ -645,10 +645,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements .registryAccess() .ownedRegistryOrThrow( Registry.BIOME_REGISTRY); - return biomeRegistry.stream() - .map(biomeRegistry::getKey).filter(Objects::nonNull) - .map(CraftNamespacedKey::fromMinecraft) - .collect(Collectors.toList()); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (ResourceLocation key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightGetBlocks.java index 7a0b00f1f..e0a851c1b 100644 --- a/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightGetBlocks.java @@ -488,9 +488,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] tmp = set.load(layerNo); - char[] setArr = new char[4096]; - System.arraycopy(tmp, 0, setArr, 0, 4096); + char[] setArr = set.load(layerNo); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. diff --git a/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightPlatformAdapter.java index 0423a03a7..2fcb72225 100644 --- a/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R1/PaperweightPlatformAdapter.java @@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; import net.minecraft.util.BitStorage; import net.minecraft.util.ExceptionCollector; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; +import net.minecraft.util.Unit; import net.minecraft.util.ZeroBitStorage; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; @@ -270,10 +272,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } // Avoid "async" methods from the main thread. @@ -291,6 +295,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + // Ensure chunk is definitely loaded before applying a ticket + io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addRegionTicket(TicketType.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + } + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; try { diff --git a/worldedit-bukkit/adapters/adapter-1_19_3/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_19_3/build.gradle.kts index fb422300e..4706a5752 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_3/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_19_3/build.gradle.kts @@ -1,3 +1,5 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + plugins { java } @@ -10,6 +12,6 @@ repositories { dependencies { // https://papermc.io/repo/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ - paperDevBundle("1.19.3-R0.1-20230312.180621-141") + the().paperDevBundle("1.19.3-R0.1-20230312.180621-141") compileOnly("io.papermc:paperlib") } diff --git a/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightFaweAdapter.java index af0bcfa32..7cfce755e 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightFaweAdapter.java @@ -650,10 +650,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements .getServer() .registryAccess() .registryOrThrow(BIOME); - return biomeRegistry.stream() - .map(biomeRegistry::getKey).filter(Objects::nonNull) - .map(CraftNamespacedKey::fromMinecraft) - .collect(Collectors.toList()); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (ResourceLocation key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightGetBlocks.java index f3114fd1a..3ee8314fd 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightGetBlocks.java @@ -490,9 +490,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] tmp = set.load(layerNo); - char[] setArr = new char[4096]; - System.arraycopy(tmp, 0, setArr, 0, 4096); + char[] setArr = set.load(layerNo); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. diff --git a/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightPlatformAdapter.java index 1a4d3e523..5f8e39940 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R2/PaperweightPlatformAdapter.java @@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; import net.minecraft.util.BitStorage; import net.minecraft.util.ExceptionCollector; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; +import net.minecraft.util.Unit; import net.minecraft.util.ZeroBitStorage; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; @@ -267,10 +269,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } // Avoid "async" methods from the main thread. @@ -288,6 +292,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + // Ensure chunk is definitely loaded before applying a ticket + io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addRegionTicket(TicketType.PLUGIN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + } + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; try { 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 4d1c1ada5..0df6dc795 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_19_4/build.gradle.kts @@ -1,3 +1,5 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + plugins { java } @@ -9,6 +11,6 @@ repositories { } dependencies { - paperDevBundle("1.19.4-R0.1-20230412.010331-64") + the().paperDevBundle("1.19.4-R0.1-20230601.025018-99") compileOnly("io.papermc:paperlib") } diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java index e23e18a2e..7cc904e5c 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; -import com.sk89q.worldedit.blocks.TileEntityBlock; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -50,7 +49,6 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; -import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.item.ItemType; @@ -60,7 +58,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.WritableRegistry; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.IntTag; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; @@ -71,14 +68,12 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.DirectionProperty; import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.chunk.LevelChunkSection; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -86,7 +81,6 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.TreeType; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_19_R3.CraftChunk; import org.bukkit.craftbukkit.v1_19_R3.CraftServer; import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState; @@ -602,10 +596,17 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements .getServer() .registryAccess() .registryOrThrow(BIOME); - return biomeRegistry.stream() - .map(biomeRegistry::getKey).filter(Objects::nonNull) - .map(CraftNamespacedKey::fromMinecraft) - .collect(Collectors.toList()); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (ResourceLocation key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java index c12f57002..1b10ddd77 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightGetBlocks.java @@ -490,9 +490,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc bitMask |= 1 << getSectionIndex; - char[] tmp = set.load(layerNo); - char[] setArr = new char[4096]; - System.arraycopy(tmp, 0, setArr, 0, 4096); + char[] setArr = set.load(layerNo); // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was // submitted to keep loaded internal chunks to queue target size. 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 33be73c69..9f8938cec 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 @@ -29,10 +29,12 @@ import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; import net.minecraft.util.BitStorage; import net.minecraft.util.ExceptionCollector; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; +import net.minecraft.util.Unit; import net.minecraft.util.ZeroBitStorage; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; @@ -182,7 +184,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { removeBlockEntityTicker.setAccessible(true); methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker); - fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); + fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "q")); fieldRemove.setAccessible(true); CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); @@ -292,10 +294,12 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); return nmsChunk; } // Avoid "async" methods from the main thread. @@ -305,6 +309,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); try { CraftChunk chunk = (CraftChunk) future.get(); + addTicket(serverLevel, chunkX, chunkZ); return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); } catch (Throwable e) { e.printStackTrace(); @@ -313,6 +318,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + // Ensure chunk is definitely loaded before applying a ticket + io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + } + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; try { diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index 41242611f..303e8fb0c 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.+" + id("com.modrinth.minotaur") version "2.7.5" } project.description = "Bukkit" @@ -32,6 +32,10 @@ repositories { name = "OSS Sonatype Snapshots" url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + maven { + name = "Glaremasters" + url = uri("https://repo.glaremasters.me/repository/towny/") + } flatDir { dir(File("src/main/resources")) } } @@ -47,7 +51,13 @@ val adapters = configurations.create("adapters") { isCanBeResolved = true shouldResolveConsistentlyWith(configurations["runtimeClasspath"]) attributes { - attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.OBFUSCATED)) + attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, + if ((project.findProperty("enginehub.obf.none") as String?).toBoolean()) { + objects.named(Obfuscation.NONE) + } else { + objects.named(Obfuscation.OBFUSCATED) + } + ) } } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitQueueHandler.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitQueueHandler.java index 6e1bb1f9e..eec38e7ac 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitQueueHandler.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitQueueHandler.java @@ -3,8 +3,6 @@ package com.fastasyncworldedit.bukkit.adapter; import co.aikar.timings.Timings; import com.fastasyncworldedit.bukkit.listener.ChunkListener; import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.sk89q.worldedit.internal.util.LogManagerCompat; -import org.apache.logging.log4j.Logger; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -31,7 +29,7 @@ public class BukkitQueueHandler extends QueueHandler { } @Override - public void startSet(boolean parallel) { + public void startUnsafe(boolean parallel) { ChunkListener.physicsFreeze = true; if (parallel) { try { @@ -51,7 +49,7 @@ public class BukkitQueueHandler extends QueueHandler { } @Override - public void endSet(boolean parallel) { + public void endUnsafe(boolean parallel) { ChunkListener.physicsFreeze = false; if (parallel) { try { 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 8f3b4d118..7c00ebb2b 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 @@ -165,7 +165,11 @@ public abstract class Regenerator { + for (long xz : coords) { + chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + } + }).get(); // wait until finished this step } } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/listener/ChunkListener.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/listener/ChunkListener.java index 5d2e35335..7b9599084 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/listener/ChunkListener.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/listener/ChunkListener.java @@ -371,48 +371,6 @@ public abstract class ChunkListener implements Listener { } } - /** - * Prevent firework from loading chunks. - */ - @EventHandler(priority = EventPriority.LOWEST) - public void onChunkLoad(ChunkLoadEvent event) { - if (!Settings.settings().TICK_LIMITER.FIREWORKS_LOAD_CHUNKS) { - Chunk chunk = event.getChunk(); - Entity[] entities = chunk.getEntities(); - World world = chunk.getWorld(); - - Exception e = new Exception(); - int start = 14; - int end = 22; - int depth = Math.min(end, getDepth(e)); - - for (int frame = start; frame < depth; frame++) { - StackTraceElement elem = getElement(e, frame); - if (elem == null) { - return; - } - String className = elem.getClassName(); - int len = className.length(); - if (len > 15 && className.charAt(len - 15) == 'E' && className - .endsWith("EntityFireworks")) { - for (Entity ent : world.getEntities()) { - if (ent.getType() == EntityType.FIREWORK) { - Vector velocity = ent.getVelocity(); - double vertical = Math.abs(velocity.getY()); - if (Math.abs(velocity.getX()) > vertical - || Math.abs(velocity.getZ()) > vertical) { - LOGGER.warn( - "[FAWE `tick-limiter`] Detected and cancelled rogue FireWork at {}", - ent.getLocation()); - ent.remove(); - } - } - } - } - } - } - } - @EventHandler(priority = EventPriority.LOWEST) public void onItemSpawn(ItemSpawnEvent event) { if (physicsFreeze) { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index 876517dd6..f1edf65f7 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -112,11 +112,15 @@ public class WorldEditPlugin extends JavaPlugin { private BukkitServerInterface platform; private BukkitConfiguration config; private BukkitPermissionAttachmentManager permissionAttachmentManager; + // Fawe start + private BukkitCommandSender bukkitConsoleCommandSender; + // Fawe end @Override public void onLoad() { //FAWE start + this.bukkitConsoleCommandSender = new BukkitCommandSender(this, Bukkit.getConsoleSender()); // This is already covered by Spigot, however, a more pesky warning with a proper explanation over "Ambiguous plugin name..." can't hurt. Plugin[] plugins = Bukkit.getServer().getPluginManager().getPlugins(); for (Plugin p : plugins) { @@ -594,7 +598,7 @@ public class WorldEditPlugin extends JavaPlugin { return new BukkitBlockCommandSender(this, (BlockCommandSender) sender); } - return new BukkitCommandSender(this, sender); + return bukkitConsoleCommandSender; } public BukkitServerInterface getInternalPlatform() { diff --git a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIExtraCommands.java b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIExtraCommands.java index 9f6389463..27cc08821 100644 --- a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIExtraCommands.java +++ b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/CLIExtraCommands.java @@ -23,6 +23,7 @@ import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.regions.selector.ExtendingCuboidRegionSelector; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.task.Task; import com.sk89q.worldedit.world.World; @@ -39,9 +40,12 @@ public class CLIExtraCommands { desc = "Select the entire world" ) public void selectWorld(Actor actor, World world, LocalSession session) { - session.setRegionSelector(world, new CuboidRegionSelector( - world, world.getMinimumPoint(), world.getMaximumPoint() - )); + final CuboidRegionSelector selector; + if (session.getRegionSelector(world) instanceof ExtendingCuboidRegionSelector) { + selector = new ExtendingCuboidRegionSelector(world, world.getMinimumPoint(), world.getMaximumPoint()); + } else { + selector = new CuboidRegionSelector(world, world.getMinimumPoint(), world.getMaximumPoint()); + } actor.printInfo(TextComponent.of("Selected the entire world.")); } diff --git a/worldedit-core/doctools/build.gradle.kts b/worldedit-core/doctools/build.gradle.kts index 8f4555040..6f2123f20 100644 --- a/worldedit-core/doctools/build.gradle.kts +++ b/worldedit-core/doctools/build.gradle.kts @@ -1,14 +1,14 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.5.30" + kotlin("jvm") version "1.8.20" application } applyCommonConfiguration() tasks.withType { - kotlinOptions.jvmTarget = "11" + kotlinOptions.jvmTarget = "17" } application.mainClass.set("com.sk89q.worldedit.internal.util.DocumentationPrinter") diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index edde8ed49..8f4818f2f 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -12,7 +12,9 @@ import com.fastasyncworldedit.core.util.RandomTextureUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TextureUtil; import com.fastasyncworldedit.core.util.WEManager; +import com.fastasyncworldedit.core.util.task.KeyQueuedExecutorService; import com.github.luben.zstd.Zstd; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.internal.util.LogManagerCompat; import net.jpountz.lz4.LZ4Factory; @@ -33,6 +35,9 @@ import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.util.Date; import java.util.List; +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** @@ -86,6 +91,7 @@ public class Fawe { * The platform specific implementation. */ private final IFawe implementation; + private final KeyQueuedExecutorService clipboardExecutor; private FaweVersion version; private TextureUtil textures; private QueueHandler queueHandler; @@ -131,6 +137,15 @@ public class Fawe { }, 0); TaskManager.taskManager().repeat(timer, 1); + + clipboardExecutor = new KeyQueuedExecutorService<>(new ThreadPoolExecutor( + 1, + Settings.settings().QUEUE.PARALLEL_THREADS, + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder().setNameFormat("fawe-clipboard-%d").build() + )); } /** @@ -339,9 +354,10 @@ public class Fawe { Settings.settings().QUEUE.TARGET_SIZE, Settings.settings().QUEUE.PARALLEL_THREADS ); - if (Settings.settings().QUEUE.TARGET_SIZE < 2 * Settings.settings().QUEUE.PARALLEL_THREADS) { + if (Settings.settings().QUEUE.TARGET_SIZE < 4 * Settings.settings().QUEUE.PARALLEL_THREADS) { LOGGER.error( - "queue.target_size is {}, and queue.parallel_threads is {}. It is HIGHLY recommended that queue" + ".target_size be at least twice queue.parallel_threads or higher.", + "queue.target_size is {}, and queue.parallel_threads is {}. It is HIGHLY recommended that queue" + + ".target_size be at least four times queue.parallel_threads or greater.", Settings.settings().QUEUE.TARGET_SIZE, Settings.settings().QUEUE.PARALLEL_THREADS ); @@ -427,4 +443,15 @@ public class Fawe { return this.thread = Thread.currentThread(); } + /** + * Gets the executor used for clipboard IO if clipboard on disk is enabled or null + * + * @return Executor used for clipboard IO if clipboard on disk is enabled or null + * @since 2.6.2 + */ + @Nullable + public KeyQueuedExecutorService getClipboardExecutor() { + return this.clipboardExecutor; + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/SuggestInputParseException.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/SuggestInputParseException.java index c38b38888..fc50bf1a3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/SuggestInputParseException.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/SuggestInputParseException.java @@ -1,6 +1,8 @@ package com.fastasyncworldedit.core.command; +import com.fastasyncworldedit.core.configuration.Caption; import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.util.formatting.text.Component; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -13,33 +15,81 @@ public class SuggestInputParseException extends InputParseException { private final InputParseException cause; private final Supplier> getSuggestions; - private String prefix; + /** + * @deprecated Use {@link SuggestInputParseException#SuggestInputParseException(Component, Supplier)} + */ + @Deprecated(forRemoval = true, since = "2.6.2") public SuggestInputParseException(String msg, String prefix, Supplier> getSuggestions) { - this(new InputParseException(msg), prefix, getSuggestions); + this(new InputParseException(msg), getSuggestions); } + /** + * @deprecated Use {@link SuggestInputParseException#of(Throwable, Supplier)} + */ + @Deprecated(forRemoval = true, since = "2.6.2") public static SuggestInputParseException of(Throwable other, String prefix, Supplier> getSuggestions) { if (other instanceof InputParseException) { - return of((InputParseException) other, prefix, getSuggestions); + return of((InputParseException) other, getSuggestions); } - return of(new InputParseException(other.getMessage()), prefix, getSuggestions); + return of(new InputParseException(other.getMessage()), getSuggestions); } + /** + * @deprecated Use {@link SuggestInputParseException#of(InputParseException, Supplier)} + */ + @Deprecated(forRemoval = true, since = "2.6.2") public static SuggestInputParseException of(InputParseException other, String prefix, Supplier> getSuggestions) { if (other instanceof SuggestInputParseException) { return (SuggestInputParseException) other; } - return new SuggestInputParseException(other, prefix, getSuggestions); + return new SuggestInputParseException(other, getSuggestions); } + /** + * @deprecated Use {@link SuggestInputParseException#SuggestInputParseException(InputParseException, Supplier)} + */ + @Deprecated(forRemoval = true, since = "2.6.2") public SuggestInputParseException(InputParseException other, String prefix, Supplier> getSuggestions) { - super(other.getMessage()); + super(other.getRichMessage()); + checkNotNull(getSuggestions); + checkNotNull(other); + this.cause = other; + this.getSuggestions = getSuggestions; + } + + /** + * Create a new SuggestInputParseException instance + * + * @param message Message to send + * @param getSuggestions Supplier of list of suggestions to give to user + * @since 2.6.2 + */ + public SuggestInputParseException(Component message, Supplier> getSuggestions) { + this(new InputParseException(message), getSuggestions); + } + + public static SuggestInputParseException of(Throwable other, Supplier> getSuggestions) { + if (other instanceof InputParseException) { + return of((InputParseException) other, getSuggestions); + } + //noinspection deprecation + return of(new InputParseException(other.getMessage()), getSuggestions); + } + + public static SuggestInputParseException of(InputParseException other, Supplier> getSuggestions) { + if (other instanceof SuggestInputParseException) { + return (SuggestInputParseException) other; + } + return new SuggestInputParseException(other, getSuggestions); + } + + public SuggestInputParseException(InputParseException other, Supplier> getSuggestions) { + super(other.getRichMessage()); checkNotNull(getSuggestions); checkNotNull(other); this.cause = other; this.getSuggestions = getSuggestions; - this.prefix = prefix; } public static SuggestInputParseException get(InvocationTargetException e) { @@ -54,7 +104,7 @@ public class SuggestInputParseException extends InputParseException { } public static SuggestInputParseException of(String input, List values) { - throw new SuggestInputParseException("No value: " + input, input, () -> + throw new SuggestInputParseException(Caption.of("fawe.error.no-value-for-input", input), () -> values.stream() .map(Object::toString) .filter(v -> v.startsWith(input)) @@ -76,8 +126,12 @@ public class SuggestInputParseException extends InputParseException { return getSuggestions.get(); } + /** + * @deprecated Unused + */ + @Deprecated(forRemoval = true, since = "2.6.2") public SuggestInputParseException prepend(String input) { - this.prefix = input + prefix; + // Do nothing return this; } 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 c2cb7704d..5eba1f01f 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 @@ -102,11 +102,10 @@ public class Settings extends Config { public FaweLimit getLimit(Actor actor) { FaweLimit limit; - if (actor.hasPermission("fawe.limit.*") || actor.hasPermission("fawe.bypass")) { - limit = FaweLimit.MAX.copy(); - } else { - limit = new FaweLimit(); + if (actor.hasPermission("fawe.bypass") || actor.hasPermission("fawe.limit.unlimited")) { + return FaweLimit.MAX.copy(); } + limit = new FaweLimit(); ArrayList keys = new ArrayList<>(LIMITS.getSections()); if (keys.remove("default")) { keys.add("default"); @@ -394,6 +393,7 @@ public class Settings extends Config { "Where block properties are specified, any blockstate with the property will be disallowed (e.g. all directions", "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.", "Example block property blocking:", " - \"minecraft:conduit[waterlogged=true]\"", " - \"minecraft:piston[extended=false,facing=west]\"", @@ -520,10 +520,10 @@ public class Settings extends Config { " - A smaller value will reduce memory usage", " - A value too small may break some operations (deform?)", " - Values smaller than the configurated parallel-threads are not accepted", - " - It is recommended this option be at least 2x greater than parallel-threads" + " - It is recommended this option be at least 4x greater than parallel-threads" }) - public int TARGET_SIZE = 64; + public int TARGET_SIZE = 8 * Runtime.getRuntime().availableProcessors(); @Comment({ "Force FAWE to start placing chunks regardless of whether an edit is finished processing", " - A larger value will use slightly less CPU time", @@ -671,6 +671,14 @@ public class Settings extends Config { }) public int MAX_IMAGE_SIZE = 8294400; + @Comment({ + "Whitelist of hostnames to allow images to be downloaded from", + " - Adding '*' to the list will allow any host, but this is NOT adviseable", + " - Crash exploits exist with malformed images", + " - See: https://medium.com/chargebee-engineering/perils-of-parsing-pixel-flood-attack-on-java-imageio-a97aeb06637d" + }) + public List ALLOWED_IMAGE_HOSTS = new ArrayList<>(Collections.singleton(("i.imgur.com"))); + } public static class EXTENT { @@ -694,7 +702,7 @@ public class Settings extends Config { public static class TICK_LIMITER { @Comment("Enable the limiter") - public boolean ENABLED = true; + public boolean ENABLED = false; @Comment("The interval in ticks") public int INTERVAL = 20; @Comment("Max falling blocks per interval (per chunk)") @@ -703,12 +711,6 @@ public class Settings extends Config { public int PHYSICS_MS = 10; @Comment("Max item spawns per interval (per chunk)") public int ITEMS = 256; - @Comment({ - "Whether fireworks can load chunks", - " - Fireworks usually travel vertically so do not load any chunks", - " - Horizontal fireworks can be hacked in to crash a server" - }) - public boolean FIREWORKS_LOAD_CHUNKS = false; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichMaskParser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichMaskParser.java index 31c8f3d3e..4e0fbfa91 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichMaskParser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/RichMaskParser.java @@ -50,7 +50,7 @@ public class RichMaskParser extends FaweParser { @Override public Mask parseFromInput(String input, ParserContext context) throws InputParseException { if (input.isEmpty()) { - throw new SuggestInputParseException("No input provided", "", () -> Stream + throw new SuggestInputParseException(Caption.of("fawe.error.no-input-provided"), () -> Stream .of("#", ",", "&") .map(n -> n + ":") .collect(Collectors.toList()) @@ -95,7 +95,6 @@ public class RichMaskParser extends FaweParser { "https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns" )) )), - full, () -> { if (full.length() == 1) { return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions("")); @@ -148,6 +147,7 @@ public class RichMaskParser extends FaweParser { try { builder.addRegex(full); } catch (InputParseException ignored) { + builder.clear(); context.setPreferringWildcard(false); context.setRestricted(false); BaseBlock block = worldEdit.getBlockFactory().parseFromInput(full, context); @@ -162,7 +162,6 @@ public class RichMaskParser extends FaweParser { "https://intellectualsites.github.io/fastasyncworldedit-documentation/masks/masks" )) )), - full, () -> { if (full.length() == 1) { return new ArrayList<>(worldEdit.getMaskFactory().getSuggestions("")); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java index 4971ee878..3ebd84fa6 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/pattern/RichPatternParser.java @@ -47,8 +47,7 @@ public class RichPatternParser extends FaweParser { public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { if (input.isEmpty()) { throw new SuggestInputParseException( - "No input provided", - "", + Caption.of("fawe.error.no-input-provided"), () -> Stream .concat(Stream.of("#", ",", "&"), BlockTypes.getNameSpaces().stream().map(n -> n + ":")) .collect(Collectors.toList()) @@ -88,7 +87,6 @@ public class RichPatternParser extends FaweParser { "https://intellectualsites.github.io/fastasyncworldedit-documentation/patterns/patterns" )) )), - full, () -> { if (full.length() == 1) { return new ArrayList<>(worldEdit.getPatternFactory().getSuggestions("")); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java index db270fa16..f1c523ce3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java @@ -141,7 +141,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB BlockState state = BlockTypesCache.states[block]; if (blockedBlocks != null) { if (blockedBlocks.contains(state.getBlockType().getId())) { - blocks[i] = 0; + blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__; continue; } } @@ -150,7 +150,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB } for (FuzzyBlockState fuzzy : blockedStates) { if (fuzzy.equalsFuzzy(state)) { - blocks[i] = 0; + blocks[i] = BlockTypesCache.ReservedIDs.__RESERVED__; continue it; } } @@ -178,7 +178,7 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB @Override public ProcessorScope getScope() { - return ProcessorScope.CHANGING_BLOCKS; + return ProcessorScope.REMOVING_BLOCKS; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/FaweRegionExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/FaweRegionExtent.java index 6fa2767e0..84044f9b2 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/FaweRegionExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/FaweRegionExtent.java @@ -168,7 +168,7 @@ public abstract class FaweRegionExtent extends ResettableExtent implements IBatc @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.REMOVING_BLOCKS; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java index e7f88c7c5..c13c5965b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java @@ -172,7 +172,7 @@ public class MultiRegionExtent extends FaweRegionExtent { set = intersection.processSet(chunk, get, set); } if (disallowedIntersection != null) { - intersection.processSet(chunk, get, set); + set = disallowedIntersection.processSet(chunk, get, set, true); } return set; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/CPUOptimizedClipboard.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/CPUOptimizedClipboard.java index 6f566fcb9..1a30a0df8 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/CPUOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/CPUOptimizedClipboard.java @@ -11,6 +11,7 @@ import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockTypesCache; import java.io.IOException; import java.util.Collection; @@ -191,8 +192,8 @@ public class CPUOptimizedClipboard extends LinearClipboard { @Override public > boolean setBlock(int index, B block) { char ordinal = block.getOrdinalChar(); - if (ordinal == 0) { - ordinal = 1; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } states[index] = ordinal; boolean hasNbt = block instanceof BaseBlock && block.hasNbtData(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java index 100e3265f..a9704e36b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/DiskOptimizedClipboard.java @@ -26,6 +26,7 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; import org.apache.logging.log4j.Logger; import java.io.ByteArrayOutputStream; @@ -155,7 +156,9 @@ public class DiskOptimizedClipboard extends LinearClipboard { /** * Load an existing file as a DiskOptimizedClipboard. The file MUST exist and MUST be created as a DiskOptimizedClipboard * with data written to it. + * @deprecated Will be made private, use {@link DiskOptimizedClipboard#loadFromFile(File)} */ + @Deprecated(forRemoval = true, since = "2.6.2") public DiskOptimizedClipboard(File file) { this(file, VERSION); } @@ -166,14 +169,15 @@ public class DiskOptimizedClipboard extends LinearClipboard { * * @param file File to read from * @param versionOverride An override version to allow loading of older clipboards if required + * @deprecated Will be made private, use {@link DiskOptimizedClipboard#loadFromFile(File)} */ + @Deprecated(forRemoval = true, since = "2.6.2") public DiskOptimizedClipboard(File file, int versionOverride) { super(readSize(file, versionOverride), BlockVector3.ZERO); headerSize = getHeaderSizeOverrideFromVersion(versionOverride); nbtMap = new HashMap<>(); try { this.file = file; - checkFileLength(file); this.braf = new RandomAccessFile(file, "rw"); braf.setLength(file.length()); this.nbtBytesRemaining = Integer.MAX_VALUE - (int) file.length(); @@ -202,32 +206,6 @@ public class DiskOptimizedClipboard extends LinearClipboard { } } - private void checkFileLength(File file) throws IOException { - long expectedFileSize = headerSize + ((long) getVolume() << 1); - if (file.length() > Integer.MAX_VALUE) { - if (expectedFileSize >= Integer.MAX_VALUE) { - throw new IOException(String.format( - "Cannot load clipboard of file size: %d > 2147483647 bytes (2.147 GiB), " + "volume: %d blocks", - file.length(), - getVolume() - )); - } else { - throw new IOException(String.format( - "Cannot load clipboard of file size > 2147483647 bytes (2.147 GiB). Possible corrupt file? Mismatch" + - " between volume `%d` and file length `%d`!", - file.length(), - getVolume() - )); - } - } else if (expectedFileSize != file.length()) { - throw new IOException(String.format( - "Possible corrupt clipboard file? Mismatch between expected file size `%d` and actual file size `%d`!", - expectedFileSize, - file.length() - )); - } - } - /** * Attempt to load a file into a new {@link DiskOptimizedClipboard} instance. Will attempt to recover on version mismatch * failure. @@ -723,8 +701,8 @@ public class DiskOptimizedClipboard extends LinearClipboard { try { int index = headerSize + (getIndex(x, y, z) << 1); char ordinal = block.getOrdinalChar(); - if (ordinal == 0) { - ordinal = 1; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } byteBuffer.putChar(index, ordinal); boolean hasNbt = block instanceof BaseBlock && block.hasNbtData(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/MemoryOptimizedClipboard.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/MemoryOptimizedClipboard.java index ed76a9d1f..449795e5d 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/MemoryOptimizedClipboard.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/MemoryOptimizedClipboard.java @@ -16,6 +16,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; import java.io.IOException; import java.util.Collection; @@ -269,8 +270,8 @@ public class MemoryOptimizedClipboard extends LinearClipboard { @Override public > boolean setBlock(int index, B block) { int ordinal = block.getOrdinal(); - if (ordinal == 0) { - ordinal = 1; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } setOrdinal(index, ordinal); boolean hasNbt = block instanceof BaseBlock && block.hasNbtData(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/io/FastSchematicWriter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/io/FastSchematicWriter.java index 696dd3ef5..db3452d29 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/io/FastSchematicWriter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/clipboard/io/FastSchematicWriter.java @@ -172,8 +172,8 @@ public class FastSchematicWriter implements ClipboardWriter { } int ordinal = block.getOrdinal(); - if (ordinal == 0) { - ordinal = 1; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } char value = palette[ordinal]; if (value == Character.MAX_VALUE) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/DistrFilter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/DistrFilter.java index 78a7ea293..89b294279 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/DistrFilter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/DistrFilter.java @@ -41,8 +41,8 @@ public class DistrFilter extends ForkedFilter { @Override public final void applyBlock(FilterBlock block) { int ordinal = block.getOrdinal(); - if (ordinal == 0) { - ordinal = 1; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } counter[ordinal]++; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java index 85fe20986..e57ccb490 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java @@ -257,7 +257,7 @@ public class MultiBatchProcessor implements IBatchProcessor { for (IBatchProcessor processor : processors) { scope = Math.max(scope, processor.getScope().intValue()); } - return ProcessorScope.valueOf(0); + return ProcessorScope.valueOf(scope); } /** diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java index 7fdd3e67a..e3f09e0a3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java @@ -3,9 +3,9 @@ package com.fastasyncworldedit.core.extent.processor; /** * The scope of a processor. * Order in which processors are executed: - * - ADDING_BLOCKS (processors that strictly ADD blocks to an edit ONLY) - * - CHANGING_BLOCKS (processors that strictly ADD or CHANGE blocks being set) - * - REMOVING_BLOCKS (processors that string ADD, CHANGE or REMOVE blocks being set) + * - ADDING_BLOCKS (processors that may ADD blocks to an edit ONLY) + * - CHANGING_BLOCKS (processors that may ADD or CHANGE blocks being set) + * - REMOVING_BLOCKS (processors that may ADD, CHANGE or REMOVE blocks being set) * - CUSTOM (processors that do not specify a SCOPE) * - READING_SET_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g. history processors) */ diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java index efa2447f7..3cdfeafb9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java @@ -73,11 +73,11 @@ public class HeightmapProcessor implements IBatchProcessor { for (int y = 15; y >= 0; y--) { // We don't need to actually iterate over x and z as we're both reading and writing an index for (int j = 0; j < BLOCKS_PER_Y; j++) { - char ordinal = 0; + char ordinal = BlockTypesCache.ReservedIDs.__RESERVED__; if (hasSectionSet) { ordinal = setSection[index(y, j)]; } - if (ordinal == 0) { + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { if (!hasSectionGet) { if (!hasSectionSet) { continue layer; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/BlockMaskBuilder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/BlockMaskBuilder.java index 0b89e0ada..5bfeb766c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/BlockMaskBuilder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/BlockMaskBuilder.java @@ -178,8 +178,7 @@ public class BlockMaskBuilder { } if (operator == null) { throw new SuggestInputParseException( - "No operator for " + input, - "", + Caption.of("fawe.error.no-operator-for-input", input), () -> Arrays.asList("=", "~", "!", "<", ">", "<=", ">=") ); } @@ -195,7 +194,7 @@ public class BlockMaskBuilder { String value = charSequence.toString(); final PropertyKey fKey = key; Collection types = type != null ? Collections.singleton(type) : blockTypeList; - throw new SuggestInputParseException("No value for " + input, input, () -> { + throw new SuggestInputParseException(Caption.of("fawe.error.no-value-for-input", input), () -> { HashSet values = new HashSet<>(); types.stream().filter(t -> t.hasProperty(fKey)).forEach(t -> { Property p = t.getProperty(fKey); @@ -287,7 +286,7 @@ public class BlockMaskBuilder { } private void suggest(String input, String property, Collection finalTypes) throws InputParseException { - throw new SuggestInputParseException(input + " does not have: " + property, input, () -> { + throw new SuggestInputParseException(Caption.of("worldedit.error.parser.unknown-property", property, input), () -> { Set keys = PropertyKeySet.empty(); finalTypes.forEach(t -> t.getProperties().forEach(p -> keys.add(p.getKey()))); return keys.stream().map(PropertyKey::getName) diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableBiomeChange.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableBiomeChange.java index 46860c106..744a372e9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableBiomeChange.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableBiomeChange.java @@ -5,6 +5,7 @@ import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.history.UndoContext; import com.sk89q.worldedit.history.change.Change; import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; public class MutableBiomeChange implements Change { @@ -13,8 +14,8 @@ public class MutableBiomeChange implements Change { private int to; public MutableBiomeChange() { - this.from = 0; - this.to = 0; + this.from = BlockTypesCache.ReservedIDs.__RESERVED__; + this.to = BlockTypesCache.ReservedIDs.__RESERVED__; } public void setBiome(int x, int y, int z, int from, int to) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableFullBlockChange.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableFullBlockChange.java index 81337afb3..2b99cb8a9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableFullBlockChange.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/change/MutableFullBlockChange.java @@ -6,6 +6,7 @@ import com.sk89q.worldedit.extent.inventory.BlockBagException; import com.sk89q.worldedit.history.UndoContext; import com.sk89q.worldedit.history.change.Change; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; public class MutableFullBlockChange implements Change { @@ -39,14 +40,14 @@ public class MutableFullBlockChange implements Change { if (blockBag != null) { BlockState toState = BlockState.getFromOrdinal(to); if (fromState != toState) { - if (allowFetch && from != 0) { + if (allowFetch && from != BlockTypesCache.ReservedIDs.__RESERVED__) { try { blockBag.fetchPlacedBlock(fromState); } catch (BlockBagException e) { return; } } - if (allowStore && to != 0) { + if (allowStore && to != BlockTypesCache.ReservedIDs.__RESERVED__) { try { blockBag.storeDroppedBlock(toState); } catch (BlockBagException ignored) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java index e52ef0cba..b3493c896 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java @@ -39,19 +39,27 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; +/** + * This batch processor writes changes to a concrete implementation. + * {@link #processSet(IChunk, IChunkGet, IChunkSet)} is synchronized to guarantee consistency. + * To avoid many blocking threads on this method, changes are enqueued in {@link #queue}. + * This allows to keep other threads free for other work. + */ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { private static final Logger LOGGER = LogManagerCompat.getLogger(); private final World world; private final AtomicInteger lastException = new AtomicInteger(); - protected AtomicInteger waitingCombined = new AtomicInteger(0); - protected AtomicInteger waitingAsync = new AtomicInteger(0); - - protected boolean closed; + private final Semaphore workerSemaphore = new Semaphore(1, false); + private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); + protected volatile boolean closed; public AbstractChangeSet(World world) { this.world = world; @@ -65,16 +73,11 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { if (closed) { return; } - waitingAsync.incrementAndGet(); TaskManager.taskManager().async(() -> { - waitingAsync.decrementAndGet(); - synchronized (waitingAsync) { - waitingAsync.notifyAll(); - } try { close(); } catch (IOException e) { - e.printStackTrace(); + LOGGER.catching(e); } }); } @@ -82,20 +85,10 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { @Override public void flush() { try { - if (!Fawe.isMainThread()) { - while (waitingAsync.get() > 0) { - synchronized (waitingAsync) { - waitingAsync.wait(1000); - } - } - } - while (waitingCombined.get() > 0) { - synchronized (waitingCombined) { - waitingCombined.wait(1000); - } - } - } catch (InterruptedException e) { - e.printStackTrace(); + // drain with this thread too + drainQueue(true); + } catch (Exception e) { + LOGGER.catching(e); } } @@ -125,7 +118,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { } @Override - public synchronized IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) { + public final synchronized IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set) { int bx = chunk.getX() << 4; int bz = chunk.getZ() << 4; @@ -193,7 +186,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { } final int combinedFrom = from; final int combinedTo = blocksSet[index]; - if (combinedTo != 0) { + if (combinedTo != BlockTypesCache.ReservedIDs.__RESERVED__) { add(xx, yy, zz, combinedFrom, combinedTo); } } @@ -306,12 +299,12 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { BaseBlock to = change.getCurrent(); add(loc, from, to); } catch (Exception e) { - e.printStackTrace(); + LOGGER.catching(e); } } public boolean isEmpty() { - return waitingCombined.get() == 0 && waitingAsync.get() == 0 && size() == 0; + return queue.isEmpty() && workerSemaphore.availablePermits() == 1 && size() == 0; } public void add(BlockVector3 loc, BaseBlock from, BaseBlock to) { @@ -353,7 +346,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { add(x, y, z, combinedFrom, combinedTo); } catch (Exception e) { - e.printStackTrace(); + LOGGER.catching(e); } } @@ -362,7 +355,6 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { } public Future addWriteTask(final Runnable writeTask, final boolean completeNow) { - AbstractChangeSet.this.waitingCombined.incrementAndGet(); Runnable wrappedTask = () -> { try { writeTask.run(); @@ -372,25 +364,55 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { } else { int hash = t.getMessage().hashCode(); if (lastException.getAndSet(hash) != hash) { - t.printStackTrace(); - } - } - } finally { - if (AbstractChangeSet.this.waitingCombined.decrementAndGet() <= 0) { - synchronized (AbstractChangeSet.this.waitingAsync) { - AbstractChangeSet.this.waitingAsync.notifyAll(); - } - synchronized (AbstractChangeSet.this.waitingCombined) { - AbstractChangeSet.this.waitingCombined.notifyAll(); + LOGGER.catching(t); } } } }; if (completeNow) { wrappedTask.run(); - return Futures.immediateCancelledFuture(); + return Futures.immediateVoidFuture(); } else { - return Fawe.instance().getQueueHandler().submit(wrappedTask); + CompletableFuture task = new CompletableFuture<>(); + queue.add(() -> { + wrappedTask.run(); + task.complete(null); + }); + // make sure changes are processed + triggerWorker(); + return task; + } + } + + private void triggerWorker() { + if (workerSemaphore.availablePermits() == 0) { + return; // fast path to avoid additional tasks: a worker is already draining the queue + } + // create a new worker to drain the current queue + Fawe.instance().getQueueHandler().async(() -> drainQueue(false)); + } + + private void drainQueue(boolean ignoreRunningState) { + if (!workerSemaphore.tryAcquire()) { + if (ignoreRunningState) { + // ignoreRunningState means we want to block + // even if another thread is already draining + try { + workerSemaphore.acquire(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } else { + return; // another thread is draining the queue already, ignore + } + } + try { + Runnable next; + while ((next = queue.poll()) != null) { // process all tasks in the queue + next.run(); + } + } finally { + workerSemaphore.release(); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractDelegateChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractDelegateChangeSet.java index ad3173fd6..195a5fdbd 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractDelegateChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractDelegateChangeSet.java @@ -25,8 +25,6 @@ public class AbstractDelegateChangeSet extends AbstractChangeSet { public AbstractDelegateChangeSet(AbstractChangeSet parent) { super(parent.getWorld()); this.parent = parent; - this.waitingCombined = parent.waitingCombined; - this.waitingAsync = parent.waitingAsync; } public final AbstractChangeSet getParent() { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java index c75a163f7..c2ae362ae 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java @@ -258,7 +258,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet { if (blockSize > 0) { return false; } - if (waitingCombined.get() != 0 || waitingAsync.get() != 0) { + if (!super.isEmpty()) { return false; } flush(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java index c899c668d..00a47178c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/limit/FaweLimit.java @@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.limit; import com.fastasyncworldedit.core.FaweCache; +import java.util.Collections; import java.util.Set; public class FaweLimit { @@ -114,10 +115,10 @@ public class FaweLimit { MAX.FAST_PLACEMENT = true; MAX.CONFIRM_LARGE = true; MAX.RESTRICT_HISTORY_TO_REGIONS = false; - MAX.STRIP_NBT = null; + MAX.STRIP_NBT = Collections.emptySet(); MAX.UNIVERSAL_DISALLOWED_BLOCKS = false; - MAX.DISALLOWED_BLOCKS = null; - MAX.REMAP_PROPERTIES = null; + MAX.DISALLOWED_BLOCKS = Collections.emptySet(); + MAX.REMAP_PROPERTIES = Collections.emptySet(); } public boolean MAX_CHANGES() { @@ -241,7 +242,7 @@ public class FaweLimit { && FAST_PLACEMENT && !RESTRICT_HISTORY_TO_REGIONS && (STRIP_NBT == null || STRIP_NBT.isEmpty()) - && !UNIVERSAL_DISALLOWED_BLOCKS + // && !UNIVERSAL_DISALLOWED_BLOCKS --> do not include this, it effectively has no relevance && (DISALLOWED_BLOCKS == null || DISALLOWED_BLOCKS.isEmpty()) && (REMAP_PROPERTIES == null || REMAP_PROPERTIES.isEmpty()); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/BlockVector3ChunkMap.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/BlockVector3ChunkMap.java index 775f96e4d..a9f8a0210 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/BlockVector3ChunkMap.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/BlockVector3ChunkMap.java @@ -9,7 +9,20 @@ import java.util.Map; public class BlockVector3ChunkMap implements IAdaptedMap { - private final Int2ObjectArrayMap map = new Int2ObjectArrayMap<>(); + private final Int2ObjectArrayMap map; + + public BlockVector3ChunkMap() { + map = new Int2ObjectArrayMap<>(); + } + + /** + * Create a new instance that is a copy of an existing map + * + * @param map existing map to copy + */ + public BlockVector3ChunkMap(BlockVector3ChunkMap map) { + this.map = new Int2ObjectArrayMap<>(map.getParent()); + } @Override public Map getParent() { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java index 4ee815292..807a90fb4 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java @@ -7,6 +7,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockTypesCache; import javax.annotation.Nullable; import java.util.Map; @@ -71,7 +72,7 @@ public interface IBatchProcessor { if (arr != null) { int index = (minY & 15) << 8; for (int i = 0; i < index; i++) { - arr[i] = 0; + arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__; } } else { arr = new char[4096]; @@ -89,7 +90,7 @@ public interface IBatchProcessor { if (arr != null) { int index = ((maxY + 1) & 15) << 8; for (int i = index; i < arr.length; i++) { - arr[i] = 0; + arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__; } } else { arr = new char[4096]; @@ -130,7 +131,7 @@ public interface IBatchProcessor { if (arr != null) { int index = (minY & 15) << 8; for (int i = index; i < 4096; i++) { - arr[i] = 0; + arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__; } } set.setBlocks(layer, arr); @@ -139,7 +140,7 @@ public interface IBatchProcessor { if (arr != null) { int index = ((maxY + 1) & 15) << 8; for (int i = 0; i < index; i++) { - arr[i] = 0; + arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__; } } set.setBlocks(layer, arr); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java index 79aed9f23..1af60b3a2 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java @@ -8,8 +8,9 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockStateHolder; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -95,7 +96,7 @@ public interface IChunkSet extends IBlocks, OutputExtent { } default Map getHeightMaps() { - return new HashMap<>(); + return new EnumMap<>(HeightMapType.class); } @Override @@ -115,4 +116,15 @@ public interface IChunkSet extends IBlocks, OutputExtent { */ boolean hasBiomes(int layer); + /** + * Create an entirely distinct copy of this SET instance. All mutable data must be copied to sufficiently prevent leakage + * between the copy and the original. + * + * @return distinct new {@link IChunkSet instance} + */ + @Nonnull + default IChunkSet createCopy() { + return this; + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java index 49ebd84ca..7f604a277 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java @@ -14,6 +14,7 @@ import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; import com.fastasyncworldedit.core.util.MemUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal; +import com.fastasyncworldedit.core.util.task.FaweForkJoinWorkerThreadFactory; import com.fastasyncworldedit.core.wrappers.WorldWrapper; import com.google.common.util.concurrent.Futures; import com.sk89q.worldedit.world.World; @@ -39,10 +40,41 @@ import java.util.function.Supplier; @SuppressWarnings({"unchecked", "rawtypes"}) public abstract class QueueHandler implements Trimable, Runnable { - private final ForkJoinPool forkJoinPoolPrimary = new ForkJoinPool(); - private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool(); + private static final int PROCESSORS = Runtime.getRuntime().availableProcessors(); + + /** + * Primary queue should be used for tasks that are unlikely to wait on other tasks, IO, etc. (i.e. spend most of their + * time utilising CPU. + */ + private final ForkJoinPool forkJoinPoolPrimary = new ForkJoinPool( + PROCESSORS, + new FaweForkJoinWorkerThreadFactory("FAWE Fork Join Pool Primary - %s"), + null, + false + ); + /** + * Secondary queue should be used for "cleanup" tasks that are likely to be shorter in life than those submitted to the + * primary queue. They may be IO-bound tasks. + */ + private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool( + PROCESSORS, + new FaweForkJoinWorkerThreadFactory("FAWE Fork Join Pool Secondary - %s"), + null, + false + ); + /** + * Main "work-horse" queue for FAWE. Handles chunk submission (and chunk submission alone). Blocking in order to forcibly + * prevent overworking/over-submission of chunk process tasks. + */ private final ThreadPoolExecutor blockingExecutor = FaweCache.INSTANCE.newBlockingExecutor(); + /** + * Queue for tasks to be completed on the main thread. These take priority of tasks submitted to syncWhenFree queue + */ private final ConcurrentLinkedQueue syncTasks = new ConcurrentLinkedQueue<>(); + /** + * Queue for tasks to be completed on the main thread. These are completed only if and when there is time left in a tick + * after completing all tasks in the syncTasks queue + */ private final ConcurrentLinkedQueue syncWhenFree = new ConcurrentLinkedQueue<>(); private final Map>> chunkGetCache = new HashMap<>(); @@ -53,9 +85,8 @@ public abstract class QueueHandler implements Trimable, Runnable { */ private long last; private long allocate = 50; - private double targetTPS = 18; - public QueueHandler() { + protected QueueHandler() { TaskManager.taskManager().repeat(this, 1); } @@ -81,13 +112,19 @@ public abstract class QueueHandler implements Trimable, Runnable { } } + /** + * Get if the {@code blockingExecutor} is saturated with tasks or not. Under-utilisation implies the queue has space for + * more submissions. + * + * @return true if {@code blockingExecutor} is not saturated with tasks + */ public boolean isUnderutilized() { return blockingExecutor.getActiveCount() < blockingExecutor.getMaximumPoolSize(); } private long getAllocate() { long now = System.currentTimeMillis(); - targetTPS = 18 - Math.max(Settings.settings().QUEUE.EXTRA_TIME_MS * 0.05, 0); + double targetTPS = 18 - Math.max(Settings.settings().QUEUE.EXTRA_TIME_MS * 0.05, 0); long diff = 50 + this.last - (this.last = now); long absDiff = Math.abs(diff); if (diff == 0) { @@ -126,6 +163,10 @@ public abstract class QueueHandler implements Trimable, Runnable { } while (System.currentTimeMillis() - start < currentAllocate); } + /** + * @deprecated For removal without replacement. + */ + @Deprecated(forRemoval = true, since = "2.6.2") public > void complete(Future task) { try { while (task != null) { @@ -136,49 +177,140 @@ public abstract class QueueHandler implements Trimable, Runnable { } } + /** + * Complete a task in the {@code forkJoinPoolSecondary} queue. Secondary queue should be used for "cleanup" tasks that are + * likely to be shorter in life than those submitted to the primary queue. They may be IO-bound tasks. + * + * @param run Runnable to run + * @param value Value to return when done + * @param Value type + * @return Future for submitted task + */ public Future async(Runnable run, T value) { return forkJoinPoolSecondary.submit(run, value); } + /** + * Complete a task in the {@code forkJoinPoolSecondary} queue. Secondary queue should be used for "cleanup" tasks that are + * likely to be shorter in life than those submitted to the primary queue. They may be IO-bound tasks. + * + * @param run Runnable to run + * @return Future for submitted task + */ public Future async(Runnable run) { return forkJoinPoolSecondary.submit(run); } + /** + * Complete a task in the {@code forkJoinPoolSecondary} queue. Secondary queue should be used for "cleanup" tasks that are + * likely to be shorter in life than those submitted to the primary queue. They may be IO-bound tasks. + * + * @param call Callable to run + * @param Return value type + * @return Future for submitted task + */ public Future async(Callable call) { return forkJoinPoolSecondary.submit(call); } - public ForkJoinTask submit(Runnable call) { - return forkJoinPoolPrimary.submit(call); + /** + * Complete a task in the {@code forkJoinPoolPrimary} queue. Primary queue should be used for tasks that are unlikely to + * wait on other tasks, IO, etc. (i.e. spend most of their time utilising CPU. + * + * @param run Task to run + * @return {@link ForkJoinTask} representing task being run + */ + public ForkJoinTask submit(Runnable run) { + return forkJoinPoolPrimary.submit(run); } + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. + * + * @param run Task to run + * @param Value type + * @return Future representing task + */ public Future sync(Runnable run) { return sync(run, syncTasks); } + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. + * + * @param call Task to run + * @param Value type + * @return Future representing task + */ public Future sync(Callable call) throws Exception { return sync(call, syncTasks); } - public Future sync(Supplier call) { - return sync(call, syncTasks); + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. + * + * @param supplier Task to run + * @param Value type + * @return Future representing task + */ + public Future sync(Supplier supplier) { + return sync(supplier, syncTasks); } - // Lower priority sync task (runs only when there are no other tasks) + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed + * only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods. + * + * @param run Task to run + * @param value Value to return when done + * @param Value type + * @return Future representing task + */ public Future syncWhenFree(Runnable run, T value) { return sync(run, value, syncWhenFree); } + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed + * only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods. + * + * @param run Task to run + * @param Value type + * @return Future representing task + */ public Future syncWhenFree(Runnable run) { return sync(run, syncWhenFree); } + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed + * only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods. + * + * @param call Task to run + * @param Value type + * @return Future representing task + */ public Future syncWhenFree(Callable call) throws Exception { return sync(call, syncWhenFree); } - public Future syncWhenFree(Supplier call) { - return sync(call, syncWhenFree); + /** + * Submit a task to be run on the main thread. Does not guarantee to be run on the next tick as FAWE will only operate to + * maintain approx. 18 tps. Takes lower priority than tasks submitted via any {@code QueueHandler#sync} method. Completed + * only if and when there is time left in a tick after completing all sync tasks submitted using the aforementioned methods. + * + * @param supplier Task to run + * @param Value type + * @return Future representing task + */ + public Future syncWhenFree(Supplier supplier) { + return sync(supplier, syncWhenFree); } private Future sync(Runnable run, T value, Queue queue) { @@ -229,6 +361,15 @@ public abstract class QueueHandler implements Trimable, Runnable { } } + /** + * Internal use only. Specifically for submitting {@link IQueueChunk} for "processing" an edit. Submits to the blocking + * executor, the main "work-horse" queue for FAWE. Handles chunk submission (and chunk submission alone). Blocking in order + * to forcibly prevent overworking/over-submission of chunk process tasks. + * + * @param chunk chunk + * @param + * @return Future representing task + */ public > T submit(IQueueChunk chunk) { // if (MemUtil.isMemoryFree()) { TODO NOT IMPLEMENTED - optimize this // return (T) forkJoinPoolSecondary.submit(chunk); @@ -260,6 +401,9 @@ public abstract class QueueHandler implements Trimable, Runnable { return new SingleThreadQueueExtent(); } + /** + * Sets the current thread's {@link IQueueExtent} instance in the queue pool to null. + */ public void unCache() { queuePool.set(null); } @@ -272,14 +416,58 @@ public abstract class QueueHandler implements Trimable, Runnable { return queue; } - public abstract void startSet(boolean parallel); + /** + * Indicate a "set" task is being started. + * + * @param parallel if the "set" being started is parallel/async + * @deprecated To be replaced by better-named {@link QueueHandler#startUnsafe(boolean)} )} + */ + @Deprecated(forRemoval = true, since = "2.6.2") + public void startSet(boolean parallel) { + startUnsafe(parallel); + } - public abstract void endSet(boolean parallel); + /** + * Indicate a "set" task is ending. + * + * @param parallel if the "set" being started is parallel/async + * @deprecated To be replaced by better-named {@link QueueHandler#endUnsafe(boolean)} )} + */ + @Deprecated(forRemoval = true, since = "2.6.2") + public void endSet(boolean parallel) { + startUnsafe(parallel); + } + + /** + * Indicate an unsafe task is starting. Physics are frozen, async catchers disabled, etc. for the duration of the task + * + * @param parallel If the task is being run async and/or in parallel + */ + public abstract void startUnsafe(boolean parallel); + + /** + * Indicate a/the unsafe task submitted after a {@link QueueHandler#startUnsafe(boolean)} call has ended. + * + * @param parallel If the task was being run async and/or in parallel + */ + public abstract void endUnsafe(boolean parallel); + + /** + * Create a new queue for a given world. + */ public IQueueExtent getQueue(World world) { return getQueue(world, null, null); } + /** + * Create a new queue for a given world. + * + * @param world World to create queue for + * @param processor existing processor to set to queue or null + * @param postProcessor existing post-processor to set to queue or null + * @return New queue for given world + */ public IQueueExtent getQueue(World world, IBatchProcessor processor, IBatchProcessor postProcessor) { final IQueueExtent queue = pool(); IChunkCache cacheGet = getOrCreateWorldCache(world); @@ -294,6 +482,12 @@ public abstract class QueueHandler implements Trimable, Runnable { return queue; } + /** + * Trims each chunk GET cache + * + * @param aggressive if each chunk GET cache should be trimmed aggressively + * @return true if all chunk GET caches could be trimmed + */ @Override public boolean trim(boolean aggressive) { boolean result = true; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index 8ad257c76..a41d8786b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -275,8 +275,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen * Get a new IChunk from either the pool, or create a new one
+ Initialize it at the * coordinates * - * @param chunkX - * @param chunkZ + * @param chunkX X chunk coordinate + * @param chunkZ Z chunk coordinate * @return IChunk */ private ChunkHolder poolOrCreate(int chunkX, int chunkZ) { @@ -309,19 +309,11 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen // If queueing is enabled AND either of the following // - memory is low & queue size > num threads + 8 // - queue size > target size and primary queue has less than num threads submissions - if (enabledQueue && ((lowMem && size > Settings.settings().QUEUE.PARALLEL_THREADS + 8) || (size > Settings.settings().QUEUE.TARGET_SIZE && Fawe - .instance() - .getQueueHandler() - .isUnderutilized()))) { + int targetSize = lowMem ? Settings.settings().QUEUE.PARALLEL_THREADS + 8 : Settings.settings().QUEUE.TARGET_SIZE; + if (enabledQueue && size > targetSize && (lowMem || Fawe.instance().getQueueHandler().isUnderutilized())) { chunk = chunks.removeFirst(); final Future future = submitUnchecked(chunk); if (future != null && !future.isDone()) { - final int targetSize; - if (lowMem) { - targetSize = Settings.settings().QUEUE.PARALLEL_THREADS + 8; - } else { - targetSize = Settings.settings().QUEUE.TARGET_SIZE; - } pollSubmissions(targetSize, lowMem); submissions.add(future); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java index 291a3cf57..9ab8734ea 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java @@ -15,12 +15,11 @@ import com.sk89q.worldedit.world.block.BlockTypesCache; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; +import java.util.EnumMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.stream.IntStream; public class CharSetBlocks extends CharBlocks implements IChunkSet { @@ -40,7 +39,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { public BlockVector3ChunkMap tiles; public HashSet entities; public HashSet entityRemoves; - public Map heightMaps; + public EnumMap heightMaps; private boolean fastMode = false; private int bitMask = -1; @@ -93,7 +92,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { @Override public Map getHeightMaps() { - return heightMaps == null ? new HashMap<>() : heightMaps; + return heightMaps == null ? new EnumMap<>(HeightMapType.class) : heightMaps; } @Override @@ -177,7 +176,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { @Override public void setHeightMap(HeightMapType type, int[] heightMap) { if (heightMaps == null) { - heightMaps = new HashMap<>(); + heightMaps = new EnumMap<>(HeightMapType.class); } heightMaps.put(type, heightMap); } @@ -306,8 +305,12 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { || (heightMaps != null && !heightMaps.isEmpty())) { return false; } - //noinspection SimplifyStreamApiCallChains - this is faster than using #noneMatch - return !IntStream.range(minSectionPosition, maxSectionPosition + 1).anyMatch(this::hasSection); + for (int i = minSectionPosition; i <= maxSectionPosition; i++) { + if (hasSection(i)) { + return false; + } + } + return true; } @Override @@ -316,6 +319,9 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { tiles = null; entities = null; entityRemoves = null; + light = null; + skyLight = null; + heightMaps = null; super.reset(); return null; } @@ -329,6 +335,62 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { return biomes != null && biomes[layer] != null; } + @Override + public ThreadUnsafeCharBlocks createCopy() { + char[][] blocksCopy = new char[sectionCount][]; + for (int i = 0; i < sectionCount; i++) { + if (blocks[i] != null) { + blocksCopy[i] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER]; + System.arraycopy(blocks[i], 0, blocksCopy[i], 0, FaweCache.INSTANCE.BLOCKS_PER_LAYER); + } + } + BiomeType[][] biomesCopy; + if (biomes == null) { + biomesCopy = null; + } else { + biomesCopy = new BiomeType[sectionCount][]; + for (int i = 0; i < sectionCount; i++) { + if (biomes[i] != null) { + biomesCopy[i] = new BiomeType[biomes[i].length]; + System.arraycopy(biomes[i], 0, biomesCopy[i], 0, biomes[i].length); + } + } + } + char[][] lightCopy = createLightCopy(light, sectionCount); + char[][] skyLightCopy = createLightCopy(skyLight, sectionCount); + return new ThreadUnsafeCharBlocks( + blocksCopy, + minSectionPosition, + maxSectionPosition, + biomesCopy, + sectionCount, + lightCopy, + skyLightCopy, + tiles != null ? new BlockVector3ChunkMap<>(tiles) : null, + entities != null ? new HashSet<>(entities) : null, + entityRemoves != null ? new HashSet<>(entityRemoves) : null, + heightMaps != null ? new EnumMap<>(heightMaps) : null, + defaultOrdinal(), + fastMode, + bitMask + ); + } + + static char[][] createLightCopy(char[][] lightArr, int sectionCount) { + if (lightArr == null) { + return null; + } else { + char[][] lightCopy = new char[sectionCount][]; + for (int i = 0; i < sectionCount; i++) { + if (lightArr[i] != null) { + lightCopy[i] = new char[lightArr[i].length]; + System.arraycopy(lightArr[i], 0, lightCopy[i], 0, lightArr[i].length); + } + } + return lightCopy; + } + } + @Override public char[] load(final int layer) { updateSectionIndexRange(layer); @@ -348,67 +410,47 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { if (layer < minSectionPosition) { int diff = minSectionPosition - layer; sectionCount += diff; - char[][] tmpBlocks = new char[sectionCount][]; - Section[] tmpSections = new Section[sectionCount]; - Object[] tmpSectionLocks = new Object[sectionCount]; - System.arraycopy(blocks, 0, tmpBlocks, diff, blocks.length); - System.arraycopy(sections, 0, tmpSections, diff, sections.length); - System.arraycopy(sectionLocks, 0, tmpSectionLocks, diff, sections.length); - for (int i = 0; i < diff; i++) { - tmpSections[i] = EMPTY; - tmpSectionLocks[i] = new Object(); - } - blocks = tmpBlocks; - sections = tmpSections; - sectionLocks = tmpSectionLocks; minSectionPosition = layer; - if (biomes != null) { - BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64]; - System.arraycopy(biomes, 0, tmpBiomes, diff, biomes.length); - biomes = tmpBiomes; - } - if (light != null) { - char[][] tmplight = new char[sectionCount][]; - System.arraycopy(light, 0, tmplight, diff, light.length); - light = tmplight; - } - if (skyLight != null) { - char[][] tmplight = new char[sectionCount][]; - System.arraycopy(skyLight, 0, tmplight, diff, skyLight.length); - skyLight = tmplight; - } + resizeSectionsArrays(diff, false); // prepend new layer(s) } else { int diff = layer - maxSectionPosition; sectionCount += diff; - char[][] tmpBlocks = new char[sectionCount][]; - Section[] tmpSections = new Section[sectionCount]; - Object[] tmpSectionLocks = new Object[sectionCount]; - System.arraycopy(blocks, 0, tmpBlocks, 0, blocks.length); - System.arraycopy(sections, 0, tmpSections, 0, sections.length); - System.arraycopy(sectionLocks, 0, tmpSectionLocks, 0, sections.length); - for (int i = sectionCount - diff; i < sectionCount; i++) { - tmpSections[i] = EMPTY; - tmpSectionLocks[i] = new Object(); - } - blocks = tmpBlocks; - sections = tmpSections; - sectionLocks = tmpSectionLocks; maxSectionPosition = layer; - if (biomes != null) { - BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64]; - System.arraycopy(biomes, 0, tmpBiomes, 0, biomes.length); - biomes = tmpBiomes; - } - if (light != null) { - char[][] tmplight = new char[sectionCount][]; - System.arraycopy(light, 0, tmplight, 0, light.length); - light = tmplight; - } - if (skyLight != null) { - char[][] tmplight = new char[sectionCount][]; - System.arraycopy(skyLight, 0, tmplight, 0, skyLight.length); - skyLight = tmplight; - } + resizeSectionsArrays(diff, true); // append new layer(s) + } + } + + private void resizeSectionsArrays(int diff, boolean appendNew) { + char[][] tmpBlocks = new char[sectionCount][]; + Section[] tmpSections = new Section[sectionCount]; + Object[] tmpSectionLocks = new Object[sectionCount]; + int destPos = appendNew ? 0 : diff; + System.arraycopy(blocks, 0, tmpBlocks, destPos, blocks.length); + System.arraycopy(sections, 0, tmpSections, destPos, sections.length); + System.arraycopy(sectionLocks, 0, tmpSectionLocks, destPos, sections.length); + int toFillFrom = appendNew ? sectionCount - diff : 0; + int toFillTo = appendNew ? sectionCount : diff; + for (int i = toFillFrom; i < toFillTo; i++) { + tmpSections[i] = EMPTY; + tmpSectionLocks[i] = new Object(); + } + blocks = tmpBlocks; + sections = tmpSections; + sectionLocks = tmpSectionLocks; + if (biomes != null) { + BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64]; + System.arraycopy(biomes, 0, tmpBiomes, destPos, biomes.length); + biomes = tmpBiomes; + } + if (light != null) { + char[][] tmplight = new char[sectionCount][]; + System.arraycopy(light, 0, tmplight, destPos, light.length); + light = tmplight; + } + if (skyLight != null) { + char[][] tmplight = new char[sectionCount][]; + System.arraycopy(skyLight, 0, tmplight, destPos, skyLight.length); + skyLight = tmplight; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java new file mode 100644 index 000000000..f3dda058f --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java @@ -0,0 +1,531 @@ +package com.fastasyncworldedit.core.queue.implementation.blocks; + +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.math.BlockVector3ChunkMap; +import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * Equivalent to {@link CharSetBlocks} without any attempt to make thread-safe for improved performance. + * This is currently only used as a "copy" of {@link CharSetBlocks} to provide to + * {@link com.fastasyncworldedit.core.queue.IBatchProcessor} instances for processing without overlapping the continuing edit. + * + * @since 2.6.2 + */ +public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private final char defaultOrdinal; + private char[][] blocks; + private int minSectionPosition; + private int maxSectionPosition; + private int sectionCount; + private BiomeType[][] biomes; + private char[][] light; + private char[][] skyLight; + private BlockVector3ChunkMap tiles; + private HashSet entities; + private HashSet entityRemoves; + private Map heightMaps; + private boolean fastMode; + private int bitMask; + + /** + * New instance given the data stored in a {@link CharSetBlocks} instance. + * + * @since 2.6.2 + */ + ThreadUnsafeCharBlocks( + char[][] blocks, + int minSectionPosition, + int maxSectionPosition, + BiomeType[][] biomes, + int sectionCount, + char[][] light, + char[][] skyLight, + BlockVector3ChunkMap tiles, + HashSet entities, + HashSet entityRemoves, + Map heightMaps, + char defaultOrdinal, + boolean fastMode, + int bitMask + ) { + this.blocks = blocks; + this.minSectionPosition = minSectionPosition; + this.maxSectionPosition = maxSectionPosition; + this.biomes = biomes; + this.sectionCount = sectionCount; + this.light = light; + this.skyLight = skyLight; + this.tiles = tiles; + this.entities = entities; + this.entityRemoves = entityRemoves; + this.heightMaps = heightMaps; + this.defaultOrdinal = defaultOrdinal; + this.fastMode = fastMode; + this.bitMask = bitMask; + } + + @Override + public boolean hasSection(int layer) { + layer -= minSectionPosition; + return layer >= 0 && layer < blocks.length && blocks[layer] != null && blocks[layer].length == FaweCache.INSTANCE.BLOCKS_PER_LAYER; + } + + @Override + public char[] load(int layer) { + updateSectionIndexRange(layer); + layer -= minSectionPosition; + char[] arr = blocks[layer]; + if (arr == null) { + arr = blocks[layer] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER]; + } + return arr; + } + + @Nullable + @Override + public char[] loadIfPresent(int layer) { + if (layer < minSectionPosition || layer > maxSectionPosition) { + return null; + } + layer -= minSectionPosition; + return blocks[layer]; + } + + @Override + public Map getTiles() { + return tiles == null ? Collections.emptyMap() : tiles; + } + + @Override + public CompoundTag getTile(int x, int y, int z) { + return tiles.get(x, y, z); + } + + @Override + public Set getEntities() { + return entities == null ? Collections.emptySet() : entities; + } + + @Override + public Map getHeightMaps() { + return heightMaps == null ? new HashMap<>() : heightMaps; + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + updateSectionIndexRange(layer); + layer -= minSectionPosition; + if (light == null) { + light = new char[sectionCount][]; + } + if (light[layer] == null) { + light[layer] = new char[4096]; + } + Arrays.fill(light[layer], (char) 0); + if (sky) { + if (skyLight == null) { + skyLight = new char[sectionCount][]; + } + if (skyLight[layer] == null) { + skyLight[layer] = new char[4096]; + } + Arrays.fill(skyLight[layer], (char) 0); + } + } + + @Override + public boolean trim(boolean aggressive, int layer) { + return false; + } + + @Override + public int getSectionCount() { + return sectionCount; + } + + @Override + public int getMaxSectionPosition() { + return maxSectionPosition; + } + + @Override + public int getMinSectionPosition() { + return minSectionPosition; + } + + public char get(int x, int y, int z) { + int layer = (y >> 4); + if (!hasSection(layer)) { + return defaultOrdinal; + } + final int index = (y & 15) << 8 | z << 4 | x; + return blocks[layer - minSectionPosition][index]; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + int layer; + if (biomes == null || (y >> 4) < minSectionPosition || (y >> 4) > maxSectionPosition) { + return null; + } else if (biomes[(layer = (y >> 4) - minSectionPosition)] == null) { + return null; + } + return biomes[layer][(y & 15) >> 2 | (z >> 2) << 2 | x >> 2]; + } + + @Override + public BlockState getBlock(int x, int y, int z) { + return BlockTypesCache.states[get(x, y, z)]; + } + + @Override + public boolean setBiome(int x, int y, int z, BiomeType biome) { + updateSectionIndexRange(y >> 4); + int layer = (y >> 4) - minSectionPosition; + if (biomes == null) { + biomes = new BiomeType[sectionCount][]; + biomes[layer] = new BiomeType[64]; + } else if (biomes[layer] == null) { + biomes[layer] = new BiomeType[64]; + } + biomes[layer][(y & 12) << 2 | (z & 12) | (x & 12) >> 2] = biome; + return true; + } + + @Override + public boolean setBiome(BlockVector3 position, BiomeType biome) { + return setBiome(position.getX(), position.getY(), position.getZ(), biome); + } + + public void set(int x, int y, int z, char value) { + final int layer = y >> 4; + final int index = (y & 15) << 8 | z << 4 | x; + try { + blocks[layer][index] = value; + } catch (ArrayIndexOutOfBoundsException exception) { + LOGGER.error("Tried setting block at coordinates (" + x + "," + y + "," + z + ")"); + assert Fawe.platform() != null; + LOGGER.error("Layer variable was = {}", layer, exception); + } + } + + @Override + public > boolean setBlock(int x, int y, int z, T holder) { + updateSectionIndexRange(y >> 4); + set(x, y, z, holder.getOrdinalChar()); + holder.applyTileEntity(this, x, y, z); + return true; + } + + @Override + public void setBlocks(int layer, char[] data) { + updateSectionIndexRange(layer); + layer -= minSectionPosition; + this.blocks[layer] = data; + } + + @Override + public boolean isEmpty() { + if (biomes != null + || light != null + || skyLight != null + || (entities != null && !entities.isEmpty()) + || (tiles != null && !tiles.isEmpty()) + || (entityRemoves != null && !entityRemoves.isEmpty()) + || (heightMaps != null && !heightMaps.isEmpty())) { + return false; + } + for (int i = minSectionPosition; i <= maxSectionPosition; i++) { + if (hasSection(i)) { + return false; + } + } + return true; + } + + @Override + public boolean setTile(int x, int y, int z, CompoundTag tile) { + updateSectionIndexRange(y >> 4); + if (tiles == null) { + tiles = new BlockVector3ChunkMap<>(); + } + tiles.put(x, y, z, tile); + return true; + } + + @Override + public void setBlockLight(int x, int y, int z, int value) { + updateSectionIndexRange(y >> 4); + if (light == null) { + light = new char[sectionCount][]; + } + final int layer = (y >> 4) - minSectionPosition; + if (light[layer] == null) { + char[] c = new char[4096]; + Arrays.fill(c, (char) 16); + light[layer] = c; + } + final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15); + light[layer][index] = (char) value; + } + + @Override + public void setSkyLight(int x, int y, int z, int value) { + updateSectionIndexRange(y >> 4); + if (skyLight == null) { + skyLight = new char[sectionCount][]; + } + final int layer = (y >> 4) - minSectionPosition; + if (skyLight[layer] == null) { + char[] c = new char[4096]; + Arrays.fill(c, (char) 16); + skyLight[layer] = c; + } + final int index = (y & 15) << 8 | (z & 15) << 4 | (x & 15); + skyLight[layer][index] = (char) value; + } + + @Override + public void setHeightMap(HeightMapType type, int[] heightMap) { + if (heightMaps == null) { + heightMaps = new EnumMap<>(HeightMapType.class); + } + heightMaps.put(type, heightMap); + } + + @Override + public void setLightLayer(int layer, char[] toSet) { + updateSectionIndexRange(layer); + if (light == null) { + light = new char[sectionCount][]; + } + layer -= minSectionPosition; + light[layer] = toSet; + } + + @Override + public void setSkyLightLayer(int layer, char[] toSet) { + updateSectionIndexRange(layer); + if (skyLight == null) { + skyLight = new char[sectionCount][]; + } + layer -= minSectionPosition; + skyLight[layer] = toSet; + } + + @Override + public void setFullBright(int layer) { + updateSectionIndexRange(layer); + layer -= minSectionPosition; + if (light == null) { + light = new char[sectionCount][]; + } + if (light[layer] == null) { + light[layer] = new char[4096]; + } + if (skyLight == null) { + skyLight = new char[sectionCount][]; + } + if (skyLight[layer] == null) { + skyLight[layer] = new char[4096]; + } + Arrays.fill(light[layer], (char) 15); + Arrays.fill(skyLight[layer], (char) 15); + } + + @Override + public void setEntity(CompoundTag tag) { + if (entities == null) { + entities = new HashSet<>(); + } + entities.add(tag); + } + + @Override + public void removeEntity(UUID uuid) { + if (entityRemoves == null) { + entityRemoves = new HashSet<>(); + } + entityRemoves.add(uuid); + } + + @Override + public void setFastMode(boolean fastMode) { + this.fastMode = fastMode; + } + + @Override + public boolean isFastMode() { + return fastMode; + } + + @Override + public void setBitMask(int bitMask) { + this.bitMask = bitMask; + } + + @Override + public int getBitMask() { + return bitMask; + } + + @Override + public Set getEntityRemoves() { + return entityRemoves == null ? Collections.emptySet() : entityRemoves; + } + + @Override + public BiomeType[][] getBiomes() { + return biomes; + } + + @Override + public boolean hasBiomes() { + return IChunkSet.super.hasBiomes(); + } + + @Override + public char[][] getLight() { + return light; + } + + @Override + public char[][] getSkyLight() { + return skyLight; + } + + @Override + public boolean hasLight() { + return IChunkSet.super.hasLight(); + } + + @Override + public IChunkSet reset() { + blocks = new char[sectionCount][]; + biomes = new BiomeType[sectionCount][]; + light = new char[sectionCount][]; + skyLight = new char[sectionCount][]; + tiles.clear(); + entities.clear(); + entityRemoves.clear(); + heightMaps.clear(); + return this; + } + + @Override + public boolean hasBiomes(int layer) { + layer -= minSectionPosition; + return layer >= 0 && layer < biomes.length && biomes[layer] != null && biomes[layer].length > 0; + } + + @Override + public IChunkSet createCopy() { + char[][] blocksCopy = new char[sectionCount][]; + for (int i = 0; i < sectionCount; i++) { + blocksCopy[i] = new char[FaweCache.INSTANCE.BLOCKS_PER_LAYER]; + if (blocks[i] != null) { + System.arraycopy(blocks[i], 0, blocksCopy[i], 0, FaweCache.INSTANCE.BLOCKS_PER_LAYER); + } + } + BiomeType[][] biomesCopy; + if (biomes == null) { + biomesCopy = null; + } else { + biomesCopy = new BiomeType[sectionCount][]; + for (int i = 0; i < sectionCount; i++) { + if (biomes[i] != null) { + biomesCopy[i] = new BiomeType[biomes[i].length]; + System.arraycopy(biomes[i], 0, biomesCopy[i], 0, biomes[i].length); + } + } + } + char[][] lightCopy = CharSetBlocks.createLightCopy(light, sectionCount); + char[][] skyLightCopy = CharSetBlocks.createLightCopy(skyLight, sectionCount); + return new ThreadUnsafeCharBlocks( + blocksCopy, + minSectionPosition, + maxSectionPosition, + biomesCopy, + sectionCount, + lightCopy, + skyLightCopy, + tiles != null ? new BlockVector3ChunkMap<>(tiles) : null, + entities != null ? new HashSet<>(entities) : null, + entityRemoves != null ? new HashSet<>(entityRemoves) : null, + heightMaps != null ? new HashMap<>(heightMaps) : null, + defaultOrdinal, + fastMode, + bitMask + ); + } + + @Override + public boolean trim(boolean aggressive) { + return false; + } + + // Checks and updates the various section arrays against the new layer index + private void updateSectionIndexRange(int layer) { + if (layer >= minSectionPosition && layer <= maxSectionPosition) { + return; + } + if (layer < minSectionPosition) { + int diff = minSectionPosition - layer; + sectionCount += diff; + minSectionPosition = layer; + resizeSectionsArrays(layer, diff, false); // prepend new layer(s) + } else { + int diff = layer - maxSectionPosition; + sectionCount += diff; + maxSectionPosition = layer; + resizeSectionsArrays(layer, diff, true); // append new layer(s) + } + } + + private void resizeSectionsArrays(int layer, int diff, boolean appendNew) { + char[][] tmpBlocks = new char[sectionCount][]; + int destPos = appendNew ? 0 : diff; + System.arraycopy(blocks, 0, tmpBlocks, destPos, blocks.length); + blocks = tmpBlocks; + if (biomes != null) { + BiomeType[][] tmpBiomes = new BiomeType[sectionCount][64]; + System.arraycopy(biomes, 0, tmpBiomes, destPos, biomes.length); + biomes = tmpBiomes; + } + if (light != null) { + char[][] tmplight = new char[sectionCount][]; + System.arraycopy(light, 0, tmplight, destPos, light.length); + light = tmplight; + } + if (skyLight != null) { + char[][] tmplight = new char[sectionCount][]; + System.arraycopy(skyLight, 0, tmplight, destPos, skyLight.length); + skyLight = tmplight; + } + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java index 9347ecf59..7390758bc 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java @@ -1044,7 +1044,7 @@ public class ChunkHolder> implements IQueueChunk { if (chunkSet != null && !chunkSet.isEmpty()) { chunkSet.setBitMask(bitMask); try { - return this.call(chunkSet, () -> { + return this.call(chunkSet.createCopy(), () -> { this.delegate = NULL; chunkSet = null; calledLock.unlock(stamp); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java index 8deb59d13..8122a840c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MainUtil.java @@ -52,6 +52,7 @@ import java.io.PrintWriter; import java.lang.reflect.Array; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -68,7 +69,6 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -81,10 +81,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.regex.Pattern; -import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -533,6 +531,21 @@ public class MainUtil { return readImage(new FileInputStream(file)); } + public static void checkImageHost(URI uri) throws IOException { + if (Settings.settings().WEB.ALLOWED_IMAGE_HOSTS.contains("*")) { + return; + } + String host = uri.getHost(); + if (Settings.settings().WEB.ALLOWED_IMAGE_HOSTS.stream().anyMatch(host::equalsIgnoreCase)) { + return; + } + throw new IOException(String.format( + "Host `%s` not allowed! Whitelisted image hosts are: %s", + host, + StringMan.join(Settings.settings().WEB.ALLOWED_IMAGE_HOSTS, ", ") + )); + } + public static BufferedImage toRGB(BufferedImage src) { if (src == null) { return src; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java index ad69c87d6..f3abf0ac3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java @@ -157,13 +157,13 @@ public abstract class TaskManager { */ public void runUnsafe(Runnable run) { QueueHandler queue = Fawe.instance().getQueueHandler(); - queue.startSet(true); + queue.startUnsafe(Fawe.isMainThread()); try { run.run(); } catch (Throwable e) { e.printStackTrace(); } - queue.endSet(true); + queue.endUnsafe(Fawe.isMainThread()); } /** diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/WEManager.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/WEManager.java index 85f4eb2d1..439b486e3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/WEManager.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/WEManager.java @@ -130,7 +130,9 @@ public class WEManager { backupRegions.add(region); } } else { - player.print(Caption.of("fawe.error.region-mask-invalid", mask.getClass().getSimpleName())); + if (Settings.settings().ENABLED_COMPONENTS.DEBUG) { + player.printDebug(Caption.of("fawe.error.region-mask-invalid", mask.getClass().getSimpleName())); + } removed = true; iterator.remove(); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/collection/SimpleRandomCollection.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/collection/SimpleRandomCollection.java index d67227abe..dc2107559 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/collection/SimpleRandomCollection.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/collection/SimpleRandomCollection.java @@ -36,7 +36,7 @@ public class SimpleRandomCollection extends RandomCollection { @Override public E next(int x, int y, int z) { - return map.ceilingEntry(getRandom().nextDouble(x, y, z)).getValue(); + return map.ceilingEntry(getRandom().nextDouble(x, y, z) * this.total).getValue(); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java index a8998e1f0..f99a17e6f 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/image/ImageUtil.java @@ -203,6 +203,7 @@ public class ImageUtil { arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; } URL url = new URL(arg); + MainUtil.checkImageHost(url.toURI()); BufferedImage img = MainUtil.readImage(url); if (img == null) { throw new IOException("Failed to read " + url + ", please try again later"); @@ -218,7 +219,7 @@ public class ImageUtil { return MainUtil.readImage(file); } throw new InputParseException(Caption.of("fawe.error.invalid-image", TextComponent.of(arg))); - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { throw new InputParseException(TextComponent.of(e.getMessage())); } } @@ -229,7 +230,9 @@ public class ImageUtil { if (arg.contains("imgur.com") && !arg.contains("i.imgur.com")) { arg = "https://i.imgur.com/" + arg.split("imgur.com/")[1] + ".png"; } - return new URL(arg).toURI(); + URI uri = new URI(arg); + MainUtil.checkImageHost(uri); + return uri; } if (arg.startsWith("file:/")) { arg = arg.replaceFirst("file:/+", ""); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java index 10f910928..7cd91f87a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/AsyncNotifyQueue.java @@ -1,9 +1,13 @@ package com.fastasyncworldedit.core.util.task; import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.configuration.Settings; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.Closeable; import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -11,6 +15,13 @@ import java.util.function.Supplier; public class AsyncNotifyQueue implements Closeable { + private static final ForkJoinPool QUEUE_SUBMISSIONS = new ForkJoinPool( + Settings.settings().QUEUE.PARALLEL_THREADS, + new FaweForkJoinWorkerThreadFactory("AsyncNotifyQueue - %s"), + null, + false + ); + private final Lock lock = new ReentrantLock(true); private final Thread.UncaughtExceptionHandler handler; private boolean closed; @@ -59,7 +70,7 @@ public class AsyncNotifyQueue implements Closeable { } return null; }; - self[0] = Fawe.instance().getQueueHandler().async(wrapped); + self[0] = QUEUE_SUBMISSIONS.submit(wrapped); return self[0]; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/FaweForkJoinWorkerThreadFactory.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/FaweForkJoinWorkerThreadFactory.java new file mode 100644 index 000000000..589b5b863 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/FaweForkJoinWorkerThreadFactory.java @@ -0,0 +1,21 @@ +package com.fastasyncworldedit.core.util.task; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; + +public class FaweForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + + private final String nameFormat; + + public FaweForkJoinWorkerThreadFactory(String nameFormat) { + this.nameFormat = nameFormat; + } + + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setName(String.format(nameFormat, worker.getPoolIndex())); + return worker; + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/KeyQueuedExecutorService.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/KeyQueuedExecutorService.java new file mode 100644 index 000000000..d9db5c888 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/task/KeyQueuedExecutorService.java @@ -0,0 +1,172 @@ +package com.fastasyncworldedit.core.util.task; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Executor service that queues tasks based on keys, executing tasks on a configurable {@link ThreadPoolExecutor} + * + * @param Key type + * @since 2.6.2 + */ +public class KeyQueuedExecutorService { + + private final ExecutorService parent; + private final Map keyQueue = new HashMap<>(); + + /** + * Create a new {@link KeyQueuedExecutorService} instance + * + * @param parent Parent {@link ExecutorService} to use for actual task completion + */ + public KeyQueuedExecutorService(ExecutorService parent) { + this.parent = parent; + } + + /** + * Delegates to {@link ThreadPoolExecutor#shutdown()} + */ + public void shutdown() { + parent.shutdown(); + } + + /** + * Delegates to {@link ThreadPoolExecutor#shutdownNow()} + */ + @Nonnull + public List shutdownNow() { + return parent.shutdownNow(); + } + + /** + * Delegates to {@link ThreadPoolExecutor#isShutdown()} + */ + public boolean isShutdown() { + return parent.isShutdown(); + } + + /** + * Delegates to {@link ThreadPoolExecutor#isTerminated()} + */ + public boolean isTerminated() { + return parent.isTerminated(); + } + + /** + * Delegates to {@link ThreadPoolExecutor#awaitTermination(long, TimeUnit)} + */ + public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException { + return parent.awaitTermination(timeout, unit); + } + + protected FutureTask newTaskFor(Runnable runnable, T value) { + return new FutureTask<>(runnable, value); + } + + protected FutureTask newTaskFor(Callable callable) { + return new FutureTask<>(callable); + } + + @Nonnull + public Future submit(@Nonnull K key, @Nonnull Callable task) { + FutureTask ftask = newTaskFor(task); + execute(key, ftask); + return ftask; + } + + @Nonnull + public Future submit(@Nonnull K key, @Nonnull Runnable task, T result) { + FutureTask ftask = newTaskFor(task, result); + execute(key, ftask); + return ftask; + } + + @Nonnull + public Future submit(@Nonnull K key, @Nonnull Runnable task) { + FutureTask ftask = newTaskFor(task, null); + execute(key, ftask); + return ftask; + } + + public void execute(@Nonnull K key, @Nonnull FutureTask command) { + synchronized (keyQueue) { + boolean triggerRun = false; + KeyRunner runner = keyQueue.get(key); + if (runner == null) { + runner = new KeyRunner(key); + keyQueue.put(key, runner); + triggerRun = true; + } + runner.add(command); + if (triggerRun) { + runner.triggerRun(); + } + } + } + + private final class KeyRunner { + + private final Queue> tasks = new ConcurrentLinkedQueue<>(); + private final K key; + + private KeyRunner(K key) { + this.key = key; + } + + void add(FutureTask task) { + if (!tasks.add(task)) { + throw new RejectedExecutionException(rejection()); + } + } + + void triggerRun() { + Runnable task = tasks.poll(); + if (task == null) { + throw new RejectedExecutionException(rejection()); + } + try { + run(task); + } catch (RejectedExecutionException e) { + synchronized (keyQueue) { + keyQueue.remove(key); + } + throw new RejectedExecutionException(rejection(), e); + } + } + + private void run(Runnable task) { + parent.execute(() -> { + task.run(); + Runnable next = tasks.poll(); + if (next == null) { + synchronized (keyQueue) { + next = tasks.poll(); + if (next == null) { + keyQueue.remove(key); + } + } + } + if (next != null) { + run(next); + } + }); + } + + private String rejection() { + return "Task for the key '" + key + "' rejected"; + } + + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java index c235ca784..2512af96c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java @@ -531,16 +531,14 @@ public final class EditSessionBuilder { } if (allowedRegions == null && Settings.settings().REGION_RESTRICTIONS) { if (actor != null && !actor.hasPermission("fawe.bypass.regions")) { - if (actor instanceof Player) { - Player player = (Player) actor; + if (actor instanceof Player player) { allowedRegions = player.getAllowedRegions(); } } } if (disallowedRegions == null && Settings.settings().REGION_RESTRICTIONS && Settings.settings().REGION_RESTRICTIONS_OPTIONS.ALLOW_BLACKLISTS) { if (actor != null && !actor.hasPermission("fawe.bypass.regions")) { - if (actor instanceof Player) { - Player player = (Player) actor; + if (actor instanceof Player player) { disallowedRegions = player.getDisallowedRegions(); } } @@ -561,6 +559,9 @@ public final class EditSessionBuilder { } } } + if (placeChunks && regionExtent != null) { + queue.addProcessor(regionExtent); + } // There's no need to do the below (and it'll also just be a pain to implement) if we're not placing chunks if (placeChunks) { if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) { @@ -597,15 +598,14 @@ public final class EditSessionBuilder { this.extent = regionExtent; } if (this.limit != null && this.limit.STRIP_NBT != null && !this.limit.STRIP_NBT.isEmpty()) { + this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT); if (placeChunks) { - queue.addProcessor(new StripNBTExtent(this.extent, this.limit.STRIP_NBT)); - } else { - this.extent = new StripNBTExtent(this.extent, this.limit.STRIP_NBT); + queue.addProcessor((IBatchProcessor) this.extent); } } if (this.limit != null && !this.limit.isUnlimited()) { Set limitBlocks = new HashSet<>(); - if ((getActor() == null || getActor().hasPermission("worldedit.anyblock")) && this.limit.UNIVERSAL_DISALLOWED_BLOCKS) { + if (getActor() != null && !getActor().hasPermission("worldedit.anyblock") && this.limit.UNIVERSAL_DISALLOWED_BLOCKS) { limitBlocks.addAll(WorldEdit.getInstance().getConfiguration().disallowedBlocks); } if (this.limit.DISALLOWED_BLOCKS != null && !this.limit.DISALLOWED_BLOCKS.isEmpty()) { @@ -613,10 +613,9 @@ public final class EditSessionBuilder { } Set> remaps = this.limit.REMAP_PROPERTIES; if (!limitBlocks.isEmpty() || (remaps != null && !remaps.isEmpty())) { + this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps); if (placeChunks) { - queue.addProcessor(new DisallowedBlocksExtent(this.extent, limitBlocks, remaps)); - } else { - this.extent = new DisallowedBlocksExtent(this.extent, limitBlocks, remaps); + queue.addProcessor((IBatchProcessor) this.extent); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java index 149af4eed..0b2bd576d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java @@ -82,7 +82,10 @@ public class BiomeCommands { aliases = {"biomels", "/biomelist", "/listbiomes"}, desc = "Gets all biomes available." ) - @CommandPermissions("worldedit.biome.list") + @CommandPermissions( + value = "worldedit.biome.list", + queued = false + ) public void biomeList( Actor actor, @ArgFlag(name = 'p', desc = "Page number.", def = "1") diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 5c87d9136..358f75413 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -134,6 +134,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileSystems; import java.util.List; @@ -310,11 +311,11 @@ public class BrushCommands { }, desc = "Join multiple objects together in a curve", descFooter = - "Click to select some objects,click the same block twice to connect the objects.\n" - + "Insufficient brush radius, or clicking the the wrong spot will result in undesired shapes. The shapes must be simple lines or loops.\n" - + "Pic1: http://i.imgur.com/CeRYAoV.jpg -> http://i.imgur.com/jtM0jA4.png\n" - + "Pic2: http://i.imgur.com/bUeyc72.png -> http://i.imgur.com/tg6MkcF.png" - + "Tutorial: https://www.planetminecraft.com/blog/fawe-tutorial/" + """ + Click to select some objects,click the same block twice to connect the objects. + Insufficient brush radius, or clicking the the wrong spot will result in undesired shapes. The shapes must be simple lines or loops. + Pic1: http://i.imgur.com/CeRYAoV.jpg -> http://i.imgur.com/jtM0jA4.png + Pic2: http://i.imgur.com/bUeyc72.png -> http://i.imgur.com/tg6MkcF.pngTutorial: https://www.planetminecraft.com/blog/fawe-tutorial/""" ) @CommandPermissions("worldedit.brush.spline") public void splineBrush( @@ -337,9 +338,10 @@ public class BrushCommands { "vaesweep" }, desc = "Sweep your clipboard content along a curve", - descFooter = "Sweeps your clipboard content along a curve.\n" - + "Define a curve by selecting the individual points with a brush\n" - + "Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve" + descFooter = """ + Sweeps your clipboard content along a curve. + Define a curve by selecting the individual points with a brush + Set [copies] to a value > 0 if you want to have your selection pasted a limited amount of times equally spaced on the curve""" ) @CommandPermissions("worldedit.brush.sweep") public void sweepBrush( @@ -520,11 +522,9 @@ public class BrushCommands { @Switch(name = 'a', desc = "Use image Alpha") boolean alpha, @Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut ) - throws WorldEditException, IOException { + throws WorldEditException, IOException, URISyntaxException { URL url = new URL(imageURL); - if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { - throw new IOException("Only i.imgur.com links are allowed!"); - } + MainUtil.checkImageHost(url.toURI()); BufferedImage image = MainUtil.readImage(url); worldEdit.checkMaxBrushRadius(radius); if (yscale != 1) { @@ -636,10 +636,10 @@ public class BrushCommands { @Command( name = "layer", desc = "Replaces terrain with a layer.", - descFooter = "Replaces terrain with a layer.\n" - + "Example: /br layer 5 oak_planks,orange_stained_glass,magenta_stained_glass,black_wool - Places several " + - "layers on a surface\n" - + "Pic: https://i.imgur.com/XV0vYoX.png" + descFooter = """ + Replaces terrain with a layer. + Example: /br layer 5 oak_planks,orange_stained_glass,magenta_stained_glass,black_wool - Places several layers on a surface + Pic: https://i.imgur.com/XV0vYoX.png""" ) @CommandPermissions("worldedit.brush.layer") public void surfaceLayer( @@ -658,11 +658,11 @@ public class BrushCommands { @Command( name = "splatter", desc = "Splatter a pattern on a surface", - descFooter = "Sets a bunch of blocks randomly on a surface.\n" - + "Pic: https://i.imgur.com/hMD29oO.png\n" - + "Example: /br splatter stone,dirt 30 15\n" - + "Note: The seeds define how many splotches there are, recursion defines how large, " - + "solid defines whether the pattern is applied per seed, else per block." + descFooter = """ + Sets a bunch of blocks randomly on a surface. + Pic: https://i.imgur.com/hMD29oO.png + Example: /br splatter stone,dirt 30 15 + Note: The seeds define how many splotches there are, recursion defines how large, solid defines whether the pattern is applied per seed, else per block.""" ) @CommandPermissions("worldedit.brush.splatter") public void splatterBrush( @@ -691,9 +691,10 @@ public class BrushCommands { "scommand" }, desc = "Run commands at random points on a surface", - descFooter = "Run commands at random points on a surface\n" - + " - Your selection will be expanded to the specified size around each point\n" - + " - Placeholders: {x}, {y}, {z}, {world}, {size}" + descFooter = """ + Run commands at random points on a surface + - Your selection will be expanded to the specified size around each point + - Placeholders: {x}, {y}, {z}, {world}, {size}""" ) @CommandPermissions("worldedit.brush.scattercommand") public void scatterCommandBrush( @@ -723,9 +724,10 @@ public class BrushCommands { name = "height", aliases = {"heightmap"}, desc = "Raise or lower terrain using a heightmap", - descFooter = "This brush raises and lowers land.\n" - + "Note: Use a negative yscale to reduce height\n" - + "Snow Pic: https://i.imgur.com/Hrzn0I4.png" + descFooter = """ + This brush raises and lowers land. + Note: Use a negative yscale to reduce height + Snow Pic: https://i.imgur.com/Hrzn0I4.png""" ) @CommandPermissions("worldedit.brush.height") public void heightBrush( @@ -890,9 +892,11 @@ public class BrushCommands { "copypasta" }, desc = "Copy Paste brush", - descFooter = "Left click the base of an object to copy.\n" + "Right click to paste\n" - + "Note: Works well with the clipboard scroll action\n" - + "Video: https://www.youtube.com/watch?v=RPZIaTbqoZw" + descFooter = """ + Left click the base of an object to copy. + Right click to paste + Note: Works well with the clipboard scroll action + Video: https://www.youtube.com/watch?v=RPZIaTbqoZw""" ) @CommandPermissions("worldedit.brush.copy") public void copy( @@ -914,9 +918,10 @@ public class BrushCommands { name = "command", aliases = {"cmd"}, desc = "Command brush", - descFooter = "Run the commands at the clicked position.\n" - + " - Your selection will be expanded to the specified size around each point\n" - + " - Placeholders: {x}, {y}, {z}, {world}, {size}" + descFooter = """ + Run the commands at the clicked position. + - Your selection will be expanded to the specified size around each point + - Placeholders: {x}, {y}, {z}, {world}, {size}""" ) @CommandPermissions("worldedit.brush.command") public void command( @@ -1036,7 +1041,7 @@ public class BrushCommands { ) throws WorldEditException { WorldEdit.getInstance().checkMaxBrushRadius(radius); - BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()); + BrushTool tool = session.getBrushTool(player); tool.setSize(radius); tool.setFill(null); tool.setBrush(new OperationFactoryBrush(factory, shape, session), permission); @@ -1196,17 +1201,12 @@ public class BrushCommands { brush = new HollowSphereBrush(); } else { //FAWE start - Suggest different brush material if sand or gravel is used - if (pattern instanceof BlockStateHolder) { - BlockType type = ((BlockStateHolder) pattern).getBlockType(); - switch (type.getId()) { - case "minecraft:sand": - case "minecraft:gravel": - player.print( - Caption.of("fawe.worldedit.brush.brush.try.other")); - falling = true; - break; - default: - break; + if (pattern instanceof BlockStateHolder holder) { + BlockType type = holder.getBlockType(); + if (type == BlockTypes.SAND || type == BlockTypes.GRAVEL) { + player.print( + Caption.of("fawe.worldedit.brush.brush.try.other")); + falling = true; } } if (falling) { @@ -1258,13 +1258,12 @@ public class BrushCommands { @Command( name = "clipboard", - desc = "@Deprecated use instead: `/br copypaste`)", + desc = "Paste your clipboard at the brush location. Includes any transforms.", descFooter = "Choose the clipboard brush.\n" + "Without the -o flag, the paste will appear centered at the target location. " + "With the flag, then the paste will appear relative to where you had " + "stood relative to the copied area when you copied it." ) - @Deprecated @CommandPermissions("worldedit.brush.clipboard") public void clipboardBrush( Player player, LocalSession session, @@ -1278,7 +1277,11 @@ public class BrushCommands { boolean pasteBiomes, @ArgFlag(name = 'm', desc = "Skip blocks matching this mask in the clipboard") @ClipboardMask - Mask sourceMask, InjectedValueAccess context + Mask sourceMask, InjectedValueAccess context, + //FAWE start - random rotation + @Switch(name = 'r', desc = "Apply random rotation on paste, combines with existing clipboard transforms") + boolean randomRotate + //FAWE end ) throws WorldEditException { ClipboardHolder holder = session.getClipboard(); @@ -1294,9 +1297,9 @@ public class BrushCommands { set( context, - new ClipboardBrush(newHolder, ignoreAir, usingOrigin, pasteEntities, pasteBiomes, - sourceMask - ), + //FAWE start - random rotation + new ClipboardBrush(newHolder, ignoreAir, usingOrigin, pasteEntities, pasteBiomes, sourceMask, randomRotate), + //FAWE end "worldedit.brush.clipboard" ); } @@ -1361,7 +1364,7 @@ public class BrushCommands { iterations = Math.min(limit.MAX_ITERATIONS, iterations); //FAWE end - set(context, new SnowSmoothBrush(iterations, mask), "worldedit.brush.snowsmooth").setSize(radius); + set(context, new SnowSmoothBrush(iterations, snowBlockCount, mask), "worldedit.brush.snowsmooth").setSize(radius); player.print(Caption.of( "worldedit.brush.smooth.equip", radius, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java index d1b9084fb..c36252f85 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ChunkCommands.java @@ -79,7 +79,10 @@ public class ChunkCommands { aliases = {"/chunkinfo"}, desc = "Get information about the chunk you're inside" ) - @CommandPermissions("worldedit.chunkinfo") + @CommandPermissions( + value = "worldedit.chunkinfo", + queued = false + ) public void chunkInfo(Player player) { Location pos = player.getBlockLocation(); int chunkX = (int) Math.floor(pos.getBlockX() / 16.0); @@ -99,7 +102,10 @@ public class ChunkCommands { aliases = {"/listchunks"}, desc = "List chunks that your selection includes" ) - @CommandPermissions("worldedit.listchunks") + @CommandPermissions( + value = "worldedit.listchunks", + queued = false + ) public void listChunks( Actor actor, World world, LocalSession session, @ArgFlag(name = 'p', desc = "Page number.", def = "1") int page diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index 945ce0364..6ce86f455 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -74,6 +74,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionIntersection; import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.regions.selector.ExtendingCuboidRegionSelector; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; @@ -159,35 +160,7 @@ public class ClipboardCommands { session.getPlacementPosition(actor)); ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint()); copy.setCopyingEntities(copyEntities); - copy.setCopyingBiomes(copyBiomes); - - Mask sourceMask = editSession.getSourceMask(); - Region[] regions = editSession.getAllowedRegions(); - Region allowedRegion; - if (regions == null || regions.length == 0) { - allowedRegion = new NullRegion(); - } else { - allowedRegion = new RegionIntersection(regions); - } - final Mask firstSourceMask = mask != null ? mask : sourceMask; - final Mask finalMask = MaskIntersection.of(firstSourceMask, new RegionMask(allowedRegion)).optimize(); - if (finalMask != Masks.alwaysTrue()) { - copy.setSourceMask(finalMask); - } - if (sourceMask != null) { - editSession.setSourceMask(null); - new MaskTraverser(sourceMask).reset(editSession); - editSession.setSourceMask(null); - } - - try { - Operations.completeLegacy(copy); - } catch (Throwable e) { - throw e; - } finally { - clipboard.flush(); - } - session.setClipboard(new ClipboardHolder(clipboard)); + createCopy(session, editSession, copyBiomes, mask, clipboard, copy); copy.getStatusMessages().forEach(actor::print); //FAWE end @@ -298,7 +271,25 @@ public class ClipboardCommands { copy.setSourceFunction(new BlockReplace(editSession, leavePattern)); copy.setCopyingEntities(copyEntities); copy.setRemovingEntities(true); + createCopy(session, editSession, copyBiomes, mask, clipboard, copy); + + if (!actor.hasPermission("fawe.tips")) { + actor.print(Caption.of("fawe.tips.tip.lazycut")); + } + copy.getStatusMessages().forEach(actor::print); + //FAWE end + } + + private void createCopy( + final LocalSession session, + final EditSession editSession, + final boolean copyBiomes, + final Mask mask, + final Clipboard clipboard, + final ForwardExtentCopy copy + ) { copy.setCopyingBiomes(copyBiomes); + Mask sourceMask = editSession.getSourceMask(); Region[] regions = editSession.getAllowedRegions(); Region allowedRegion; @@ -317,20 +308,13 @@ public class ClipboardCommands { new MaskTraverser(sourceMask).reset(editSession); editSession.setSourceMask(null); } + try { Operations.completeLegacy(copy); - } catch (Throwable e) { - throw e; } finally { clipboard.flush(); } session.setClipboard(new ClipboardHolder(clipboard)); - - if (!actor.hasPermission("fawe.tips")) { - actor.print(Caption.of("fawe.tips.tip.lazycut")); - } - copy.getStatusMessages().forEach(actor::print); - //FAWE end } @Command( @@ -444,7 +428,12 @@ public class ClipboardCommands { Vector3 max = realTo.add(holder .getTransform() .apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())); - RegionSelector selector = new CuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint()); + final CuboidRegionSelector selector; + if (session.getRegionSelector(world) instanceof ExtendingCuboidRegionSelector) { + selector = new ExtendingCuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint()); + } else { + selector = new CuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint()); + } session.setRegionSelector(world, selector); selector.learnChanges(); selector.explainRegionAdjust(actor, session); @@ -475,9 +464,10 @@ public class ClipboardCommands { @Command( name = "/rotate", desc = "Rotate the contents of the clipboard", - descFooter = "Non-destructively rotate the contents of the clipboard.\n" - + "Angles are provided in degrees and a positive angle will result in a clockwise rotation. " - + "Multiple rotations can be stacked. Interpolation is not performed so angles should be a multiple of 90 degrees.\n" + descFooter = """ + Non-destructively rotate the contents of the clipboard. + Angles are provided in degrees and a positive angle will result in a clockwise rotation. Multiple rotations can be stacked. Interpolation is not performed so angles should be a multiple of 90 degrees. + """ ) @CommandPermissions("worldedit.clipboard.rotate") public void rotate( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java index 3d54e40a7..0cf5b1a78 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java @@ -149,14 +149,11 @@ public class GeneralCommands { String arg0 = args.get(0).toLowerCase(Locale.ENGLISH); String flipped; switch (arg0) { - case "on": - flipped = "off"; - break; - case "off": - flipped = "on"; - break; - default: + case "on" -> flipped = "off"; + case "off" -> flipped = "on"; + default -> { return TextComponent.of("There is no replacement for //fast " + arg0); + } } return CommandUtil.createNewCommandReplacementText("//perf " + flipped); } @@ -362,7 +359,10 @@ public class GeneralCommands { descFooter = "This is dependent on platform implementation. " + "Not all platforms support watchdog hooks, or contain a watchdog." ) - @CommandPermissions("worldedit.watchdog") + @CommandPermissions( + value = "worldedit.watchdog", + queued = false + ) public void watchdog( Actor actor, LocalSession session, @Arg(desc = "The mode to set the watchdog hook to", def = "") @@ -424,7 +424,10 @@ public class GeneralCommands { aliases = {"/searchitem", "/l", "/search"}, desc = "Search for an item" ) - @CommandPermissions("worldedit.searchitem") + @CommandPermissions( + value = "worldedit.searchitem", + queued = false + ) public void searchItem( Actor actor, @Switch(name = 'b', desc = "Only search for blocks") @@ -573,7 +576,10 @@ public class GeneralCommands { aliases = {"tips"}, desc = "Toggle FAWE tips" ) - @CommandPermissions("fawe.tips") + @CommandPermissions( + value = "fawe.tips", + queued = false + ) public void tips(Actor actor, LocalSession session) throws WorldEditException { if (actor.togglePermission("fawe.tips")) { actor.print(Caption.of("fawe.info.worldedit.toggle.tips.on")); @@ -627,7 +633,6 @@ public class GeneralCommands { String command = "/searchitem " + (blocksOnly ? "-b " : "") + (itemsOnly ? "-i " : "") + "-p %page% " + search; Map results = new TreeMap<>(); String idMatch = search.replace(' ', '_'); - String nameMatch = search.toLowerCase(Locale.ROOT); for (ItemType searchType : ItemType.REGISTRY) { if (blocksOnly && !searchType.hasBlockType()) { continue; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index dabc1dc92..ca67ad132 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -65,6 +65,7 @@ import org.jetbrains.annotations.Range; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.IOException; +import java.net.URISyntaxException; import java.net.URL; import java.util.List; import java.util.concurrent.ExecutorService; @@ -119,18 +120,15 @@ public class GenerationCommands { final double radiusX; final double radiusZ; switch (radii.size()) { - case 1: - radiusX = radiusZ = Math.max(1, radii.get(0)); - break; - - case 2: + case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0)); + case 2 -> { radiusX = Math.max(1, radii.get(0)); radiusZ = Math.max(1, radii.get(1)); - break; - - default: + } + default -> { actor.print(Caption.of("worldedit.cyl.invalid-radius")); return 0; + } } worldEdit.checkMaxRadius(radiusX); worldEdit.checkMaxRadius(radiusZ); @@ -169,18 +167,15 @@ public class GenerationCommands { final double radiusX; final double radiusZ; switch (radii.size()) { - case 1: - radiusX = radiusZ = Math.max(1, radii.get(0)); - break; - - case 2: + case 1 -> radiusX = radiusZ = Math.max(1, radii.get(0)); + case 2 -> { radiusX = Math.max(1, radii.get(0)); radiusZ = Math.max(1, radii.get(1)); - break; - - default: + } + default -> { actor.print(Caption.of("worldedit.cyl.invalid-radius")); return 0; + } } worldEdit.checkMaxRadius(radiusX); @@ -234,19 +229,16 @@ public class GenerationCommands { final double radiusY; final double radiusZ; switch (radii.size()) { - case 1: - radiusX = radiusY = radiusZ = Math.max(0, radii.get(0)); - break; - - case 3: + case 1 -> radiusX = radiusY = radiusZ = Math.max(0, radii.get(0)); + case 3 -> { radiusX = Math.max(0, radii.get(0)); radiusY = Math.max(0, radii.get(1)); radiusZ = Math.max(0, radii.get(2)); - break; - - default: + } + default -> { actor.print(Caption.of("worldedit.sphere.invalid-radius")); return 0; + } } worldEdit.checkMaxRadius(radiusX); @@ -437,9 +429,10 @@ public class GenerationCommands { name = "/generatebiome", aliases = {"/genbiome", "/gb"}, desc = "Sets biome according to a formula.", - descFooter = "Formula must return positive numbers (true) if the point is inside the shape\n" - + "Sets the biome of blocks in that shape.\n" - + "For details, see https://ehub.to/we/expr" + descFooter = """ + Formula must return positive numbers (true) if the point is inside the shape + Sets the biome of blocks in that shape. + For details, see https://ehub.to/we/expr""" ) @CommandPermissions("worldedit.generation.shape.biome") @Logging(ALL) @@ -588,12 +581,10 @@ public class GenerationCommands { @Arg(desc = "boolean", def = "true") boolean randomize, @Arg(desc = "TODO", def = "100") int threshold, @Arg(desc = "BlockVector2", def = "") BlockVector2 dimensions - ) throws WorldEditException, IOException { + ) throws WorldEditException, IOException, URISyntaxException { TextureUtil tu = Fawe.instance().getCachedTextureUtil(randomize, 0, threshold); URL url = new URL(imageURL); - if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { - throw new IOException("Only i.imgur.com links are allowed!"); - } + MainUtil.checkImageHost(url.toURI()); if (dimensions != null) { checkCommandArgument( (long) dimensions.getX() * dimensions.getZ() <= Settings.settings().WEB.MAX_IMAGE_SIZE, @@ -626,14 +617,12 @@ public class GenerationCommands { BlockVector3 pos1 = session.getPlacementPosition(actor); BlockVector3 pos2 = pos1.add(image.getWidth() - 1, 0, image.getHeight() - 1); CuboidRegion region = new CuboidRegion(pos1, pos2); - int[] count = new int[1]; final BufferedImage finalImage = image; RegionVisitor visitor = new RegionVisitor(region, pos -> { int x = pos.getBlockX() - pos1.getBlockX(); int z = pos.getBlockZ() - pos1.getBlockZ(); int color = finalImage.getRGB(x, z); BlockType block = tu.getNearestBlock(color); - count[0]++; if (block != null) { return editSession.setBlock(pos, block.getDefaultState()); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistorySubCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistorySubCommands.java index 3688041d4..452d5084c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistorySubCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/HistorySubCommands.java @@ -223,7 +223,10 @@ public class HistorySubCommands { aliases = {"summary", "summarize"}, desc = "Summarize an edit" ) - @CommandPermissions("worldedit.history.info") + @CommandPermissions( + value = "worldedit.history.info", + queued = false + ) public synchronized void summary( Player player, RollbackDatabase database, Arguments arguments, @Arg(desc = "Player uuid/name") @@ -314,8 +317,7 @@ public class HistorySubCommands { public Component apply(@Nullable Supplier input) { ChangeSet edit = input.get(); - if (edit instanceof RollbackOptimizedHistory) { - RollbackOptimizedHistory rollback = (RollbackOptimizedHistory) edit; + if (edit instanceof RollbackOptimizedHistory rollback) { UUID uuid = rollback.getUUID(); int index = rollback.getIndex(); @@ -368,7 +370,10 @@ public class HistorySubCommands { aliases = {"inspect", "search", "near"}, desc = "Find nearby edits" ) - @CommandPermissions("worldedit.history.find") + @CommandPermissions( + value = "worldedit.history.find", + queued = false + ) public synchronized void find( Player player, World world, RollbackDatabase database, Arguments arguments, @ArgFlag(name = 'u', def = "", desc = "String user") @@ -429,7 +434,10 @@ public class HistorySubCommands { aliases = {"distribution"}, desc = "View block distribution for an edit" ) - @CommandPermissions("worldedit.history.distr") + @CommandPermissions( + value = "worldedit.history.distr", + queued = false + ) public void distr( Player player, LocalSession session, RollbackDatabase database, Arguments arguments, @Arg(desc = "Player uuid/name") @@ -468,7 +476,10 @@ public class HistorySubCommands { name = "list", desc = "List your history" ) - @CommandPermissions("worldedit.history.list") + @CommandPermissions( + value = "worldedit.history.list", + queued = false + ) public void list( Player player, LocalSession session, RollbackDatabase database, Arguments arguments, @Arg(desc = "Player uuid/name") @@ -476,7 +487,6 @@ public class HistorySubCommands { @ArgFlag(name = 'p', desc = "Page to view.", def = "") Integer page ) { - int index = session.getHistoryIndex(); List> history = Lists.transform( session.getHistory(), (Function>) input -> () -> input diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/NavigationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/NavigationCommands.java index 156fe95e9..db0d59b38 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/NavigationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/NavigationCommands.java @@ -60,7 +60,10 @@ public class NavigationCommands { aliases = {"!", "/unstuck"}, desc = "Escape from being stuck inside a block" ) - @CommandPermissions("worldedit.navigation.unstuck") + @CommandPermissions( + value = "worldedit.navigation.unstuck", + queued = false + ) public void unstuck(Player player) throws WorldEditException { player.findFreePosition(); player.print(Caption.of("worldedit.unstuck.moved")); @@ -71,7 +74,10 @@ public class NavigationCommands { aliases = {"asc", "/asc", "/ascend"}, desc = "Go up a floor" ) - @CommandPermissions("worldedit.navigation.ascend") + @CommandPermissions( + value = "worldedit.navigation.ascend", + queued = false + ) public void ascend( Player player, @Arg(desc = "# of levels to ascend", def = "1") @@ -102,7 +108,10 @@ public class NavigationCommands { aliases = {"desc", "/desc", "/descend"}, desc = "Go down a floor" ) - @CommandPermissions("worldedit.navigation.descend") + @CommandPermissions( + value = "worldedit.navigation.descend", + queued = false + ) public void descend( Player player, @Arg(desc = "# of levels to descend", def = "1") @@ -159,7 +168,10 @@ public class NavigationCommands { aliases = {"/thru"}, desc = "Pass through walls" ) - @CommandPermissions("worldedit.navigation.thru.command") + @CommandPermissions( + value = "worldedit.navigation.thru.command", + queued = false + ) public void thru(Player player) throws WorldEditException { if (player.passThroughForwardWall(6)) { player.print(Caption.of("worldedit.thru.moved")); @@ -173,7 +185,10 @@ public class NavigationCommands { aliases = {"j", "/jumpto", "/j"}, desc = "Teleport to a location" ) - @CommandPermissions("worldedit.navigation.jumpto.command") + @CommandPermissions( + value = "worldedit.navigation.jumpto.command", + queued = false + ) public void jumpTo( Player player, @Arg(desc = "Location to jump to", def = "") diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 5d3fb30e8..a72cba4b3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -140,7 +140,10 @@ public class RegionCommands { name = "/test", desc = "test region" ) - @CommandPermissions("worldedit.region.test") + @CommandPermissions( + value = "worldedit.region.test", + queued = false + ) @Logging(REGION) public void test( Actor actor, EditSession editSession, @@ -175,7 +178,10 @@ public class RegionCommands { aliases = "/nbt", desc = "View nbt info for a block" ) - @CommandPermissions("worldedit.nbtinfo") + @CommandPermissions( + value = "worldedit.nbtinfo", + queued = false + ) public void nbtinfo(Player player, EditSession editSession) { Location pos = player.getBlockTrace(128); if (pos == null) { @@ -228,13 +234,12 @@ public class RegionCommands { @Switch(name = 'h', desc = "Generate only a shell") boolean shell ) throws WorldEditException { - if (!(region instanceof CuboidRegion)) { + if (!(region instanceof CuboidRegion cuboidregion)) { actor.print(Caption.of("worldedit.line.cuboid-only")); return 0; } checkCommandArgument(thickness >= 0, "Thickness must be >= 0"); - CuboidRegion cuboidregion = (CuboidRegion) region; BlockVector3 pos1 = cuboidregion.getPos1(); BlockVector3 pos2 = cuboidregion.getPos2(); int blocksChanged = editSession.drawLine(pattern, pos1, pos2, thickness, !shell); @@ -261,13 +266,12 @@ public class RegionCommands { @Switch(name = 'h', desc = "Generate only a shell") boolean shell ) throws WorldEditException { - if (!(region instanceof ConvexPolyhedralRegion)) { + if (!(region instanceof ConvexPolyhedralRegion cpregion)) { actor.print(Caption.of("worldedit.curve.invalid-type")); return 0; } checkCommandArgument(thickness >= 0, "Thickness must be >= 0"); - ConvexPolyhedralRegion cpregion = (ConvexPolyhedralRegion) region; List vectors = new ArrayList<>(cpregion.getVertices()); int blocksChanged = editSession.drawSpline(pattern, vectors, 0, 0, 0, 10, thickness, !shell); @@ -468,7 +472,10 @@ public class RegionCommands { desc = "Bypass region restrictions", descFooter = "Bypass region restrictions" ) - @CommandPermissions("fawe.admin") + @CommandPermissions( + value = "fawe.admin", + queued = false + ) public void wea(Actor actor) throws WorldEditException { if (actor.togglePermission("fawe.bypass")) { actor.print(Caption.of("fawe.info.worldedit.bypassed")); @@ -697,7 +704,7 @@ public class RegionCommands { actor.print(Caption.of("fawe.regen.time")); //FAWE end RegenOptions options = RegenOptions.builder() - .seed(!randomSeed ? seed : new Long(ThreadLocalRandom.current().nextLong())) + .seed(!randomSeed ? seed : Long.valueOf(ThreadLocalRandom.current().nextLong())) .regenBiomes(regenBiomes) .biomeType(biomeType) .build(); @@ -718,9 +725,10 @@ public class RegionCommands { @Command( name = "/deform", desc = "Deforms a selected region with an expression", - descFooter = "The expression is executed for each block and is expected\n" - + "to modify the variables x, y and z to point to a new block\n" - + "to fetch. For details, see https://ehub.to/we/expr" + descFooter = """ + The expression is executed for each block and is expected + to modify the variables x, y and z to point to a new block + to fetch. For details, see https://ehub.to/we/expr""" ) @CommandPermissions("worldedit.region.deform") @Logging(ALL) @@ -794,9 +802,10 @@ public class RegionCommands { @Command( name = "/hollow", desc = "Hollows out the object contained in this selection", - descFooter = "Hollows out the object contained in this selection.\n" - + "Optionally fills the hollowed out part with the given block.\n" - + "Thickness is measured in manhattan distance." + descFooter = """ + Hollows out the object contained in this selection. + Optionally fills the hollowed out part with the given block. + Thickness is measured in manhattan distance.""" ) @CommandPermissions("worldedit.region.hollow") @Logging(REGION) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index 0151efbb6..539d9c70a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -26,7 +26,6 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure; import com.fastasyncworldedit.core.util.MainUtil; -import com.google.common.base.Function; import com.google.common.collect.Multimap; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; @@ -46,6 +45,7 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.formatting.component.ErrorFormat; @@ -90,6 +90,8 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; import java.util.regex.Pattern; import static com.fastasyncworldedit.core.util.ReflectionUtils.as; @@ -211,11 +213,9 @@ public class SchematicCommands { } ClipboardHolder clipboard = session.getClipboard(); - if (clipboard instanceof URIClipboardHolder) { - URIClipboardHolder identifiable = (URIClipboardHolder) clipboard; + if (clipboard instanceof URIClipboardHolder identifiable) { if (identifiable.contains(uri)) { - if (identifiable instanceof MultiClipboardHolder) { - MultiClipboardHolder multi = (MultiClipboardHolder) identifiable; + if (identifiable instanceof MultiClipboardHolder multi) { multi.remove(uri); if (multi.getHolders().isEmpty()) { session.setClipboard(null); @@ -317,12 +317,16 @@ public class SchematicCommands { @Arg(desc = "File name.") String filename, @Arg(desc = "Format name.", def = "fast") - String formatName + String formatName, + //FAWE start - random rotation + @Switch(name = 'r', desc = "Apply random rotation to the clipboard") + boolean randomRotate + //FAWE end ) throws FilenameException { LocalConfiguration config = worldEdit.getConfiguration(); //FAWE start - ClipboardFormat format = null; + ClipboardFormat format; InputStream in = null; try { URI uri; @@ -385,6 +389,12 @@ public class SchematicCommands { uri = file.toURI(); format.hold(actor, uri, in); + if (randomRotate) { + AffineTransform transform = new AffineTransform(); + int rotate = 90 * ThreadLocalRandom.current().nextInt(4); + transform = transform.rotateY(rotate); + session.getClipboard().setTransform(transform); + } actor.print(Caption.of("fawe.worldedit.schematic.schematic.loaded", filename)); } catch (IllegalArgumentException e) { actor.print(Caption.of("worldedit.schematic.unknown-filename", TextComponent.of(filename))); @@ -515,7 +525,10 @@ public class SchematicCommands { aliases = {"listformats", "f"}, desc = "List available formats" ) - @CommandPermissions("worldedit.schematic.formats") + @CommandPermissions( + value = "worldedit.schematic.formats", + queued = false + ) public void formats(Actor actor) { actor.print(Caption.of("worldedit.schematic.formats.title")); StringBuilder builder; @@ -541,7 +554,10 @@ public class SchematicCommands { desc = "List saved schematics", descFooter = "Note: Format is not fully verified until loading." ) - @CommandPermissions("worldedit.schematic.list") + @CommandPermissions( + value = "worldedit.schematic.list", + queued = false + ) public void list( Actor actor, LocalSession session, @ArgFlag(name = 'p', desc = "Page to view.", def = "1") @@ -812,7 +828,6 @@ public class SchematicCommands { final String SCHEMATIC_NAME = file.getName(); double oldKbOverwritten = 0; - String overwrittenPath = curFilepath; int numFiles = -1; if (checkFilesize) { @@ -828,10 +843,10 @@ public class SchematicCommands { if (overwrite) { oldKbOverwritten = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0; int iter = 1; - while (new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) { + while (new File(curFilepath + "." + iter + "." + format.getPrimaryFileExtension()).exists()) { iter++; } - file = new File(overwrittenPath + "." + iter + "." + format.getPrimaryFileExtension()); + file = new File(curFilepath + "." + iter + "." + format.getPrimaryFileExtension()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index e6e06e9a2..64e3c8a81 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -314,7 +314,10 @@ public class SelectionCommands { name = "/wand", desc = "Get the wand object" ) - @CommandPermissions("worldedit.wand") + @CommandPermissions( + value = "worldedit.wand", + queued = false + ) public void wand( Player player, LocalSession session, @Switch(name = 'n', desc = "Get a navigation wand") boolean navWand @@ -348,7 +351,10 @@ public class SelectionCommands { aliases = {"/toggleeditwand"}, desc = "Remind the user that the wand is now a tool and can be unbound with /tool none." ) - @CommandPermissions("worldedit.wand.toggle") + @CommandPermissions( + value = "worldedit.wand.toggle", + queued = false + ) public void toggleWand(Player player) { player.print( Caption.of( @@ -499,7 +505,10 @@ public class SelectionCommands { name = "/size", desc = "Get information about the selection" ) - @CommandPermissions("worldedit.selection.size") + @CommandPermissions( + value = "worldedit.selection.size", + queued = false + ) public void size( Actor actor, World world, LocalSession session, @Switch(name = 'c', desc = "Get clipboard info instead") @@ -734,6 +743,7 @@ public class SelectionCommands { box.appendCommand("sphere", Caption.of("worldedit.select.sphere.description"), "//sel sphere"); box.appendCommand("cyl", Caption.of("worldedit.select.cyl.description"), "//sel cyl"); box.appendCommand("convex", Caption.of("worldedit.select.convex.description"), "//sel convex"); + //FAWE start box.appendCommand("polyhedral", Caption.of("fawe.selection.sel.polyhedral"), "//sel polyhedral"); box.appendCommand("fuzzy[=]", Caption.of("fawe.selection.sel.fuzzy-instruction"), "//sel fuzzy[=]"); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotCommands.java index 66cfdd853..6d9bda084 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotCommands.java @@ -98,7 +98,10 @@ public class SnapshotCommands { name = "list", desc = "List snapshots" ) - @CommandPermissions("worldedit.snapshots.list") + @CommandPermissions( + value = "worldedit.snapshots.list", + queued = false + ) void list( Actor actor, World world, @ArgFlag(name = 'p', desc = "Page of results to return", def = "1") @@ -127,8 +130,7 @@ public class SnapshotCommands { TextComponent.of(world.getName()) )); - if (config.snapshotDatabase instanceof FileSystemSnapshotDatabase) { - FileSystemSnapshotDatabase db = (FileSystemSnapshotDatabase) config.snapshotDatabase; + if (config.snapshotDatabase instanceof FileSystemSnapshotDatabase db) { Path root = db.getRoot(); if (Files.isDirectory(root)) { WorldEdit.logger.info("No snapshots were found for world '" diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotUtilCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotUtilCommands.java index 8de52f6aa..b98785631 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotUtilCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SnapshotUtilCommands.java @@ -95,7 +95,7 @@ public class SnapshotUtilCommands { if (snapshotName != null) { URI uri = resolveSnapshotName(config, snapshotName); Optional snapOpt = config.snapshotDatabase.getSnapshot(uri); - if (!snapOpt.isPresent()) { + if (snapOpt.isEmpty()) { actor.print(Caption.of("worldedit.restore.not-available")); return; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java index 6f47132f7..0694b2af3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolUtilCommands.java @@ -140,7 +140,7 @@ public class ToolUtilCommands { @Arg(desc = "The range of the brush") int range ) throws WorldEditException { - session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()).setRange(range); + session.getBrushTool(player).setRange(range); player.print(Caption.of("worldedit.tool.range.set")); } @@ -156,7 +156,7 @@ public class ToolUtilCommands { ) throws WorldEditException { we.checkMaxBrushRadius(size); - session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()).setSize(size); + session.getBrushTool(player).setSize(size); player.print(Caption.of("worldedit.tool.size.set")); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index b3403bba4..f064868b4 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -29,7 +29,6 @@ import com.fastasyncworldedit.core.util.StringMan; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.image.ImageUtil; import com.fastasyncworldedit.core.util.task.DelegateConsumer; -import com.google.common.base.Function; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.LocalConfiguration; @@ -98,6 +97,7 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -130,7 +130,10 @@ public class UtilityCommands { aliases = {"/hmi", "hmi"}, desc = "Generate the heightmap interface: https://github.com/IntellectualSites/HeightMap" ) - @CommandPermissions("fawe.admin") + @CommandPermissions( + value = "fawe.admin", + queued = false + ) public void heightmapInterface( Actor actor, @Arg(name = "min", desc = "int", def = "100") int min, @@ -145,12 +148,9 @@ public class UtilityCommands { final int sub = srcFolder.getAbsolutePath().length(); List images = new ArrayList<>(); MainUtil.iterateFiles(srcFolder, file -> { - switch (file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(Locale.ROOT)) { - case ".png": - case ".jpeg": - break; - default: - return; + String s = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(Locale.ROOT); + if (!s.equals(".png") && !s.equals(".jpeg")) { + return; } try { String name = file.getAbsolutePath().substring(sub); @@ -187,7 +187,7 @@ public class UtilityCommands { StringBuilder config = new StringBuilder(); config.append("var images = [\n"); for (String image : images) { - config.append('"' + image.replace(File.separator, "/") + "\",\n"); + config.append('"').append(image.replace(File.separator, "/")).append("\",\n"); } config.append("];\n"); config.append("// The low res images (they should all be the same size)\n"); @@ -805,7 +805,10 @@ public class UtilityCommands { name = "/help", desc = "Displays help for WorldEdit commands" ) - @CommandPermissions("worldedit.help") + @CommandPermissions( + value = "worldedit.help", + queued = false + ) public void help( Actor actor, @Switch(name = 's', desc = "List sub-commands of the given command, if applicable") @@ -859,7 +862,6 @@ public class UtilityCommands { URI uri = input.getKey(); String path = input.getValue(); - boolean url = false; boolean loaded = isLoaded.apply(uri); URIType type = URIType.FILE; @@ -959,21 +961,13 @@ public class UtilityCommands { if (len > 0) { for (String arg : args) { switch (arg.toLowerCase(Locale.ROOT)) { - case "me": - case "mine": - case "local": - case "private": - listMine = true; - break; - case "public": - case "global": - listGlobal = true; - break; - case "all": + case "me", "mine", "local", "private" -> listMine = true; + case "public", "global" -> listGlobal = true; + case "all" -> { listMine = true; listGlobal = true; - break; - default: + } + default -> { if (arg.endsWith("/") || arg.endsWith(File.separator)) { arg = arg.replace("/", File.separator); String newDirFilter = dirFilter + arg; @@ -995,7 +989,7 @@ public class UtilityCommands { } else { filters.add(arg); } - break; + } } } } @@ -1005,7 +999,7 @@ public class UtilityCommands { List toFilter = new ArrayList<>(); if (!filters.isEmpty()) { - forEachFile = new DelegateConsumer(forEachFile) { + forEachFile = new DelegateConsumer<>(forEachFile) { @Override public void accept(File file) { toFilter.add(file); @@ -1015,7 +1009,7 @@ public class UtilityCommands { if (formatName != null) { final ClipboardFormat cf = ClipboardFormats.findByAlias(formatName); - forEachFile = new DelegateConsumer(forEachFile) { + forEachFile = new DelegateConsumer<>(forEachFile) { @Override public void accept(File file) { if (cf.isFormat(file)) { @@ -1024,7 +1018,7 @@ public class UtilityCommands { } }; } else { - forEachFile = new DelegateConsumer(forEachFile) { + forEachFile = new DelegateConsumer<>(forEachFile) { @Override public void accept(File file) { if (!file.toString().endsWith(".cached")) { @@ -1062,7 +1056,7 @@ public class UtilityCommands { } if (listGlobal) { File rel = MainUtil.resolveRelative(new File(dir, dirFilter)); - forEachFile = new DelegateConsumer(forEachFile) { + forEachFile = new DelegateConsumer<>(forEachFile) { @Override public void accept(File f) { try { @@ -1172,7 +1166,7 @@ public class UtilityCommands { StringBuilder name = new StringBuilder(); if (relative.isAbsolute()) { relative = root.toURI().relativize(file.toURI()); - name.append(".." + File.separator); + name.append("..").append(File.separator); } name.append(relative.getPath()); return name.toString(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java index aac36df2f..e596298ea 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java @@ -27,9 +27,13 @@ import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.session.ClipboardHolder; +import java.util.concurrent.ThreadLocalRandom; + public class ClipboardBrush implements Brush { private final ClipboardHolder holder; @@ -38,6 +42,9 @@ public class ClipboardBrush implements Brush { private final boolean pasteEntities; private final boolean pasteBiomes; private final Mask sourceMask; + //FAWE start - random rotation + private final boolean randomRotate; + //FAWE end public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin) { this.holder = holder; @@ -46,23 +53,48 @@ public class ClipboardBrush implements Brush { this.pasteBiomes = false; this.pasteEntities = false; this.sourceMask = null; + //FAWE start - random rotation + this.randomRotate = false; + //FAWE end } public ClipboardBrush( ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities, boolean pasteBiomes, Mask sourceMask ) { + //FAWE start - random rotation + this(holder, ignoreAirBlocks, usingOrigin, pasteEntities, pasteBiomes, sourceMask, false); + } + + public ClipboardBrush( + ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities, + boolean pasteBiomes, Mask sourceMask, boolean randomRotate + ) { + //FAWE end this.holder = holder; this.ignoreAirBlocks = ignoreAirBlocks; this.usingOrigin = usingOrigin; this.pasteEntities = pasteEntities; this.pasteBiomes = pasteBiomes; this.sourceMask = sourceMask; + this.randomRotate = randomRotate; } @Override public void build(EditSession editSession, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException { + //FAWE start - random rotation + Transform originalTransform = holder.getTransform(); + Transform transform = new AffineTransform(); + if (this.randomRotate) { + int rotate = 90 * ThreadLocalRandom.current().nextInt(4); + transform = ((AffineTransform) transform).rotateY(rotate); + if (originalTransform != null) { + transform = originalTransform.combine(transform); + } + } + holder.setTransform(transform); + //FAWE end Clipboard clipboard = holder.getClipboard(); Region region = clipboard.getRegion(); BlockVector3 centerOffset = region.getCenter().toBlockPoint().subtract(clipboard.getOrigin()); @@ -77,6 +109,10 @@ public class ClipboardBrush implements Brush { .build(); Operations.completeLegacy(operation); + //FAWE start - random rotation + // reset transform + holder.setTransform(originalTransform); + //FAWE end } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/ConfirmHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/ConfirmHandler.java index 5bf1ccc6c..067f9cfeb 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/ConfirmHandler.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/annotation/ConfirmHandler.java @@ -24,7 +24,7 @@ public class ConfirmHandler implements CommandCallListener { } Optional actorOpt = parameters.injectedValue(Key.of(Actor.class)); - if (!actorOpt.isPresent()) { + if (actorOpt.isEmpty()) { return; } Actor actor = actorOpt.get(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java index 781b02ae2..8f5b5cb53 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/entity/Player.java @@ -437,14 +437,16 @@ public interface Player extends Entity, Actor { } else { continue; } - WorldEdit.getInstance().getExecutorService().submit(() -> { + Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> { doc.close(); // Ensure closed before deletion doc.getFile().delete(); }); } } - } else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT || Settings.settings().CLIPBOARD.USE_DISK) { - WorldEdit.getInstance().getExecutorService().submit(() -> session.setClipboard(null)); + } else if (Settings.settings().CLIPBOARD.USE_DISK) { + Fawe.instance().getClipboardExecutor().submit(getUniqueId(), () -> session.setClipboard(null)); + } else if (Settings.settings().CLIPBOARD.DELETE_ON_LOGOUT) { + session.setClipboard(null); } if (Settings.settings().HISTORY.DELETE_ON_LOGOUT) { session.clearHistory(); @@ -470,7 +472,10 @@ public interface Player extends Entity, Actor { } } catch (EmptyClipboardException ignored) { } - DiskOptimizedClipboard doc = DiskOptimizedClipboard.loadFromFile(file); + DiskOptimizedClipboard doc = Fawe.instance().getClipboardExecutor().submit( + getUniqueId(), + () -> DiskOptimizedClipboard.loadFromFile(file) + ).get(); Clipboard clip = doc.toClipboard(); ClipboardHolder holder = new ClipboardHolder(clip); session.setClipboard(holder); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java index 406964c24..9f798e21c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java @@ -321,10 +321,24 @@ public class PlatformManager { return queryCapability(Capability.CONFIGURATION).getConfiguration(); } + /** + * Get the current supported {@link SideEffect}s. + * + * @return the supported side effects + * @throws NoCapablePlatformException thrown if no platform is capable + */ public Collection getSupportedSideEffects() { return queryCapability(Capability.WORLD_EDITING).getSupportedSideEffects(); } + /** + * Get the initialized state of the Platform. + * {@return if the platform manager is initialized} + */ + public boolean isInitialized() { + return initialized.get(); + } + /** * You shouldn't have been calling this anyways, but this is now deprecated. Either don't * fire this event at all, or fire the new event via the event bus if you're a platform. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java index 9ad799b4c..464ef3e4f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java @@ -266,8 +266,8 @@ public class SpongeSchematicReader extends NBTSchematicReader { if (id == null) { continue; } + values.put("id", id); //FAWE end - values.put("id", values.get("Id")); values.remove("Id"); values.remove("Pos"); if (fixer != null) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java index 369694f0b..6f3a63787 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/expression/Functions.java @@ -75,6 +75,7 @@ public final class Functions { ); handle = handle.asType(handle.type().changeReturnType(Number.class)); handle = filterReturnValue(handle, DOUBLE_VALUE); + handle = handle.asType(handle.type().wrap()); } // return vararg-ity if (wasVarargs) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java index 26f7636f4..cc2f03aa2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java @@ -31,6 +31,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.storage.ChunkStore; import javax.annotation.Nonnull; @@ -823,14 +824,15 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { boolean trimX = lowerX != 0 || upperX != 15; boolean trimZ = lowerZ != 0 || upperZ != 15; + if (!(trimX || trimZ)) { + return set; + } + for (int layer = get.getMinSectionPosition(); layer < get.getMaxSectionPosition(); layer++) { if (!set.hasSection(layer)) { continue; } char[] arr = Objects.requireNonNull(set.loadIfPresent(layer)); // This shouldn't be null if above is true - if (!(trimX || trimZ)) { - continue; - } int indexY = 0; for (int y = 0; y < 16; y++, indexY += 256) { // For each y layer within a chunk section int index; @@ -839,14 +841,14 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { for (int z = 0; z < lowerZ; z++) { // null the z values for (int x = 0; x < 16; x++, index++) { - arr[index] = 0; + arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__; } } index = indexY + upperZi; for (int z = upperZ + 1; z < 16; z++) { // null the z values for (int x = 0; x < 16; x++, index++) { - arr[index] = 0; + arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__; } } } @@ -855,11 +857,11 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { for (int z = lowerZ; z <= upperZ; z++, index += 16) { for (int x = 0; x < lowerX; x++) { // null the x values - arr[index + x] = 0; + arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__; } for (int x = upperX + 1; x < 16; x++) { // null the x values - arr[index + x] = 0; + arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__; } } } @@ -925,7 +927,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { for (int z = lowerZ; z <= upperZ; z++) { // null the z values for (int x = 0; x < 16; x++, index++) { - arr[index] = 0; + arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__; } } } @@ -934,7 +936,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { for (int z = lowerZ; z <= upperZ; z++, index += 16) { for (int x = lowerX; x <= upperX; x++) { // null the x values - arr[index + x] = 0; + arr[index + x] = BlockTypesCache.ReservedIDs.__RESERVED__; } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java index e02e72029..021e042be 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Polygonal2DRegion.java @@ -506,24 +506,26 @@ public class Polygonal2DRegion extends AbstractRegion implements FlatRegion { //FAWE start @Override public boolean containsEntireCuboid(int bx, int tx, int by, int ty, int bz, int tz) { - for (int x = bx; x <= tx; x++) { - if (!contains(x, 0, bz)) { - return false; + for (int y : new int[]{by, ty}) { + for (int x = bx; x <= tx; x++) { + if (!contains(x, y, bz)) { + return false; + } } - } - for (int x = bx; x <= tx; x++) { - if (!contains(x, 0, tz)) { - return false; + for (int x = bx; x <= tx; x++) { + if (!contains(x, y, tz)) { + return false; + } } - } - for (int z = bz; z <= tz; z++) { - if (!contains(bx, 0, z)) { - return false; + for (int z = bz; z <= tz; z++) { + if (!contains(bx, y, z)) { + return false; + } } - } - for (int z = bz; z <= tz; z++) { - if (!contains(tx, 0, z)) { - return false; + for (int z = bz; z <= tz; z++) { + if (!contains(tx, y, z)) { + return false; + } } } return true; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java index 8d718fb05..b89536520 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java @@ -38,12 +38,11 @@ import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BlockTypesCache; import javax.annotation.Nullable; import java.util.List; import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; /** * Represents a physical shape. @@ -402,16 +401,22 @@ public interface Region extends Iterable, Cloneable, IBatchProcess // contains some boolean processExtra = false; for (int layer = getMinimumY() >> 4; layer <= getMaximumY() >> 4; layer++) { + if (!set.hasSection(layer)) { + continue; + } int by = layer << 4; int ty = by + 15; if (!containsEntireCuboid(bx, tx, by, ty, bz, tz)) { processExtra = true; - char[] arr = set.load(layer); + char[] arr = set.loadIfPresent(layer); + if (arr == null) { + continue; + } for (int y = 0, index = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++, index++) { - if (arr[index] != 0 && !contains(x, y, z)) { - arr[index] = 0; + if (arr[index] != BlockTypesCache.ReservedIDs.__RESERVED__ && !contains(bx + x, by + y, bz + z)) { + arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__; } } } @@ -460,8 +465,8 @@ public interface Region extends Iterable, Cloneable, IBatchProcess for (int y = 0, index = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++, index++) { - if (arr[index] != 0 && contains(x, y, z)) { - arr[index] = 0; + if (arr[index] != BlockTypesCache.ReservedIDs.__RESERVED__ && contains(x, y, z)) { + arr[index] = BlockTypesCache.ReservedIDs.__RESERVED__; processExtra = true; } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ConvexPolyhedralRegionSelector.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ConvexPolyhedralRegionSelector.java index ced04eddf..70471ceae 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ConvexPolyhedralRegionSelector.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/selector/ConvexPolyhedralRegionSelector.java @@ -26,6 +26,7 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.internal.cui.CUIRegion; import com.sk89q.worldedit.internal.cui.SelectionPointEvent; import com.sk89q.worldedit.internal.cui.SelectionPolygonEvent; +import com.sk89q.worldedit.internal.cui.SelectionShapeEvent; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.ConvexPolyhedralRegion; @@ -206,6 +207,7 @@ public class ConvexPolyhedralRegionSelector implements RegionSelector, CUIRegion checkNotNull(session); checkNotNull(pos); + session.dispatchCUIEvent(player, new SelectionShapeEvent(getTypeID())); session.describeCUI(player); player.print(Caption.of("worldedit.selection.convex.explain.primary", TextComponent.of(pos.toString()))); @@ -226,6 +228,7 @@ public class ConvexPolyhedralRegionSelector implements RegionSelector, CUIRegion public void explainRegionAdjust(Actor player, LocalSession session) { checkNotNull(player); checkNotNull(session); + session.dispatchCUIEvent(player, new SelectionShapeEvent(getTypeID())); session.describeCUI(player); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java index 5fca65799..e6715907c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java @@ -47,8 +47,16 @@ public final class NamespacedRegistry extends Registry { this(name, MINECRAFT_NAMESPACE); } + public NamespacedRegistry(final String name, final boolean checkInitialized) { + this(name, MINECRAFT_NAMESPACE, checkInitialized); + } + public NamespacedRegistry(final String name, final String defaultNamespace) { - super(name); + this(name, defaultNamespace, false); + } + + public NamespacedRegistry(final String name, final String defaultNamespace, final boolean checkInitialized) { + super(name, checkInitialized); this.defaultNamespace = defaultNamespace; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java index 2bb20010a..b29af49e8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.registry; +import com.sk89q.worldedit.WorldEdit; + import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; @@ -35,9 +37,15 @@ public class Registry implements Iterable { private final Map map = new HashMap<>(); private final String name; + private final boolean checkInitialized; public Registry(final String name) { + this(name, false); + } + + public Registry(final String name, final boolean checkInitialized) { this.name = name; + this.checkInitialized = checkInitialized; } public String getName() { @@ -53,6 +61,11 @@ public class Registry implements Iterable { @Nullable public V get(final String key) { checkState(key.equals(key.toLowerCase(Locale.ROOT)), "key must be lowercase: %s", key); + if (this.checkInitialized) { + checkState( + WorldEdit.getInstance().getPlatformManager().isInitialized(), + "WorldEdit is not initialized yet."); + } return this.map.get(key); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/Int2BaseBlockMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/Int2BaseBlockMap.java index bf8e3bed5..fca089a68 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/Int2BaseBlockMap.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/Int2BaseBlockMap.java @@ -170,7 +170,7 @@ class Int2BaseBlockMap extends AbstractInt2ObjectMap { return old; } int oldId = commonMap.put(key, internalId); - return assumeAsBlock(oldId); + return BlockStateIdAccess.isValidInternalId(oldId) ? assumeAsBlock(oldId) : uncommonMap.remove(key); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java index ab503e1fb..e8501da69 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java @@ -32,7 +32,7 @@ import com.sk89q.worldedit.registry.NamespacedRegistry; public class BiomeType implements RegistryItem, Keyed, BiomePattern { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("biome type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("biome type", true); private final String id; private int legacyId = -1; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java index 3ed6f7a3f..29d8ee53b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java @@ -36,7 +36,7 @@ public class BlockCategory extends Category implements Keyed { //FAWE start private boolean[] flatMap; //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block tag"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block tag", true); public BlockCategory(final String id) { super(id); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java index 87959ebe6..f08675fc5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockState.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.world.block; import com.fastasyncworldedit.core.command.SuggestInputParseException; +import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.function.mask.SingleBlockStateMask; import com.fastasyncworldedit.core.queue.ITileInput; import com.fastasyncworldedit.core.registry.state.PropertyKey; @@ -43,6 +44,7 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.state.AbstractProperty; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; import com.sk89q.worldedit.world.registry.BlockMaterial; @@ -150,7 +152,7 @@ public class BlockState implements BlockStateHolder, Pattern { type = BlockTypes.get(key); if (type == null) { String input = key.toString(); - throw new SuggestInputParseException("Does not match a valid block type: " + input, input, () -> Stream.of( + throw new SuggestInputParseException(Caption.of("fawe.error.invalid-block-type", TextComponent.of(input)), () -> Stream.of( BlockTypesCache.values) .map(BlockType::getId) .filter(id -> StringMan.blockStateMatches(input, id)) @@ -211,8 +213,7 @@ public class BlockState implements BlockStateHolder, Pattern { String input = charSequence.toString(); BlockType finalType = type; throw new SuggestInputParseException( - "Invalid property " + key + ":" + input + " for type " + type, - input, + Caption.of("worldedit.error.parser.unknown-property", key + ":" + input, type), () -> finalType.getProperties().stream() .map(Property::getName) @@ -222,8 +223,7 @@ public class BlockState implements BlockStateHolder, Pattern { ); } else { throw new SuggestInputParseException( - "No operator for " + state, - "", + Caption.of("fawe.error.no-operator-for-input", state), () -> Collections.singletonList("=") ); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java index 043a89bf5..01a5d5c8a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java @@ -56,7 +56,7 @@ import static com.google.common.base.Preconditions.checkArgument; public class BlockType implements Keyed, Pattern { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block type", true); private static final Logger LOGGER = LogManagerCompat.getLogger(); private final String id; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java index e79a9af5a..b8af4bce5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java @@ -20,9 +20,11 @@ package com.sk89q.worldedit.world.block; import com.fastasyncworldedit.core.command.SuggestInputParseException; +import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.util.StringMan; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.registry.LegacyMapper; import javax.annotation.Nullable; @@ -1967,7 +1969,7 @@ public final class BlockTypes { } } - throw new SuggestInputParseException("Does not match a valid block type: " + inputLower, inputLower, () -> Stream.of( + throw new SuggestInputParseException(Caption.of("fawe.error.invalid-block-type", TextComponent.of(input)), () -> Stream.of( BlockTypesCache.values) .filter(b -> StringMan.blockStateMatches(inputLower, b.getId())) .map(BlockType::getId) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java index 98a70b7e5..5a530c8a6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java @@ -27,7 +27,7 @@ import com.sk89q.worldedit.registry.NamespacedRegistry; public class EntityType implements RegistryItem, Keyed { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("entity type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("entity type", true); private final String id; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java index 97549320d..ecba92f8b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java @@ -39,7 +39,7 @@ import javax.annotation.Nullable; public class ItemType implements RegistryItem, Keyed { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("item type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("item type", true); private final String id; @SuppressWarnings("deprecation") diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java index 0fbf9556a..3491347a4 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/BaseExpressionTest.java @@ -35,6 +35,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -63,6 +64,7 @@ class BaseExpressionTest { }); WorldEdit.getInstance().getPlatformManager().register(mockPlat); WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); + assertTrue(WorldEdit.getInstance().getPlatformManager().isInitialized(), "Platform is not initialized"); WorldEdit.getInstance().getConfiguration().calculationTimeout = 1_000; } diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java index ec175bd72..a1d418f85 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java @@ -449,4 +449,14 @@ class ExpressionTest extends BaseExpressionTest { assertTrue(e.getMessage().contains("Calculations exceeded time limit")); } + @Test + public void testRound() { + checkTestCase("round(1.3)", 1); + checkTestCase("round(0.9)", 1); + checkTestCase("round(-1.1)", -1); + checkTestCase("round(-0.9)", -1); + checkTestCase("round(1.5)", 2); + checkTestCase("round(-1.5)", -1); + } + } diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java index 1bb09ee17..9558ac0bb 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java @@ -21,6 +21,8 @@ package com.sk89q.worldedit.util.collection; import com.google.common.collect.ImmutableMap; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.StringTag; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.event.platform.PlatformsRegisteredEvent; import com.sk89q.worldedit.extension.platform.Capability; @@ -87,12 +89,20 @@ class BlockMapTest { Stream.of(Capability.values()) .collect(Collectors.toMap(Function.identity(), __ -> Preference.NORMAL)) ); + when(MOCKED_PLATFORM.getConfiguration()).thenReturn(new LocalConfiguration() { + @Override + public void load() { + } + }); PlatformManager platformManager = WorldEdit.getInstance().getPlatformManager(); platformManager.register(MOCKED_PLATFORM); WorldEdit.getInstance().getEventBus().post(new PlatformsRegisteredEvent()); + assertTrue(WorldEdit.getInstance().getPlatformManager().isInitialized(), "Platform is not initialized"); + registerBlock("minecraft:air"); registerBlock("minecraft:oak_wood"); + registerBlock("minecraft:chest"); } @AfterAll @@ -116,6 +126,7 @@ class BlockMapTest { private final BaseBlock air = checkNotNull(BlockTypes.AIR).getDefaultState().toBaseBlock(); private final BaseBlock oakWood = checkNotNull(BlockTypes.OAK_WOOD).getDefaultState().toBaseBlock(); + private final BaseBlock chestWithNbt = checkNotNull(BlockTypes.CHEST).getDefaultState().toBaseBlock(new CompoundTag(ImmutableMap.of("dummy", new StringTag("value")))); private AutoCloseable mocks; @@ -741,6 +752,22 @@ class BlockMapTest { }); } + @SuppressWarnings("OverwrittenKey") + @Test + @DisplayName("put with valid and invalid keys doesn't duplicate") + void putWithInvalidAndValid() { + generator.makeVectorsStream().forEach(vec -> { + BlockMap map = BlockMap.createForBaseBlock(); + // This tests https://github.com/EngineHub/WorldEdit/issues/2250 + // Due to two internal maps, a bug existed where both could have the same value + map.put(vec, chestWithNbt); + map.put(vec, air); + + assertEquals(1, map.size()); + assertEquals(air, map.get(vec)); + }); + } + } @Test diff --git a/worldedit-sponge/build.gradle.kts b/worldedit-sponge/build.gradle.kts index 76828b320..e895e2989 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.2.0") + testImplementation("org.mockito:mockito-core:5.3.1") } <<<<<<< HEAD