diff --git a/config/checkstyle/checkstyle-suppression.xml b/config/checkstyle/checkstyle-suppression.xml index 8f37b5bec9..13e951be27 100644 --- a/config/checkstyle/checkstyle-suppression.xml +++ b/config/checkstyle/checkstyle-suppression.xml @@ -9,8 +9,8 @@ - - + + diff --git a/gradle.properties b/gradle.properties index a92f77c48e..7f97622cc8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ group=com.sk89q.worldedit version=7.3.7-SNAPSHOT -org.gradle.jvmargs=-Xmx1500M +org.gradle.jvmargs=-Xmx1700M org.gradle.parallel=true loom_fabric_repository=https://maven.enginehub.org/artifactory/fabricmc/ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 69c0652ba3..227e5d8e64 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [plugins] codecov = "org.enginehub.codecov:0.2.0" neogradle-userdev = "net.neoforged.gradle.userdev:7.0.142" -fabric-loom = "fabric-loom:1.6.12" +fabric-loom = "fabric-loom:1.7.3" sponge-spongegradle = "org.spongepowered.gradle.plugin:2.2.0" sponge-vanillagradle = { id = "org.spongepowered.gradle.vanilla", version.ref = "sponge-vanillagradle" } @@ -21,8 +21,8 @@ sponge-api = "12.0.0-20240616.151530-3" sponge-api-major = "12" # https://parchmentmc.org/docs/getting-started; note that we use older MC versions some times which is OK -parchment-minecraft = "1.20.6" -parchment-mappings = "2024.06.16" +parchment-minecraft = "1.21" +parchment-mappings = "2024.07.28" # https://repo.spongepowered.org/service/rest/repository/browse/maven-public/org/spongepowered/vanillagradle/ sponge-vanillagradle = "0.2.1-20240617.053350-83" diff --git a/settings.gradle.kts b/settings.gradle.kts index bd35b3433b..ba3139aa0f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,7 +9,7 @@ pluginManagement { } plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" - id("fabric-loom") version "1.6.12" + id("fabric-loom") version "1.7.3" } dependencyResolutionManagement { repositories { diff --git a/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightAdapter.java index 19b96c9691..372c4ff7d7 100644 --- a/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightAdapter.java @@ -40,7 +40,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -435,8 +435,8 @@ public void setBiome(Location location, BiomeType biome) { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { - return new PaperweightWorldNativeAccess(this, + public NativeWorld createWorldNativeAccess(World world) { + return new PaperweightNativeWorld(this, new WeakReference<>(((CraftWorld) world).getHandle())); } diff --git a/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightNativeWorld.java similarity index 95% rename from worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightWorldNativeAccess.java rename to worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightNativeWorld.java index 51b1f8cc18..c226fe867f 100644 --- a/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1.20.2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R2/PaperweightNativeWorld.java @@ -21,7 +21,7 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.block.BlockState; @@ -41,7 +41,7 @@ import java.util.Objects; import javax.annotation.Nullable; -public class PaperweightWorldNativeAccess implements WorldNativeAccess { +public class PaperweightNativeWorld implements NativeWorld { private static final int UPDATE = 1; private static final int NOTIFY = 2; @@ -49,7 +49,7 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess world; private SideEffectSet sideEffectSet; - public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { + public PaperweightNativeWorld(PaperweightAdapter adapter, WeakReference world) { this.adapter = adapter; this.world = world; } @@ -64,8 +64,8 @@ public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { } @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); + public LevelChunk getChunk(int chunkX, int chunkZ) { + return getWorld().getChunk(chunkX, chunkZ); } @Override diff --git a/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightAdapter.java index d7abcbaa38..ef0d378101 100644 --- a/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightAdapter.java @@ -40,7 +40,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -435,8 +435,8 @@ public void setBiome(Location location, BiomeType biome) { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { - return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle())); + public NativeWorld createWorldNativeAccess(World world) { + return new PaperweightNativeWorld(this, new WeakReference<>(((CraftWorld) world).getHandle())); } private static net.minecraft.core.Direction adapt(Direction face) { diff --git a/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightNativeWorld.java similarity index 95% rename from worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightWorldNativeAccess.java rename to worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightNativeWorld.java index 5114570687..d956bccd6f 100644 --- a/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1.20.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R3/PaperweightNativeWorld.java @@ -21,7 +21,7 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.block.BlockState; @@ -41,7 +41,7 @@ import java.util.Objects; import javax.annotation.Nullable; -public class PaperweightWorldNativeAccess implements WorldNativeAccess { +public class PaperweightNativeWorld implements NativeWorld { private static final int UPDATE = 1; private static final int NOTIFY = 2; @@ -49,7 +49,7 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess world; private SideEffectSet sideEffectSet; - public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { + public PaperweightNativeWorld(PaperweightAdapter adapter, WeakReference world) { this.adapter = adapter; this.world = world; } @@ -64,8 +64,8 @@ public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { } @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); + public LevelChunk getChunk(int chunkX, int chunkZ) { + return getWorld().getChunk(chunkX, chunkZ); } @Override diff --git a/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightAdapter.java index ca3ec0d9b2..4e55afbf23 100644 --- a/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightAdapter.java @@ -40,7 +40,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -437,8 +437,8 @@ public void setBiome(Location location, BiomeType biome) { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { - return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle())); + public NativeWorld createWorldNativeAccess(World world) { + return new PaperweightNativeWorld(this, new WeakReference<>(((CraftWorld) world).getHandle())); } private static net.minecraft.core.Direction adapt(Direction face) { diff --git a/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightNativeWorld.java similarity index 95% rename from worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightWorldNativeAccess.java rename to worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightNativeWorld.java index 33096e005d..e1ce794153 100644 --- a/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1.20.6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_20_R4/PaperweightNativeWorld.java @@ -21,7 +21,7 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.block.BlockState; @@ -41,7 +41,7 @@ import java.util.Objects; import javax.annotation.Nullable; -public class PaperweightWorldNativeAccess implements WorldNativeAccess { +public class PaperweightNativeWorld implements NativeWorld { private static final int UPDATE = 1; private static final int NOTIFY = 2; @@ -49,7 +49,7 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess world; private SideEffectSet sideEffectSet; - public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { + public PaperweightNativeWorld(PaperweightAdapter adapter, WeakReference world) { this.adapter = adapter; this.world = world; } @@ -64,8 +64,8 @@ public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { } @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); + public LevelChunk getChunk(int chunkX, int chunkZ) { + return getWorld().getChunk(chunkX, chunkZ); } @Override diff --git a/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightAdapter.java index dc5de03d42..5367898a78 100644 --- a/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightAdapter.java @@ -39,7 +39,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -437,8 +437,8 @@ public void setBiome(Location location, BiomeType biome) { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { - return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle())); + public NativeWorld createWorldNativeAccess(World world) { + return new PaperweightNativeWorld(this, new WeakReference<>(((CraftWorld) world).getHandle())); } private static net.minecraft.core.Direction adapt(Direction face) { diff --git a/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightNativeWorld.java similarity index 95% rename from worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightWorldNativeAccess.java rename to worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightNativeWorld.java index f12dc48f75..faeaccf605 100644 --- a/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1.21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21/PaperweightNativeWorld.java @@ -21,7 +21,7 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.block.BlockState; @@ -41,7 +41,7 @@ import java.util.Objects; import javax.annotation.Nullable; -public class PaperweightWorldNativeAccess implements WorldNativeAccess { +public class PaperweightNativeWorld implements NativeWorld { private static final int UPDATE = 1; private static final int NOTIFY = 2; @@ -49,7 +49,7 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess world; private SideEffectSet sideEffectSet; - public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { + public PaperweightNativeWorld(PaperweightAdapter adapter, WeakReference world) { this.adapter = adapter; this.world = world; } @@ -64,8 +64,8 @@ public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { } @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); + public LevelChunk getChunk(int chunkX, int chunkZ) { + return getWorld().getChunk(chunkX, chunkZ); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 6f94aa6886..7213a95323 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -32,7 +32,8 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.util.LogManagerCompat; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -91,7 +92,7 @@ public class BukkitWorld extends AbstractWorld { } private final WeakReference worldRef; - private final WorldNativeAccess worldNativeAccess; + private final NativeWorld worldNativeAccess; /** * Construct the object. @@ -482,7 +483,7 @@ public > boolean setBlock(BlockVector3 position, B clearContainerBlockContents(position); if (worldNativeAccess != null) { try { - return worldNativeAccess.setBlock(position, block, sideEffects); + return WNASharedImpl.setBlock(worldNativeAccess, position, block, sideEffects); } catch (Exception e) { if (block instanceof BaseBlock baseBlock && baseBlock.getNbt() != null) { LOGGER.warn("Tried to set a corrupt tile entity at " + position.toString() @@ -515,7 +516,7 @@ public BaseBlock getFullBlock(BlockVector3 position) { public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) { if (worldNativeAccess != null) { - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); + WNASharedImpl.applySideEffects(worldNativeAccess, position, previousType, sideEffectSet); return Sets.intersection( WorldEditPlugin.getInstance().getInternalPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply() diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index f6a42b8bc8..10b08334e5 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -24,7 +24,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -99,12 +99,12 @@ default void tickWatchdog() { BaseBlock getFullBlock(Location location); /** - * Create a {@link WorldNativeAccess} for the given world reference. + * Create a {@link NativeWorld} for the given world reference. * * @param world the world reference * @return the native access object */ - WorldNativeAccess createWorldNativeAccess(World world); + NativeWorld createWorldNativeAccess(World world); /** * Get the state for the given entity. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 26c357fc20..467e55938f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -32,7 +32,6 @@ import com.sk89q.worldedit.extent.NullExtent; import com.sk89q.worldedit.extent.TracingExtent; import com.sk89q.worldedit.extent.buffer.ForgetfulExtentBuffer; -import com.sk89q.worldedit.extent.buffer.internal.BatchingExtent; import com.sk89q.worldedit.extent.cache.LastAccessExtentCache; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBagExtent; @@ -45,6 +44,7 @@ import com.sk89q.worldedit.extent.world.SideEffectExtent; import com.sk89q.worldedit.extent.world.SurvivalModeExtent; import com.sk89q.worldedit.extent.world.WatchdogTickingExtent; +import com.sk89q.worldedit.extent.world.internal.SectionBufferingExtent; import com.sk89q.worldedit.function.GroundFunction; import com.sk89q.worldedit.function.RegionMaskingFilter; import com.sk89q.worldedit.function.biome.BiomeReplace; @@ -87,6 +87,7 @@ import com.sk89q.worldedit.internal.expression.ExpressionTimeoutException; import com.sk89q.worldedit.internal.expression.LocalSlot.Variable; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.MathUtils; import com.sk89q.worldedit.math.Vector2; @@ -116,6 +117,7 @@ import com.sk89q.worldedit.util.eventbus.EventBus; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.AbstractWorld; import com.sk89q.worldedit.world.NullWorld; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeType; @@ -201,7 +203,6 @@ public String getDisplayName() { private @Nullable SideEffectExtent sideEffectExtent; private final SurvivalModeExtent survivalExtent; - private @Nullable BatchingExtent batchingExtent; private @Nullable ChunkBatchingExtent chunkBatchingExtent; private final BlockBagExtent blockBagExtent; @SuppressWarnings("deprecation") @@ -256,6 +257,13 @@ public String getDisplayName() { // These extents are ALWAYS used extent = traceIfNeeded(sideEffectExtent = new SideEffectExtent(world)); + if (world instanceof AbstractWorld internalWorld) { + NativeWorld nativeInterface = internalWorld.getNativeInterface(); + if (nativeInterface != null) { + // Forking extent, will not continue to above extents if enabled + extent = traceIfNeeded(new SectionBufferingExtent(extent, nativeInterface, sideEffectExtent)); + } + } if (watchdog != null) { // Reset watchdog before world placement WatchdogTickingExtent watchdogExtent = new WatchdogTickingExtent(extent, watchdog); @@ -271,7 +279,6 @@ public String getDisplayName() { this.bypassReorderHistory = traceIfNeeded(new DataValidatorExtent(extent, world)); // This extent can be skipped by calling rawSetBlock() - extent = traceIfNeeded(batchingExtent = new BatchingExtent(extent)); @SuppressWarnings("deprecation") MultiStageReorder reorder = new MultiStageReorder(extent, false); extent = traceIfNeeded(reorderExtent = reorder); @@ -621,12 +628,10 @@ public void setBatchingChunks(boolean batchingChunks) { } return; } - assert batchingExtent != null : "same nullness as chunkBatchingExtent"; if (!batchingChunks && isBatchingChunks()) { internalFlushSession(); } chunkBatchingExtent.setEnabled(batchingChunks); - batchingExtent.setEnabled(!batchingChunks); } /** @@ -655,8 +660,6 @@ public void disableBuffering() { setReorderMode(ReorderMode.NONE); if (chunkBatchingExtent != null) { chunkBatchingExtent.setEnabled(false); - assert batchingExtent != null : "same nullness as chunkBatchingExtent"; - batchingExtent.setEnabled(true); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/internal/BatchingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/internal/BatchingExtent.java deleted file mode 100644 index 674e0a5db7..0000000000 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/internal/BatchingExtent.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldedit.extent.buffer.internal; - -import com.google.common.base.Throwables; -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.extent.AbstractBufferingExtent; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.function.operation.Operation; -import com.sk89q.worldedit.function.operation.RunContext; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.util.collection.BlockMap; -import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockStateHolder; - -/** - * An extent that buffers all changes until completed. - */ -public class BatchingExtent extends AbstractBufferingExtent { - - private final BlockMap blockMap = BlockMap.createForBaseBlock(); - private boolean enabled; - - public BatchingExtent(Extent extent) { - this(extent, true); - } - - public BatchingExtent(Extent extent, boolean enabled) { - super(extent); - this.enabled = enabled; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean commitRequired() { - return enabled; - } - - @Override - public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { - if (!enabled) { - return setDelegateBlock(location, block); - } - blockMap.put(location, block.toBaseBlock()); - return true; - } - - @Override - protected BaseBlock getBufferedFullBlock(BlockVector3 position) { - if (!enabled) { - // Early exit if we're not enabled. - return null; - } - return blockMap.get(position); - } - - @Override - protected Operation commitBefore() { - if (!commitRequired()) { - return null; - } - return new Operation() { - - @Override - public Operation resume(RunContext run) throws WorldEditException { - try { - blockMap.forEach((position, block) -> { - try { - getExtent().setBlock(position, block); - } catch (WorldEditException e) { - throw new RuntimeException(e); - } - }); - } catch (RuntimeException e) { - Throwables.throwIfInstanceOf(e.getCause(), WorldEditException.class); - throw e; - } - blockMap.clear(); - return null; - } - - @Override - public void cancel() { - } - }; - } - -} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/internal/SectionBufferingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/internal/SectionBufferingExtent.java new file mode 100644 index 0000000000..23791f206a --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/internal/SectionBufferingExtent.java @@ -0,0 +1,252 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extent.world.internal; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.AbstractBufferingExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.world.SideEffectExtent; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.RunContext; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionMask; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.collection.BlockMap; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.util.Map; +import javax.annotation.Nullable; + +/** + * A special extent that buffers all changes as {@link NativeChunkSection chunk sections}. This allows highly optimized + * block setting, but has a higher likelihood of different semantics compared to traditional block setting. + */ +public class SectionBufferingExtent extends AbstractBufferingExtent { + + private static long getTopSectionKey(BlockVector3 sectionPos) { + return ((long) sectionPos.x() & 0xFF_FF_FF_FFL) | (((long) sectionPos.z() & 0xFF_FF_FF_FFL) << 32); + } + + private record SectionData( + NativeChunkSection section, + ChunkSectionMask modified + ) { + } + + private final Long2ObjectMap> sectionTable = new Long2ObjectLinkedOpenHashMap<>(); + private final BlockMap blockEntityMap = BlockMap.create(); + private final NativeWorld nativeWorld; + private final SideEffectExtent sideEffectExtent; + private boolean enabled; + + public SectionBufferingExtent(Extent extent, NativeWorld nativeWorld, SideEffectExtent sideEffectExtent) { + this(extent, nativeWorld, sideEffectExtent, true); + } + + public SectionBufferingExtent(Extent extent, NativeWorld nativeWorld, SideEffectExtent sideEffectExtent, boolean enabled) { + super(extent); + this.nativeWorld = nativeWorld; + this.sideEffectExtent = sideEffectExtent; + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean commitRequired() { + return enabled; + } + + private BlockVector3 toSectionPos(BlockVector3 position) { + return BlockVector3.at( + position.x() >> 4, + nativeWorld.getSectionIndex(position.y()), + position.z() >> 4 + ); + } + + private @Nullable SectionData getSectionData(BlockVector3 sectionPos) { + Int2ObjectArrayMap sections = sectionTable.get(getTopSectionKey(sectionPos)); + if (sections == null) { + return null; + } + return sections.get(sectionPos.y()); + } + + @Override + public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { + if (!enabled) { + return setDelegateBlock(location, block); + } + if (!sideEffectExtent.isPostEditSimulationEnabled()) { + throw new IllegalStateException("SectionBufferingExtent requires SideEffectExtent to have post-edit simulation enabled"); + } + BlockVector3 sectionPos = toSectionPos(location); + SectionData data = getSectionData(sectionPos); + if (data != null) { + NativeBlockState newState = nativeWorld.getAdapter().toNative(block.toImmutableState()); + NativeBlockState oldState = data.section.getThenSetBlock( + location.x() & 0xF, location.y() & 0xF, location.z() & 0xF, + newState + ); + if (oldState == newState && block.toBaseBlock().getNbt() == null) { + // Optimize out the change if it's the same + return false; + } + } else { + if (block.toBaseBlock().getNbt() == null) { + BaseBlock existingState = getExtent().getFullBlock(location); + if (existingState.toImmutableState() == block && existingState.getNbt() == null) { + // Optimize out the change if it's the same + return false; + } + } + data = new SectionData( + nativeWorld.getChunk(sectionPos.x(), sectionPos.z()).getChunkSection(sectionPos.y()).copy(), + new ChunkSectionMask() + ); + sectionTable.computeIfAbsent(getTopSectionKey(sectionPos), k -> new Int2ObjectArrayMap<>()) + .put(sectionPos.y(), data); + data.section.getThenSetBlock( + location.x() & 0xF, location.y() & 0xF, location.z() & 0xF, + nativeWorld.getAdapter().toNative(block.toImmutableState()) + ); + } + + LinCompoundTag nbt = block.toBaseBlock().getNbt(); + if (nbt != null) { + blockEntityMap.put(location, nbt); + } + + data.modified.set(location.x() & 0xF, location.y() & 0xF, location.z() & 0xF); + + return true; + } + + @Override + protected BaseBlock getBufferedFullBlock(BlockVector3 position) { + if (!enabled) { + // Early exit if we're not enabled. + return null; + } + BlockVector3 sectionPos = toSectionPos(position); + SectionData data = getSectionData(sectionPos); + if (data == null) { + return null; + } + NativeBlockState state = data.section.getBlock(position.x() & 0xF, position.y() & 0xF, position.z() & 0xF); + return nativeWorld.getAdapter().fromNative(state).toBaseBlock(blockEntityMap.get(position)); + } + + @Override + protected Operation commitBefore() { + if (!commitRequired()) { + return null; + } + return new Operation() { + @Override + public Operation resume(RunContext run) { + finishOperation(); + return null; + } + + @Override + public void cancel() { + } + }; + } + + private void finishOperation() { + record SectionDataWithOld( + NativeChunkSection section, + NativeChunkSection oldSection, + ChunkSectionMask modified + ) { + } + + Long2ObjectMap> effectData = new Long2ObjectLinkedOpenHashMap<>(); + + for (Long2ObjectMap.Entry> entry : sectionTable.long2ObjectEntrySet()) { + int chunkX = (int) entry.getLongKey(); + int chunkZ = (int) (entry.getLongKey() >> 32); + NativeChunk chunk = nativeWorld.getChunk(chunkX, chunkZ); + Int2ObjectArrayMap oldSections = new Int2ObjectArrayMap<>(entry.getValue().size()); + for (Int2ObjectArrayMap.Entry sectionEntry : entry.getValue().int2ObjectEntrySet()) { + SectionData data = sectionEntry.getValue(); + NativeChunkSection old = chunk.setChunkSection(sectionEntry.getIntKey(), data.section); + oldSections.put(sectionEntry.getIntKey(), new SectionDataWithOld(data.section, old, data.modified)); + } + effectData.put(entry.getLongKey(), oldSections); + + chunk.updateHeightmaps(); + } + sectionTable.clear(); + + for (Map.Entry entry : blockEntityMap.entrySet()) { + BlockVector3 position = entry.getKey(); + LinCompoundTag tag = entry.getValue(); + NativePosition pos = nativeWorld.getAdapter().newBlockPos(position.x(), position.y(), position.z()); + WNASharedImpl.updateTileEntity(nativeWorld, tag, pos); + } + blockEntityMap.clear(); + + SideEffectSet sideEffectSet = sideEffectExtent.getSideEffectSet(); + SideEffectSet perBlockEffects = sideEffectSet + .with(SideEffect.NETWORK, SideEffect.State.OFF); + for (Long2ObjectMap.Entry> entry : effectData.long2ObjectEntrySet()) { + int chunkX = (int) entry.getLongKey(); + int chunkZ = (int) (entry.getLongKey() >> 32); + NativeChunk chunk = nativeWorld.getChunk(chunkX, chunkZ); + for (Int2ObjectArrayMap.Entry sectionEntry : entry.getValue().int2ObjectEntrySet()) { + int index = sectionEntry.getIntKey(); + SectionDataWithOld data = sectionEntry.getValue(); + if (sideEffectSet.shouldApply(SideEffect.NETWORK)) { + chunk.markSectionChanged(index, data.modified); + } + int sectionBlockY = nativeWorld.getYForSectionIndex(index); + data.modified.forEach((x, y, z) -> { + NativePosition pos = chunk.getWorldPos(x, sectionBlockY + y, z); + WNASharedImpl.applySideEffectsNoLookups( + nativeWorld, chunk, perBlockEffects, pos, + data.oldSection.getBlock(x, y, z), data.section.getBlock(x, y, z) + ); + }); + } + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/collection/ChunkSectionMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/collection/ChunkSectionMask.java new file mode 100644 index 0000000000..956c7d9b64 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/collection/ChunkSectionMask.java @@ -0,0 +1,88 @@ +package com.sk89q.worldedit.internal.util.collection; + +import it.unimi.dsi.fastutil.shorts.AbstractShortCollection; +import it.unimi.dsi.fastutil.shorts.ShortCollection; +import it.unimi.dsi.fastutil.shorts.ShortIterator; + +import java.util.BitSet; + +/** + * A mask for a chunk section. + */ +public class ChunkSectionMask { + public static int index(int x, int y, int z) { + // Each value is 0-15, so 4 bits + // NOTE: This order specifically matches the order used by SectionPos in Minecraft, do not change unless they do + return (x << 8) | (z << 4) | y; + } + + @FunctionalInterface + public interface PosConsumer { + void apply(int x, int y, int z); + } + + private final BitSet mask = new BitSet(4096); + + public boolean isSet(int x, int y, int z) { + return mask.get(index(x, y, z)); + } + + public void set(int x, int y, int z) { + mask.set(index(x, y, z)); + } + + public void clear(int x, int y, int z) { + mask.clear(index(x, y, z)); + } + + public void clear() { + mask.clear(); + } + + public void setAll() { + mask.set(0, 4096); + } + + public void forEach(PosConsumer consumer) { + for (int i = mask.nextSetBit(0); i >= 0; i = mask.nextSetBit(i + 1)) { + consumer.apply((i >> 8) & 0xF, i & 0xF, (i >> 4) & 0xF); + } + } + + public int cardinality() { + return mask.cardinality(); + } + + /** + * {@return a view of this mask as a short collection} Used for updating MC internals. + */ + public ShortCollection asShortCollection() { + return new AbstractShortCollection() { + @Override + public ShortIterator iterator() { + return new ShortIterator() { + private int next = mask.nextSetBit(0); + + @Override public short nextShort() { + if (!hasNext()) { + throw new IllegalStateException(); + } + // Uses the fact that we share the order with SectionPos to efficiently map + short value = (short) next; + next = mask.nextSetBit(next + 1); + return value; + } + + @Override public boolean hasNext() { + return next >= 0; + } + }; + } + + @Override + public int size() { + return mask.cardinality(); + } + }; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeAdapter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeAdapter.java new file mode 100644 index 0000000000..aedb080d0f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeAdapter.java @@ -0,0 +1,14 @@ +package com.sk89q.worldedit.internal.wna; + +import com.sk89q.worldedit.world.block.BlockState; + +/** + * Methods for adapting data to the native types. + */ +public interface NativeAdapter { + NativeBlockState toNative(BlockState state); + + BlockState fromNative(NativeBlockState state); + + NativePosition newBlockPos(int x, int y, int z); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeBlockState.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeBlockState.java new file mode 100644 index 0000000000..5d8938bf8d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeBlockState.java @@ -0,0 +1,14 @@ +package com.sk89q.worldedit.internal.wna; + +/** + * The equivalent of {@link com.sk89q.worldedit.world.block.BlockState}, but in the platform's base. + */ +public interface NativeBlockState { + boolean isSame(NativeBlockState other); + + boolean isSameBlockType(NativeBlockState other); + + boolean hasBlockEntity(); + + NativeBlockState updateFromNeighbourShapes(NativeWorld world, NativePosition position); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunk.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunk.java new file mode 100644 index 0000000000..33c881c8d2 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunk.java @@ -0,0 +1,55 @@ +package com.sk89q.worldedit.internal.wna; + +import com.sk89q.worldedit.internal.util.collection.ChunkSectionMask; + +import javax.annotation.Nullable; + +/** + * Represents a chunk in the world. Made of {@link NativeChunkSection PlatformChunkSections}. + */ +public interface NativeChunk { + NativeWorld getWorld(); + + boolean isTicking(); + + NativePosition getWorldPos(int offsetX, int offsetY, int offsetZ); + + NativeBlockState getBlockState(NativePosition blockPos); + + @Nullable + NativeBlockState setBlockState(NativePosition blockPos, NativeBlockState newState); + + void notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); + + void markBlockChanged(NativePosition pos); + + void markSectionChanged(int index, ChunkSectionMask changed); + + void updateHeightmaps(); + + void updateLightingForSectionAirChange(int index, boolean onlyAir); + + void removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ); + + void initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState); + + /** + * Get the chunk section at the given index. + * + * @param index the index, from 0 to the max height divided by 16 + * @return the chunk section + */ + NativeChunkSection getChunkSection(int index); + + /** + * Replaces a chunk section in the given chunk. This method is also responsible for updating heightmaps + * and creating block entities, to keep consistency with {@link #setBlockState(NativePosition, NativeBlockState)} + * (the method we used to use). This is usually easily done by calling + * {@link WNASharedImpl#postChunkSectionReplacement(NativeChunk, int, NativeChunkSection, NativeChunkSection)}. + * + * @param index the index, from 0 to the max height divided by 16 + * @param section the new chunk section + * @return the old chunk section + */ + NativeChunkSection setChunkSection(int index, NativeChunkSection section); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunkSection.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunkSection.java new file mode 100644 index 0000000000..a8fb2f56fb --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunkSection.java @@ -0,0 +1,68 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.wna; + +import com.sk89q.worldedit.world.registry.BlockMaterial; + +/** + * Represents a 16x16x16 section of a chunk. + * + *

+ * Mapped onto the platform representation of a chunk section. However, unlike the platform representation, this + * interface is not thread-safe, as it is intended to be used in a single-threaded context. + * Internally this uses the platform representation's unsafe methods. + *

+ */ +public interface NativeChunkSection { + /** + * Set a block in the section. + * + * @param i the x-coordinate, 0-15 + * @param j the y-coordinate, 0-15 + * @param k the z-coordinate, 0-15 + * @param blockState the block state + * @return the old block state + */ + NativeBlockState getThenSetBlock(int i, int j, int k, NativeBlockState blockState); + + /** + * Get a block in the section. + * + * @param i the x-coordinate, 0-15 + * @param j the y-coordinate, 0-15 + * @param k the z-coordinate, 0-15 + * @return the block state + */ + NativeBlockState getBlock(int i, int j, int k); + + /** + * Is this section made of only air (specifically, {@link BlockMaterial#isAir()})? + * + * @return true if the section is only air + */ + boolean isOnlyAir(); + + /** + * Copy the section. + * + * @return the copy + */ + NativeChunkSection copy(); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativePosition.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativePosition.java new file mode 100644 index 0000000000..5c2721b775 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativePosition.java @@ -0,0 +1,12 @@ +package com.sk89q.worldedit.internal.wna; + +/** + * The equivalent of {@link com.sk89q.worldedit.math.BlockVector3}, but in the platform's base. + */ +public interface NativePosition { + int x(); + + int y(); + + int z(); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeWorld.java new file mode 100644 index 0000000000..6debad44dc --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeWorld.java @@ -0,0 +1,53 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.wna; + +import com.sk89q.worldedit.math.BlockVector3; +import org.enginehub.linbus.tree.LinCompoundTag; + +/** + * The equivalent of {@link com.sk89q.worldedit.world.World}, but in the platform's base. + */ +public interface NativeWorld { + /** + * Hacky way to get the adapter. Ideally would be part of an internal platform API or something. + * + * @return the adapter + */ + NativeAdapter getAdapter(); + + int getSectionIndex(int y); + + int getYForSectionIndex(int index); + + NativeChunk getChunk(int chunkX, int chunkZ); + + void updateLightingForBlock(NativePosition position); + + boolean updateTileEntity(NativePosition position, LinCompoundTag tag); + + void notifyNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); + + void updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); + + void updateNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit); + + void onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WNASharedImpl.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WNASharedImpl.java new file mode 100644 index 0000000000..edee67865d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WNASharedImpl.java @@ -0,0 +1,213 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.internal.wna; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTagType; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Shared implementation bits of {@link NativeWorld}. Platforms may call into this to use common code. + */ +public class WNASharedImpl { + public static > boolean setBlock( + NativeAdapter adapter, NativeWorld nativeWorld, BlockVector3 position, B block, SideEffectSet sideEffects + ) throws WorldEditException { + checkNotNull(position); + checkNotNull(block); + + int x = position.x(); + int y = position.y(); + int z = position.z(); + + // First set the block + NativeChunk chunk = nativeWorld.getChunk(x >> 4, z >> 4); + NativePosition pos = adapter.newBlockPos(x, y, z); + NativeBlockState old = chunk.getBlockState(pos); + NativeBlockState newState = adapter.toNative(block.toImmutableState()); + // change block prior to placing if it should be fixed + if (sideEffects.shouldApply(SideEffect.VALIDATION)) { + newState = newState.updateFromNeighbourShapes(nativeWorld, pos); + } + NativeBlockState lastValue = chunk.setBlockState(pos, newState); + boolean successful = lastValue != null; + + // Create the TileEntity + if (successful || old == newState) { + if (block instanceof BaseBlock baseBlock) { + successful |= updateTileEntity(nativeWorld, baseBlock.getNbt(), pos); + } + } + + if (successful) { + if (sideEffects.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { + nativeWorld.updateLightingForBlock(pos); + } + markAndNotifyBlock(nativeWorld, pos, chunk, old, newState, sideEffects); + } + + return successful; + } + + public static boolean updateTileEntity(NativeWorld nativeWorld, LinCompoundTag tag, NativePosition pos) { + if (tag == null) { + return false; + } + LinCompoundTag.Builder tagBuilder = tag.toBuilder() + .putInt("x", pos.x()) + .putInt("y", pos.y()) + .putInt("z", pos.z()); + String nbtId = extractNbtId(tag); + if (!nbtId.isBlank()) { + tagBuilder.putString("id", nbtId); + } + tag = tagBuilder.build(); + + // update if TE changed as well + return nativeWorld.updateTileEntity(pos, tag); + } + + public static String extractNbtId(LinCompoundTag tag) { + LinStringTag idTag = tag.findTag("id", LinTagType.stringTag()); + return idTag != null ? idTag.value() : ""; + } + + public static void applySideEffects( + NativeAdapter adapter, NativeWorld nativeWorld, SideEffectSet sideEffectSet, BlockVector3 position, + BlockState previousType + ) { + NativePosition pos = adapter.newBlockPos(position.x(), position.y(), position.z()); + NativeChunk chunk = nativeWorld.getChunk(position.x() >> 4, position.z() >> 4); + NativeBlockState oldData = adapter.toNative(previousType); + NativeBlockState newData = chunk.getBlockState(pos); + + applySideEffectsNoLookups(nativeWorld, chunk, sideEffectSet, pos, oldData, newData); + } + + public static void applySideEffectsNoLookups( + NativeWorld nativeWorld, NativeChunk chunk, SideEffectSet sideEffectSet, NativePosition pos, + NativeBlockState oldData, NativeBlockState newData + ) { + if (sideEffectSet.shouldApply(SideEffect.UPDATE)) { + nativeWorld.updateBlock(pos, oldData, newData); + } + + if (sideEffectSet.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { + nativeWorld.updateLightingForBlock(pos); + } + + markAndNotifyBlock(nativeWorld, pos, chunk, oldData, newData, sideEffectSet); + } + + /** + * This is a heavily modified function stripped from MC to apply WorldEdit-modifications. + * + *

+ * See Forge's World.markAndNotifyBlock + *

+ */ + public static void markAndNotifyBlock( + NativeWorld wna, NativePosition pos, NativeChunk chunk, NativeBlockState oldState, NativeBlockState newState, + SideEffectSet sideEffectSet + ) { + // Removed redundant branches + + if (chunk.isTicking()) { + if (sideEffectSet.shouldApply(SideEffect.ENTITY_AI)) { + chunk.notifyBlockUpdate(pos, oldState, newState); + } else if (sideEffectSet.shouldApply(SideEffect.NETWORK)) { + // If we want to skip entity AI, just mark the block for sending + chunk.markBlockChanged(pos); + } + } + + if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { + wna.notifyNeighbors(pos, oldState, newState); + } + + // Make connection updates optional + if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { + wna.updateNeighbors(pos, oldState, newState, 512); + } + + // Seems used only for PoI updates + if (sideEffectSet.shouldApply(SideEffect.POI_UPDATE)) { + wna.onBlockStateChange(pos, oldState, newState); + } + } + + /** + * After a chunk section replacement, this function can be called to update the heightmaps, block entities, etc. + * to keep consistency with {@link NativeChunk#setBlockState(NativePosition, NativeBlockState)}. Doing this allows + * skipping redundant updates caused by multiple set calls, and filtering out unwanted side effects. + * + * @param chunk the chunk + * @param index the replaced section index + * @param oldSection the old section + * @param newSection the new section + */ + public static void postChunkSectionReplacement( + NativeChunk chunk, int index, NativeChunkSection oldSection, NativeChunkSection newSection + ) { + for (int secX = 0; secX < 16; secX++) { + for (int secY = 0; secY < 16; secY++) { + for (int secZ = 0; secZ < 16; secZ++) { + NativeBlockState oldState = oldSection.getBlock(secX, secY, secZ); + NativeBlockState newState = newSection.getBlock(secX, secY, secZ); + if (oldState.isSame(newState)) { + continue; + } + int chunkY = chunk.getWorld().getYForSectionIndex(index) + secY; + // We skip heightmaps, they're optimized at a higher level to a single call. + + // We skip onRemove here, will call in UPDATE side effect if necessary. + + if (oldState.isSameBlockType(newState) && oldState.hasBlockEntity()) { + chunk.removeSectionBlockEntity(secX, chunkY, secZ); + } + + // We skip onPlace here, will call in UPDATE side effect if necessary. + + if (newState.hasBlockEntity()) { + chunk.initializeBlockEntity(secX, chunkY, secZ, newState); + } + } + } + } + + boolean wasOnlyAir = oldSection.isOnlyAir(); + boolean onlyAir = newSection.isOnlyAir(); + if (wasOnlyAir != onlyAir) { + chunk.updateLightingForSectionAirChange(index, onlyAir); + } + } + + private WNASharedImpl() { + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java deleted file mode 100644 index 52d64cb130..0000000000 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldedit.internal.wna; - -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; -import com.sk89q.worldedit.world.block.BlockStateHolder; -import org.enginehub.linbus.tree.LinCompoundTag; - -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Natively access and perform operations on the world. - * - * @param the native chunk type - * @param the native block state type - * @param the native position type - */ -public interface WorldNativeAccess { - - default > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { - checkNotNull(position); - checkNotNull(block); - setCurrentSideEffectSet(sideEffects); - - int x = position.x(); - int y = position.y(); - int z = position.z(); - - // First set the block - NC chunk = getChunk(x >> 4, z >> 4); - NP pos = getPosition(x, y, z); - NBS old = getBlockState(chunk, pos); - NBS newState = toNative(block.toImmutableState()); - // change block prior to placing if it should be fixed - if (sideEffects.shouldApply(SideEffect.VALIDATION)) { - newState = getValidBlockForPosition(newState, pos); - } - NBS lastValue = setBlockState(chunk, pos, newState); - boolean successful = lastValue != null; - - // Create the TileEntity - if (successful || old == newState) { - if (block instanceof BaseBlock baseBlock) { - LinCompoundTag tag = baseBlock.getNbt(); - if (tag != null) { - LinCompoundTag.Builder tagBuilder = tag.toBuilder() - .putInt("x", position.x()) - .putInt("y", position.y()) - .putInt("z", position.z()); - if (!baseBlock.getNbtId().isBlank()) { - tagBuilder.putString("id", baseBlock.getNbtId()); - } - tag = tagBuilder.build(); - - // update if TE changed as well - successful = updateTileEntity(pos, tag); - } - } - } - - if (successful) { - if (sideEffects.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { - updateLightingForBlock(pos); - } - markAndNotifyBlock(pos, chunk, old, newState, sideEffects); - } - - return successful; - } - - default void applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - setCurrentSideEffectSet(sideEffectSet); - NP pos = getPosition(position.x(), position.y(), position.z()); - NC chunk = getChunk(position.x() >> 4, position.z() >> 4); - NBS oldData = toNative(previousType); - NBS newData = getBlockState(chunk, pos); - - if (sideEffectSet.shouldApply(SideEffect.UPDATE)) { - updateBlock(pos, oldData, newData); - } - - if (sideEffectSet.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { - updateLightingForBlock(pos); - } - - markAndNotifyBlock(pos, chunk, oldData, newData, sideEffectSet); - } - - // state-keeping functions for WNA - // may be thread-unsafe, as this is single-threaded code - - /** - * Receive the current side-effect set from the high level call. - * - *

- * This allows the implementation to branch on the side-effects internally. - *

- * - * @param sideEffectSet the set of side-effects - */ - default void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - } - - // access functions - - NC getChunk(int x, int z); - - NBS toNative(BlockState state); - - NBS getBlockState(NC chunk, NP position); - - @Nullable - NBS setBlockState(NC chunk, NP position, NBS state); - - NBS getValidBlockForPosition(NBS block, NP position); - - NP getPosition(int x, int y, int z); - - void updateLightingForBlock(NP position); - - boolean updateTileEntity(NP position, LinCompoundTag tag); - - void notifyBlockUpdate(NC chunk, NP position, NBS oldState, NBS newState); - - boolean isChunkTicking(NC chunk); - - void markBlockChanged(NC chunk, NP position); - - void notifyNeighbors(NP pos, NBS oldState, NBS newState); - - default void updateBlock(NP pos, NBS oldState, NBS newState) { - } - - void updateNeighbors(NP pos, NBS oldState, NBS newState, int recursionLimit); - - void onBlockStateChange(NP pos, NBS oldState, NBS newState); - - /** - * This is a heavily modified function stripped from MC to apply WorldEdit-modifications. - * - *

- * See Forge's World.markAndNotifyBlock - *

- */ - default void markAndNotifyBlock(NP pos, NC chunk, NBS oldState, NBS newState, SideEffectSet sideEffectSet) { - NBS blockState1 = getBlockState(chunk, pos); - if (blockState1 != newState) { - return; - } - - // Remove redundant branches - if (isChunkTicking(chunk)) { - if (sideEffectSet.shouldApply(SideEffect.ENTITY_AI)) { - notifyBlockUpdate(chunk, pos, oldState, newState); - } else if (sideEffectSet.shouldApply(SideEffect.NETWORK)) { - // If we want to skip entity AI, just mark the block for sending - markBlockChanged(chunk, pos); - } - } - - if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { - notifyNeighbors(pos, oldState, newState); - } - - // Make connection updates optional - if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { - updateNeighbors(pos, oldState, newState, 512); - } - - // Seems used only for PoI updates - if (sideEffectSet.shouldApply(SideEffect.POI_UPDATE)) { - onBlockStateChange(pos, oldState, blockState1); - } - } - -} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java index 182a404487..c7ea6d325c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java @@ -21,7 +21,8 @@ * "WNA", or WorldEdit Native Access. * *

- * Contains internal helper functions for sharing code between platforms. + * Contains internal helper functions for sharing code between platforms. "Native*" interfaces are wrapped around or + * mixed in to the native structures. *

*/ package com.sk89q.worldedit.internal.wna; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java index 14c607fa4b..07cb56177d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java @@ -26,6 +26,7 @@ import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -49,6 +50,13 @@ public abstract class AbstractWorld implements World { private final PriorityQueue effectQueue = new PriorityQueue<>(); private int taskId = -1; + /** + * {@return the native interface to the world} Internal use only. + */ + public @Nullable NativeWorld getNativeInterface() { + return null; + } + @Override public boolean useItem(BlockVector3 position, BaseItem item, Direction face) { return false; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java index 609f7c7e26..a870b3dda6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java @@ -21,12 +21,11 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.concurrency.LazyReference; import org.enginehub.linbus.format.snbt.LinStringIO; import org.enginehub.linbus.tree.LinCompoundTag; -import org.enginehub.linbus.tree.LinStringTag; -import org.enginehub.linbus.tree.LinTagType; import java.util.Map; import java.util.Objects; @@ -120,8 +119,7 @@ public String getNbtId() { if (nbtData == null) { return ""; } - LinStringTag idTag = nbtData.getValue().findTag("id", LinTagType.stringTag()); - return idTag != null ? idTag.value() : ""; + return WNASharedImpl.extractNbtId(nbtData.getValue()); } @Nullable diff --git a/worldedit-fabric/build.gradle.kts b/worldedit-fabric/build.gradle.kts index a9fa0be5a0..3a738c1e96 100644 --- a/worldedit-fabric/build.gradle.kts +++ b/worldedit-fabric/build.gradle.kts @@ -35,7 +35,10 @@ dependencies { "api"(project(":worldedit-core")) "minecraft"(libs.fabric.minecraft) - "mappings"(project.the().officialMojangMappings()) + "mappings"(project.the().layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${libs.versions.parchment.minecraft.get()}:${libs.versions.parchment.mappings.get()}@zip") + }) "modImplementation"(libs.fabric.loader) diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java index 8448bde161..733c4e3936 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java @@ -25,6 +25,9 @@ import com.sk89q.worldedit.fabric.internal.FabricTransmogrifier; import com.sk89q.worldedit.fabric.internal.NBTConverter; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.registry.state.Property; @@ -68,6 +71,27 @@ public final class FabricAdapter { + private static final NativeAdapter NATIVE_ADAPTER = new NativeAdapter() { + @Override + public NativeBlockState toNative(BlockState state) { + return (NativeBlockState) FabricAdapter.adapt(state); + } + + @Override + public BlockState fromNative(NativeBlockState state) { + return FabricAdapter.adapt((net.minecraft.world.level.block.state.BlockState) state); + } + + @Override + public NativePosition newBlockPos(int x, int y, int z) { + return (NativePosition) new BlockPos(x, y, z); + } + }; + + public static NativeAdapter asNativeAdapter() { + return NATIVE_ADAPTER; + } + private FabricAdapter() { } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java index 6a49a311e3..7ce1caa3db 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java @@ -37,11 +37,12 @@ import com.sk89q.worldedit.fabric.internal.ExtendedMinecraftServer; import com.sk89q.worldedit.fabric.internal.FabricEntity; import com.sk89q.worldedit.fabric.internal.FabricServerLevelDelegateProxy; -import com.sk89q.worldedit.fabric.internal.FabricWorldNativeAccess; import com.sk89q.worldedit.fabric.internal.NBTConverter; import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -147,7 +148,6 @@ private static ResourceLocation getDimensionRegistryKey(Level world) { } private final WeakReference worldRef; - private final FabricWorldNativeAccess worldNativeAccess; /** * Construct a new world. @@ -157,7 +157,11 @@ private static ResourceLocation getDimensionRegistryKey(Level world) { FabricWorld(Level world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.worldNativeAccess = new FabricWorldNativeAccess(worldRef); + } + + @Override + public NativeWorld getNativeInterface() { + return (NativeWorld) getWorld(); } /** @@ -197,12 +201,12 @@ public Path getStoragePath() { @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { clearContainerBlockContents(position); - return worldNativeAccess.setBlock(position, block, sideEffects); + return WNASharedImpl.setBlock(FabricAdapter.asNativeAdapter(), getNativeInterface(), position, block, sideEffects); } @Override public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); + WNASharedImpl.applySideEffects(FabricAdapter.asNativeAdapter(), getNativeInterface(), sideEffectSet, position, previousType); return Sets.intersection(FabricWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java deleted file mode 100644 index 55695ac166..0000000000 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldedit.fabric.internal; - -import com.sk89q.worldedit.fabric.FabricAdapter; -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.LevelChunk; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.annotation.Nullable; - -public class FabricWorldNativeAccess implements WorldNativeAccess { - private static final int UPDATE = 1; - private static final int NOTIFY = 2; - - private final WeakReference world; - private SideEffectSet sideEffectSet; - - public FabricWorldNativeAccess(WeakReference world) { - this.world = world; - } - - private Level getWorld() { - return Objects.requireNonNull(world.get(), "The reference to the world was lost"); - } - - @Override - public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - this.sideEffectSet = sideEffectSet; - } - - @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); - } - - @Override - public BlockState toNative(com.sk89q.worldedit.world.block.BlockState state) { - int stateId = BlockStateIdAccess.getBlockStateId(state); - return BlockStateIdAccess.isValidInternalId(stateId) - ? Block.stateById(stateId) - : FabricAdapter.adapt(state); - } - - @Override - public BlockState getBlockState(LevelChunk chunk, BlockPos position) { - return chunk.getBlockState(position); - } - - @Nullable - @Override - public BlockState setBlockState(LevelChunk chunk, BlockPos position, BlockState state) { - if (chunk instanceof ExtendedChunk) { - return ((ExtendedChunk) chunk).setBlockState( - position, state, false, sideEffectSet.shouldApply(SideEffect.UPDATE) - ); - } - return chunk.setBlockState(position, state, false); - } - - @Override - public BlockState getValidBlockForPosition(BlockState block, BlockPos position) { - return Block.updateFromNeighbourShapes(block, getWorld(), position); - } - - @Override - public BlockPos getPosition(int x, int y, int z) { - return new BlockPos(x, y, z); - } - - @Override - public void updateLightingForBlock(BlockPos position) { - getWorld().getChunkSource().getLightEngine().checkBlock(position); - } - - @Override - public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { - CompoundTag nativeTag = NBTConverter.toNative(tag); - Level level = getWorld(); - BlockEntity tileEntity = level.getChunkAt(position).getBlockEntity(position); - if (tileEntity == null) { - return false; - } - tileEntity.loadWithComponents(nativeTag, level.registryAccess()); - tileEntity.setChanged(); - return true; - } - - @Override - public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, BlockState oldState, BlockState newState) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); - } - } - - @Override - public boolean isChunkTicking(LevelChunk chunk) { - return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); - } - - @Override - public void markBlockChanged(LevelChunk chunk, BlockPos position) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - ((ServerChunkCache) getWorld().getChunkSource()).blockChanged(position); - } - } - - @Override - public void notifyNeighbors(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().blockUpdated(pos, oldState.getBlock()); - if (newState.hasAnalogOutputSignal()) { - getWorld().updateNeighbourForOutputSignal(pos, newState.getBlock()); - } - } - - @Override - public void updateBlock(BlockPos pos, BlockState oldState, BlockState newState) { - Level world = getWorld(); - newState.onPlace(world, pos, oldState, false); - } - - @Override - public void updateNeighbors(BlockPos pos, BlockState oldState, BlockState newState, int recursionLimit) { - Level world = getWorld(); - oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - } - - @Override - public void onBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().onBlockStateChange(pos, oldState, newState); - } -} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeBlockState.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeBlockState.java new file mode 100644 index 0000000000..4e8713d1e9 --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeBlockState.java @@ -0,0 +1,44 @@ +package com.sk89q.worldedit.fabric.mixin; + +import com.mojang.serialization.MapCodec; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockState.class) +@Implements(@Interface(iface = NativeBlockState.class, prefix = "nbs$")) +public abstract class MixinNativeBlockState extends BlockBehaviour.BlockStateBase { + protected MixinNativeBlockState(Block owner, Reference2ObjectArrayMap, Comparable> values, MapCodec propertiesCodec) { + super(owner, values, propertiesCodec); + } + + public boolean nbs$isSame(NativeBlockState other) { + return this == other; + } + + public boolean nbs$isSameBlockType(NativeBlockState other) { + return this.getBlock() == ((BlockState) other).getBlock(); + } + + public boolean nbs$hasBlockEntity() { + return super.hasBlockEntity(); + } + + public NativeBlockState nbs$updateFromNeighbourShapes( + NativeWorld world, NativePosition position + ) { + return (NativeBlockState) Block.updateFromNeighbourShapes( + (BlockState) (Object) this, (LevelAccessor) world, (BlockPos) position + ); + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunk.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunk.java new file mode 100644 index 0000000000..1de81a850a --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunk.java @@ -0,0 +1,175 @@ +package com.sk89q.worldedit.fabric.mixin; + +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionMask; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; + +@Mixin(LevelChunk.class) +@Implements(@Interface(iface = NativeChunk.class, prefix = "nc$")) +public abstract class MixinNativeChunk extends ChunkAccess { + @Unique + private static final Set HEIGHTMAPS = EnumSet.of( + Heightmap.Types.WORLD_SURFACE, + Heightmap.Types.OCEAN_FLOOR, + Heightmap.Types.MOTION_BLOCKING, + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES + ); + + public MixinNativeChunk(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, long inhabitedTime, @org.jetbrains.annotations.Nullable LevelChunkSection[] sections, @org.jetbrains.annotations.Nullable BlendingData blendingData) { + super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData); + } + + @Shadow + public abstract FullChunkStatus getFullStatus(); + + @Shadow + public abstract BlockState getBlockState(BlockPos pos); + + @Shadow + public abstract @Nullable BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); + + @Shadow + public abstract Level getLevel(); + + @Shadow + public abstract void removeBlockEntity(BlockPos pos); + + @Shadow + public abstract @Nullable BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType); + + @Shadow + public abstract void addAndRegisterBlockEntity(BlockEntity blockEntity); + + @Shadow + protected abstract void updateBlockEntityTicker(T blockEntity); + + public NativeWorld nc$getWorld() { + return (NativeWorld) getLevel(); + } + + public boolean nc$isTicking() { + return getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + public NativePosition nc$getWorldPos(int offsetX, int offsetY, int offsetZ) { + return (NativePosition) getPos().getBlockAt(offsetX, offsetY, offsetZ); + } + + public NativeBlockState nc$getBlockState(NativePosition blockPos) { + return (NativeBlockState) getBlockState((BlockPos) blockPos); + } + + public @Nullable NativeBlockState nc$setBlockState(NativePosition blockPos, NativeBlockState newState) { + return (NativeBlockState) setBlockState((BlockPos) blockPos, (BlockState) newState, false); + } + + public void nc$notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + if (getSections()[getLevel().getSectionIndex(pos.y())] != null) { + getLevel().sendBlockUpdated( + (BlockPos) pos, (BlockState) oldState, (BlockState) newState, + Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS + ); + } + } + + public void nc$markBlockChanged(NativePosition pos) { + if (getSections()[getLevel().getSectionIndex(pos.y())] != null) { + ((ServerChunkCache) getLevel().getChunkSource()).blockChanged((BlockPos) pos); + } + } + + public void nc$markSectionChanged(int index, ChunkSectionMask changed) { + ChunkHolder holder = ((ServerChunkCache) getLevel().getChunkSource()) + .getVisibleChunkIfPresent(getPos().toLong()); + if (holder != null) { + if (holder.changedBlocksPerSection[index] == null) { + holder.hasChangedSections = true; + holder.changedBlocksPerSection[index] = new ShortOpenHashSet( + Math.max(ShortOpenHashSet.DEFAULT_INITIAL_SIZE, changed.cardinality()) + ); + } + + holder.changedBlocksPerSection[index].addAll(changed.asShortCollection()); + } + } + + public void nc$updateHeightmaps() { + Heightmap.primeHeightmaps(this, HEIGHTMAPS); + } + + public void nc$updateLightingForSectionAirChange(int index, boolean onlyAir) { + getLevel().getLightEngine().updateSectionStatus( + SectionPos.of(getPos(), getLevel().getSectionYFromSectionIndex(index)), + onlyAir + ); + } + + public void nc$removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ) { + removeBlockEntity(getPos().getBlockAt(chunkX, chunkY, chunkZ)); + } + + @SuppressWarnings("deprecation") + public void nc$initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState) { + BlockPos pos = getPos().getBlockAt(chunkX, chunkY, chunkZ); + BlockEntity blockEntity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + BlockState nativeState = (BlockState) newState; + if (blockEntity == null) { + blockEntity = ((EntityBlock) nativeState.getBlock()).newBlockEntity(pos, nativeState); + if (blockEntity != null) { + this.addAndRegisterBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(nativeState); + this.updateBlockEntityTicker(blockEntity); + } + } + + public NativeChunkSection nc$getChunkSection(int index) { + return (NativeChunkSection) getSection(index); + } + + public NativeChunkSection nc$setChunkSection(int index, NativeChunkSection section) { + Preconditions.checkPositionIndex(index, getSectionsCount()); + LevelChunkSection[] chunkSections = getSections(); + var oldSection = (NativeChunkSection) chunkSections[index]; + chunkSections[index] = (LevelChunkSection) section; + WNASharedImpl.postChunkSectionReplacement((NativeChunk) this, index, oldSection, section); + this.unsaved = true; + return oldSection; + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunkSection.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunkSection.java new file mode 100644 index 0000000000..0b92b760b3 --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunkSection.java @@ -0,0 +1,102 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.fabric.mixin; + +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(LevelChunkSection.class) +@Implements(@Interface(iface = NativeChunkSection.class, prefix = "ncs$")) +public abstract class MixinNativeChunkSection { + @Shadow + public abstract net.minecraft.world.level.block.state.BlockState setBlockState( + int x, int y, int z, net.minecraft.world.level.block.state.BlockState state, boolean lock + ); + + @Shadow + public abstract net.minecraft.world.level.block.state.BlockState getBlockState( + int x, int y, int z + ); + + @Final + @Mutable + @Shadow + private PalettedContainer states; + + @Shadow + private PalettedContainerRO> biomes; + + @Shadow + public abstract boolean hasOnlyAir(); + + public boolean ncs$isOnlyAir() { + return hasOnlyAir(); + } + + public NativeBlockState ncs$getThenSetBlock(int i, int j, int k, NativeBlockState blockState) { + BlockState nativeState = (BlockState) blockState; + if (ncs$isOnlyAir() && nativeState.isAir()) { + return blockState; + } + return (NativeBlockState) setBlockState(i, j, k, nativeState, false); + } + + public NativeBlockState ncs$getBlock(int i, int j, int k) { + return (NativeBlockState) getBlockState(i, j, k); + } + + public NativeChunkSection ncs$copy() { + return (NativeChunkSection) new LevelChunkSection( + copyPalettedContainer(states), copyPalettedContainer((PalettedContainer>) biomes) + ); + } + + /** + * Mojang is bad at writing code and their copy method doesn't replace the resize handler so it resizes the wrong + * thing. Reinitialize their data for them. + * + * @param container the container to copy + * @return the copied container + */ + private static PalettedContainer copyPalettedContainer(PalettedContainer container) { + // First init by directly moving the fields over + PalettedContainer copy = new PalettedContainer<>(container.registry, container.strategy, container.data); + + // Force re-create by using `null`, to make an actual copy of the data with the new resize handler. + PalettedContainer.Data data = copy.data; + PalettedContainer.Data data2 = copy.createOrReuseData(null, data.storage().getSize()); + data2.copyFrom(data.palette(), data.storage()); + copy.data = data2; + + return copy; + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativePosition.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativePosition.java new file mode 100644 index 0000000000..d4e788caca --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativePosition.java @@ -0,0 +1,28 @@ +package com.sk89q.worldedit.fabric.mixin; + +import com.sk89q.worldedit.internal.wna.NativePosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockPos.class) +@Implements(@Interface(iface = NativePosition.class, prefix = "nc$")) +public abstract class MixinNativePosition extends Vec3i { + public MixinNativePosition(int x, int y, int z) { + super(x, y, z); + } + + public int nc$x() { + return getX(); + } + + public int nc$y() { + return getY(); + } + + public int nc$z() { + return getZ(); + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeWorld.java new file mode 100644 index 0000000000..cdf18abe3a --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeWorld.java @@ -0,0 +1,107 @@ +package com.sk89q.worldedit.fabric.mixin; + +import com.sk89q.worldedit.fabric.FabricAdapter; +import com.sk89q.worldedit.fabric.internal.NBTConverter; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.storage.WritableLevelData; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.function.Supplier; + +@Mixin(ServerLevel.class) +@Implements(@Interface(iface = NativeWorld.class, prefix = "nw$")) +public abstract class MixinNativeWorld extends Level { + protected MixinNativeWorld(WritableLevelData levelData, ResourceKey dimension, RegistryAccess registryAccess, Holder dimensionTypeRegistration, Supplier profiler, boolean isClientSide, boolean isDebug, long biomeZoomSeed, int maxChainedNeighborUpdates) { + super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates); + } + + public NativeAdapter nw$getAdapter() { + return FabricAdapter.asNativeAdapter(); + } + + public int nw$getSectionIndex(int y) { + return super.getSectionIndex(y); + } + + public int nw$getYForSectionIndex(int index) { + return SectionPos.sectionToBlockCoord(super.getSectionYFromSectionIndex(index)); + } + + public NativeChunk nw$getChunk(int chunkX, int chunkZ) { + return (NativeChunk) getChunk(chunkX, chunkZ); + } + + public void nw$updateLightingForBlock(NativePosition position) { + ProfilerFiller profilerFiller = getProfiler(); + profilerFiller.push("updateSkyLightSources"); + getChunk(position.x() >> 4, position.z() >> 4).getSkyLightSources() + .update(this, position.x() & 0xF, position.y(), position.z() & 0xF); + profilerFiller.popPush("queueCheckLight"); + getChunkSource().getLightEngine().checkBlock((BlockPos) position); + profilerFiller.pop(); + } + + public boolean nw$updateTileEntity(NativePosition position, LinCompoundTag tag) { + CompoundTag nativeTag = NBTConverter.toNative(tag); + BlockPos nativePos = (BlockPos) position; + BlockEntity tileEntity = getChunkAt(nativePos).getBlockEntity(nativePos); + if (tileEntity == null) { + return false; + } + tileEntity.loadWithComponents(nativeTag, registryAccess()); + tileEntity.setChanged(); + return true; + } + + public void nw$notifyNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + BlockPos nativePos = (BlockPos) pos; + blockUpdated(nativePos, ((BlockState) oldState).getBlock()); + BlockState nativeNewState = (BlockState) newState; + if (nativeNewState.hasAnalogOutputSignal()) { + updateNeighbourForOutputSignal(nativePos, nativeNewState.getBlock()); + } + } + + public void nw$updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + BlockPos nativePos = (BlockPos) pos; + BlockState nativeOldState = (BlockState) oldState; + BlockState nativeNewState = (BlockState) newState; + nativeOldState.onRemove(this, nativePos, nativeNewState, false); + nativeNewState.onPlace(this, nativePos, nativeOldState, false); + } + + public void nw$updateNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit + ) { + BlockPos nativePos = (BlockPos) pos; + BlockState nativeOldState = (BlockState) oldState; + BlockState nativeNewState = (BlockState) newState; + nativeOldState.updateIndirectNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateIndirectNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + } + + public void nw$onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + onBlockStateChange((BlockPos) pos, (BlockState) oldState, (BlockState) newState); + } +} diff --git a/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json b/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json index 232b19e65f..b8e6542f12 100644 --- a/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json +++ b/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json @@ -5,11 +5,14 @@ "mixins": [ "MixinLevelChunkSetBlockHook", "MixinMinecraftServer", + "MixinNativeBlockState", + "MixinNativeChunk", + "MixinNativeChunkSection", + "MixinNativePosition", + "MixinNativeWorld", "MixinServerGamePacketListenerImpl" ], "plugin": "com.sk89q.worldedit.fabric.internal.MixinConfigPlugin", - "server": [ - ], "injectors": { "defaultRequire": 1 } diff --git a/worldedit-fabric/src/main/resources/worldedit.accesswidener b/worldedit-fabric/src/main/resources/worldedit.accesswidener index 88f77ce207..259df124d8 100644 --- a/worldedit-fabric/src/main/resources/worldedit.accesswidener +++ b/worldedit-fabric/src/main/resources/worldedit.accesswidener @@ -13,3 +13,18 @@ accessible field net/minecraft/world/level/storage/PrimaryLevelData worldOptions mutable field net/minecraft/world/level/storage/PrimaryLevelData worldOptions Lnet/minecraft/world/level/levelgen/WorldOptions; accessible method net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket (Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V + +# Ability to mark sections changed +accessible method net/minecraft/server/level/ServerChunkCache getVisibleChunkIfPresent (J)Lnet/minecraft/server/level/ChunkHolder; +accessible field net/minecraft/server/level/ChunkHolder changedBlocksPerSection [Lit/unimi/dsi/fastutil/shorts/ShortSet; +accessible field net/minecraft/server/level/ChunkHolder hasChangedSections Z + +# Ability to make a copy of the container +accessible method net/minecraft/world/level/chunk/PalettedContainer (Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Data;)V +accessible field net/minecraft/world/level/chunk/PalettedContainer registry Lnet/minecraft/core/IdMap; +accessible field net/minecraft/world/level/chunk/PalettedContainer strategy Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy; +accessible class net/minecraft/world/level/chunk/PalettedContainer$Data +accessible field net/minecraft/world/level/chunk/PalettedContainer data Lnet/minecraft/world/level/chunk/PalettedContainer$Data; + +# Ability to recreate the data using Mojang's logic +accessible method net/minecraft/world/level/chunk/PalettedContainer createOrReuseData (Lnet/minecraft/world/level/chunk/PalettedContainer$Data;I)Lnet/minecraft/world/level/chunk/PalettedContainer$Data; diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java index da7ae7e880..40cfc2a172 100644 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java @@ -37,13 +37,14 @@ import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.neoforge.internal.NBTConverter; import com.sk89q.worldedit.neoforge.internal.NeoForgeEntity; import com.sk89q.worldedit.neoforge.internal.NeoForgeServerLevelDelegateProxy; -import com.sk89q.worldedit.neoforge.internal.NeoForgeWorldNativeAccess; +import com.sk89q.worldedit.neoforge.internal.NeoForgeNativeWorld; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; @@ -143,7 +144,7 @@ private static ResourceLocation getDimensionRegistryKey(ServerLevel world) { } private final WeakReference worldRef; - private final NeoForgeWorldNativeAccess nativeAccess; + private final NeoForgeNativeWorld nativeAccess; /** * Construct a new world. @@ -153,7 +154,7 @@ private static ResourceLocation getDimensionRegistryKey(ServerLevel world) { NeoForgeWorld(ServerLevel world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.nativeAccess = new NeoForgeWorldNativeAccess(worldRef); + this.nativeAccess = new NeoForgeNativeWorld(worldRef); } /** @@ -190,12 +191,12 @@ public Path getStoragePath() { @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { clearContainerBlockContents(position); - return nativeAccess.setBlock(position, block, sideEffects); + return WNASharedImpl.setBlock(nativeAccess, position, block, sideEffects); } @Override public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - nativeAccess.applySideEffects(position, previousType, sideEffectSet); + WNASharedImpl.applySideEffects(nativeAccess, position, previousType, sideEffectSet); return Sets.intersection(NeoForgeWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); } diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeWorldNativeAccess.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeNativeWorld.java similarity index 95% rename from worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeWorldNativeAccess.java rename to worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeNativeWorld.java index c1e98765bf..11bbcaecde 100644 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeWorldNativeAccess.java +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeNativeWorld.java @@ -20,7 +20,7 @@ package com.sk89q.worldedit.neoforge.internal; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.neoforge.NeoForgeAdapter; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; @@ -38,14 +38,14 @@ import java.util.Objects; import javax.annotation.Nullable; -public class NeoForgeWorldNativeAccess implements WorldNativeAccess { +public class NeoForgeNativeWorld implements NativeWorld { private static final int UPDATE = 1; private static final int NOTIFY = 2; private final WeakReference world; private SideEffectSet sideEffectSet; - public NeoForgeWorldNativeAccess(WeakReference world) { + public NeoForgeNativeWorld(WeakReference world) { this.world = world; } @@ -59,8 +59,8 @@ public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { } @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); + public LevelChunk getChunk(int chunkX, int chunkZ) { + return getWorld().getChunk(chunkX, chunkZ); } @Override diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java index f305306db9..1d809addc5 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java @@ -27,12 +27,13 @@ import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.sponge.internal.NbtAdapter; -import com.sk89q.worldedit.sponge.internal.SpongeWorldNativeAccess; +import com.sk89q.worldedit.sponge.internal.SpongeNativeWorld; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; @@ -103,7 +104,7 @@ public final class SpongeWorld extends AbstractWorld { private static final Logger LOGGER = LogManagerCompat.getLogger(); private final WeakReference worldRef; - private final SpongeWorldNativeAccess worldNativeAccess; + private final SpongeNativeWorld worldNativeAccess; /** * Construct a new world. @@ -113,7 +114,7 @@ public final class SpongeWorld extends AbstractWorld { SpongeWorld(ServerWorld world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.worldNativeAccess = new SpongeWorldNativeAccess(new WeakReference<>((ServerLevel) world)); + this.worldNativeAccess = new SpongeNativeWorld(new WeakReference<>((ServerLevel) world)); } /** @@ -232,7 +233,7 @@ public > boolean setBlock(BlockVector3 position, B public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { checkNotNull(position); - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); + WNASharedImpl.applySideEffects(worldNativeAccess, position, previousType, sideEffectSet); return Sets.intersection( SpongeWorldEdit.inst().getInternalPlatform().getSupportedSideEffects(), diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeNativeWorld.java similarity index 94% rename from worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java rename to worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeNativeWorld.java index f831404759..ab6661dbe5 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeNativeWorld.java @@ -19,7 +19,7 @@ package com.sk89q.worldedit.sponge.internal; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.sponge.SpongeAdapter; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; @@ -37,14 +37,14 @@ import java.util.Objects; import javax.annotation.Nullable; -public class SpongeWorldNativeAccess implements WorldNativeAccess { +public class SpongeNativeWorld implements NativeWorld { private static final int UPDATE = 1; private static final int NOTIFY = 2; private final WeakReference world; private SideEffectSet sideEffectSet; - public SpongeWorldNativeAccess(WeakReference world) { + public SpongeNativeWorld(WeakReference world) { this.world = world; } @@ -58,8 +58,8 @@ public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { } @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); + public LevelChunk getChunk(int chunkX, int chunkZ) { + return getWorld().getChunk(chunkX, chunkZ); } @Override