From 42cbc195fec9ed20993d45c1590c24406624c453 Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Fri, 14 Jul 2023 17:08:05 +0100 Subject: [PATCH] feat: implement "unloaded-entity" operations - Add new extent that does an action on chunk GET load - closes #1826 --- .../fawe/v1_20_R2/PaperweightGetBlocks.java | 57 ++- .../v1_20_R2/PaperweightGetBlocks_Copy.java | 19 + .../v1_20_R2/PaperweightPostProcessor.java | 2 +- .../fawe/v1_20_R3/PaperweightGetBlocks.java | 57 ++- .../v1_20_R3/PaperweightGetBlocks_Copy.java | 19 + .../v1_20_R3/PaperweightPostProcessor.java | 2 +- .../fawe/v1_20_R4/PaperweightGetBlocks.java | 57 ++- .../v1_20_R4/PaperweightGetBlocks_Copy.java | 19 + .../v1_20_R4/PaperweightPostProcessor.java | 2 +- .../fawe/v1_21_R1/PaperweightGetBlocks.java | 59 ++- .../v1_21_R1/PaperweightGetBlocks_Copy.java | 19 + .../v1_21_R1/PaperweightPostProcessor.java | 2 +- .../core/configuration/Settings.java | 7 + .../core/extent/OncePerChunkExtent.java | 171 ++++++++ .../processor/BatchProcessorHolder.java | 10 + .../extent/processor/MultiBatchProcessor.java | 12 +- .../core/extent/processor/ProcessorScope.java | 28 +- .../heightmap/HeightmapProcessor.java | 2 +- .../processor/lighting/RelightProcessor.java | 4 +- .../history/changeset/AbstractChangeSet.java | 2 +- .../core/math/LocalBlockVector2Set.java | 382 ++++++++++++++++++ .../core/math/LocalBlockVectorSet.java | 4 +- .../core/math/MutableBlockVector2.java | 9 + .../fastasyncworldedit/core/queue/Filter.java | 5 +- .../core/queue/IBatchProcessor.java | 13 +- .../core/queue/IBlocks.java | 12 +- .../core/queue/IChunkGet.java | 9 + .../SingleThreadQueueExtent.java | 4 +- .../implementation/blocks/BitSetBlocks.java | 22 + .../implementation/blocks/CharBlocks.java | 17 + .../implementation/blocks/CharSetBlocks.java | 24 +- .../implementation/blocks/NullChunkGet.java | 17 + .../blocks/ThreadUnsafeCharBlocks.java | 22 +- .../implementation/chunk/ChunkHolder.java | 6 + .../queue/implementation/chunk/NullChunk.java | 6 + .../com/sk89q/worldedit/extent/Extent.java | 2 +- .../function/operation/ForwardExtentCopy.java | 106 ++++- 37 files changed, 1145 insertions(+), 65 deletions(-) create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/OncePerChunkExtent.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVector2Set.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java index a325bb08b..8251ee0e3 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java @@ -17,6 +17,7 @@ import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.util.LogManagerCompat; @@ -69,6 +70,7 @@ import org.enginehub.linbus.tree.LinTagType; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.AbstractCollection; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -88,6 +90,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; +import java.util.stream.Collectors; import static net.minecraft.core.registries.Registries.BIOME; @@ -145,11 +148,13 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc this.chunkPos = new IntPair(chunkX, chunkZ); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -368,8 +373,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptyList(); } @@ -413,6 +417,51 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc }; } + @Override + public Set getFullEntities() { + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + int size = entities.size(); + return new AbstractSet<>() { + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof com.sk89q.worldedit.entity.Entity e)) { + return false; + } + UUID getUUID = e.getState().getNbtData().getUUID(); + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (uuid.equals(getUUID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + Iterable result = entities + .stream() + .map(input -> new BukkitEntity(input.getBukkitEntity())) + .collect(Collectors.toList()); + return result.iterator(); + } + }; + } + private void removeEntity(Entity entity) { entity.discard(); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java index e655dd206..07528c438 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java @@ -46,6 +46,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { private final char[][] blocks; private final int minHeight; private final int maxHeight; + private final int chunkX; + private final int chunkZ; final ServerLevel serverLevel; final LevelChunk levelChunk; private Holder[][] biomes = null; @@ -56,6 +58,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { this.minHeight = serverLevel.getMinBuildHeight(); this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. this.blocks = new char[getSectionCount()][]; + this.chunkX = levelChunk.locX; + this.chunkZ = levelChunk.locZ; } protected void storeTile(BlockEntity blockEntity) { @@ -94,6 +98,11 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return null; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public boolean isCreateCopy() { return false; @@ -136,6 +145,16 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return minHeight >> 4; } + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + @Override public BiomeType getBiomeType(int x, int y, int z) { Holder biome = biomes[(y >> 4) - getMinSectionPosition()][(y & 12) << 2 | (z & 12) | (x & 12) >> 2]; diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java index 5fafdc439..aff4f8fe7 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPostProcessor.java @@ -112,7 +112,7 @@ public class PaperweightPostProcessor implements IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) { diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java index b542b3e08..a04f7d2d4 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java @@ -17,6 +17,7 @@ import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.util.LogManagerCompat; @@ -69,6 +70,7 @@ import org.enginehub.linbus.tree.LinTagType; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.AbstractCollection; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -88,6 +90,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; +import java.util.stream.Collectors; import static net.minecraft.core.registries.Registries.BIOME; @@ -145,11 +148,13 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc this.chunkPos = new IntPair(chunkX, chunkZ); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -368,8 +373,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptyList(); } @@ -413,6 +417,51 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc }; } + @Override + public Set getFullEntities() { + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + int size = entities.size(); + return new AbstractSet<>() { + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof com.sk89q.worldedit.entity.Entity e)) { + return false; + } + UUID getUUID = e.getState().getNbtData().getUUID(); + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (uuid.equals(getUUID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + Iterable result = entities + .stream() + .map(input -> new BukkitEntity(input.getBukkitEntity())) + .collect(Collectors.toList()); + return result.iterator(); + } + }; + } + private void removeEntity(Entity entity) { entity.discard(); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java index 1163c2d33..b7f536241 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java @@ -46,6 +46,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { private final char[][] blocks; private final int minHeight; private final int maxHeight; + private final int chunkX; + private final int chunkZ; final ServerLevel serverLevel; final LevelChunk levelChunk; private Holder[][] biomes = null; @@ -56,6 +58,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { this.minHeight = serverLevel.getMinBuildHeight(); this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. this.blocks = new char[getSectionCount()][]; + this.chunkX = levelChunk.locX; + this.chunkZ = levelChunk.locZ; } protected void storeTile(BlockEntity blockEntity) { @@ -94,6 +98,11 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return null; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public boolean isCreateCopy() { return false; @@ -136,6 +145,16 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return minHeight >> 4; } + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + @Override public BiomeType getBiomeType(int x, int y, int z) { Holder biome = biomes[(y >> 4) - getMinSectionPosition()][(y & 12) << 2 | (z & 12) | (x & 12) >> 2]; diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPostProcessor.java index cfd9e2753..6ed321a8d 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPostProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPostProcessor.java @@ -112,7 +112,7 @@ public class PaperweightPostProcessor implements IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) { diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java index ff05c453d..453801190 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java @@ -17,6 +17,7 @@ import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.util.LogManagerCompat; @@ -70,6 +71,7 @@ import org.enginehub.linbus.tree.LinTagType; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.AbstractCollection; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -89,6 +91,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; +import java.util.stream.Collectors; import static net.minecraft.core.registries.Registries.BIOME; @@ -146,11 +149,13 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc this.chunkPos = new IntPair(chunkX, chunkZ); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -369,8 +374,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptyList(); } @@ -414,6 +418,51 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc }; } + @Override + public Set getFullEntities() { + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + int size = entities.size(); + return new AbstractSet<>() { + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof com.sk89q.worldedit.entity.Entity e)) { + return false; + } + UUID getUUID = e.getState().getNbtData().getUUID(); + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (uuid.equals(getUUID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + Iterable result = entities + .stream() + .map(input -> new BukkitEntity(input.getBukkitEntity())) + .collect(Collectors.toList()); + return result.iterator(); + } + }; + } + private void removeEntity(Entity entity) { entity.discard(); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java index 7d199c7f6..8504c4fc2 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java @@ -47,6 +47,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { private final char[][] blocks; private final int minHeight; private final int maxHeight; + private final int chunkX; + private final int chunkZ; final ServerLevel serverLevel; final LevelChunk levelChunk; private Holder[][] biomes = null; @@ -57,6 +59,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { this.minHeight = serverLevel.getMinBuildHeight(); this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. this.blocks = new char[getSectionCount()][]; + this.chunkX = levelChunk.locX; + this.chunkZ = levelChunk.locZ; } protected void storeTile(BlockEntity blockEntity) { @@ -97,6 +101,11 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return null; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public boolean isCreateCopy() { return false; @@ -140,6 +149,16 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { } @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + + @Override public BiomeType getBiomeType(int x, int y, int z) { Holder biome = biomes[(y >> 4) - getMinSectionPosition()][(y & 12) << 2 | (z & 12) | (x & 12) >> 2]; return PaperweightPlatformAdapter.adapt(biome, serverLevel); diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPostProcessor.java index bc094a916..301ed8912 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPostProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPostProcessor.java @@ -112,7 +112,7 @@ public class PaperweightPostProcessor implements IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) { diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java index 70e7a4f22..095b17b63 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java @@ -17,6 +17,7 @@ import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.util.LogManagerCompat; @@ -70,6 +71,7 @@ import org.enginehub.linbus.tree.LinTagType; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.AbstractCollection; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -89,6 +91,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; +import java.util.stream.Collectors; import static net.minecraft.core.registries.Registries.BIOME; @@ -146,11 +149,13 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc this.chunkPos = new IntPair(chunkX, chunkZ); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -369,8 +374,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptyList(); } @@ -415,6 +419,53 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc } + @Override + public Set getFullEntities() { + getSections(true); + getChunk(); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + int size = entities.size(); + return new AbstractSet<>() { + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean contains(Object get) { + if (!(get instanceof com.sk89q.worldedit.entity.Entity e)) { + return false; + } + UUID getUUID = e.getState().getNbtData().getUUID(); + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (uuid.equals(getUUID)) { + return true; + } + } + return false; + } + + @Nonnull + @Override + public Iterator iterator() { + Iterable result = entities + .stream() + .map(input -> new BukkitEntity(input.getBukkitEntity())) + .collect(Collectors.toList()); + return result.iterator(); + } + }; + } + private void removeEntity(Entity entity) { entity.discard(); } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java index 18b557b97..479b8f50a 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java @@ -47,6 +47,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { private final char[][] blocks; private final int minHeight; private final int maxHeight; + private final int chunkX; + private final int chunkZ; final ServerLevel serverLevel; final LevelChunk levelChunk; private Holder[][] biomes = null; @@ -57,6 +59,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { this.minHeight = serverLevel.getMinBuildHeight(); this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. this.blocks = new char[getSectionCount()][]; + this.chunkX = levelChunk.locX; + this.chunkZ = levelChunk.locZ; } protected void storeTile(BlockEntity blockEntity) { @@ -97,6 +101,11 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return null; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public boolean isCreateCopy() { return false; @@ -139,6 +148,16 @@ public class PaperweightGetBlocks_Copy implements IChunkGet { return minHeight >> 4; } + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + @Override public BiomeType getBiomeType(int x, int y, int z) { Holder biome = biomes[(y >> 4) - getMinSectionPosition()][(y & 12) << 2 | (z & 12) | (x & 12) >> 2]; diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPostProcessor.java index 3b4c47087..9b1e4f3b6 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPostProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPostProcessor.java @@ -112,7 +112,7 @@ public class PaperweightPostProcessor implements IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index 60ec9311a..1be6bfb4b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -665,6 +665,13 @@ public class Settings extends Config { }) public boolean REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL = true; + @Comment({ + "[SAFE] Perform operations involving entities on chunk load", + " - Allows entities that might not otherwise be captured due to unloaded chunks to be captured", + " - Main use-case is copying larger areas with entities" + }) + public boolean IMPROVED_ENTITY_EDITS = true; + @Comment({ "Increased debug logging for brush actions and processor setup" }) diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/OncePerChunkExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/OncePerChunkExtent.java new file mode 100644 index 000000000..cd2d68f9a --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/OncePerChunkExtent.java @@ -0,0 +1,171 @@ +package com.fastasyncworldedit.core.extent; + +import com.fastasyncworldedit.core.extent.processor.ProcessorScope; +import com.fastasyncworldedit.core.math.LocalBlockVector2Set; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.fastasyncworldedit.core.queue.IChunk; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.fastasyncworldedit.core.util.ExtentTraverser; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * Extent/processor that runs a t + */ +public class OncePerChunkExtent extends AbstractDelegateExtent implements IBatchProcessor { + + private final LocalBlockVector2Set set = new LocalBlockVector2Set(); + private final IQueueExtent queue; + private final Consumer task; + private volatile long lastPair = Long.MAX_VALUE; + private volatile boolean isProcessing; + + /** + * Create a new instance. + * + * @param extent the extent + */ + public OncePerChunkExtent(Extent extent, IQueueExtent queue, Consumer task) { + super(extent); + this.queue = queue; + this.task = task; + } + + private boolean shouldRun(int chunkX, int chunkZ) { + final long pair = (long) chunkX << 32 | chunkZ & 0xffffffffL; + if (pair == lastPair) { + return false; + } + lastPair = pair; + synchronized (set) { + if (!set.contains(chunkX, chunkZ)) { + set.add(chunkX, chunkZ); + return true; + } + } + return false; + } + + private void checkAndRun(int chunkX, int chunkZ) { + if (!isProcessing && shouldRun(chunkX, chunkZ)) { + task.accept(queue.getCachedGet(chunkX, chunkZ)); + } + } + + @Override + public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { + return set; + } + + @Override + public IChunkGet processGet(final IChunkGet get) { + isProcessing = true; + if (shouldRun(get.getX(), get.getZ())) { + task.accept(get); + } + return get; + } + + @Nullable + @Override + public Extent construct(final Extent child) { + if (getExtent() != child) { + new ExtentTraverser(this).setNext(child); + } + return this; + } + + @Override + public ProcessorScope getScope() { + return ProcessorScope.READING_BLOCKS; + } + + @Override + public BlockState getBlock(final BlockVector3 position) { + checkAndRun(position.getBlockX() >> 4, position.getBlockZ() >> 4); + return super.getBlock(position); + } + + @Override + public BlockState getBlock(final int x, final int y, final int z) { + checkAndRun(x >> 4, z >> 4); + return super.getBlock(x, y, z); + } + + @Override + public BaseBlock getFullBlock(final BlockVector3 position) { + checkAndRun(position.getBlockX() >> 4, position.getBlockZ() >> 4); + return super.getFullBlock(position); + } + + @Override + public BaseBlock getFullBlock(final int x, final int y, final int z) { + checkAndRun(x >> 4, z >> 4); + return super.getFullBlock(x, y, z); + } + + @Override + public BiomeType getBiomeType(final int x, final int y, final int z) { + checkAndRun(x >> 4, z >> 4); + return super.getBiomeType(x, y, z); + } + + @Override + public BiomeType getBiome(final BlockVector3 position) { + checkAndRun(position.getBlockX() >> 4, position.getBlockZ() >> 4); + return super.getBiome(position); + } + + @Override + public int getEmittedLight(final int x, final int y, final int z) { + checkAndRun(x >> 4, z >> 4); + return super.getEmittedLight(x, y, z); + } + + @Override + public int getSkyLight(final int x, final int y, final int z) { + checkAndRun(x >> 4, z >> 4); + return super.getSkyLight(x, y, z); + } + + @Override + public int getBrightness(final int x, final int y, final int z) { + checkAndRun(x >> 4, z >> 4); + return super.getBrightness(x, y, z); + } + + @Override + public boolean setBiome(final int x, final int y, final int z, final BiomeType biome) { + checkAndRun(x >> 4, z >> 4); + return super.setBiome(x, y, z, biome); + } + + @Override + public boolean setBiome(final BlockVector3 position, final BiomeType biome) { + checkAndRun(position.getBlockX() >> 4, position.getBlockZ() >> 4); + return super.setBiome(position, biome); + } + + @Override + public void setBlockLight(final int x, final int y, final int z, final int value) { + checkAndRun(x >> 4, z >> 4); + super.setBlockLight(x, y, z, value); + } + + @Override + public void setSkyLight(final int x, final int y, final int z, final int value) { + checkAndRun(x >> 4, z >> 4); + super.setSkyLight(x, y, z, value); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java index 540e933b0..221117260 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java @@ -37,6 +37,16 @@ public class BatchProcessorHolder implements IBatchProcessorHolder { getPostProcessor().postProcess(chunk, get, set); } + @Override + public boolean processGet(final int chunkX, final int chunkZ) { + return getProcessor().processGet(chunkX, chunkZ); + } + + @Override + public IChunkGet processGet(final IChunkGet get) { + return getProcessor().processGet(get); + } + @Override public void flush() { getProcessor().flush(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java index c266fd544..4d42feb0e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java @@ -121,7 +121,7 @@ public class MultiBatchProcessor implements IBatchProcessor { for (IBatchProcessor processor : processors) { try { // We do NOT want to edit blocks in post processing - if (processor.getScope() != ProcessorScope.READING_SET_BLOCKS) { + if (processor.getScope() != ProcessorScope.READING_BLOCKS) { continue; } futures.add(processor.postProcessSet(chunk, get, set)); @@ -152,7 +152,7 @@ public class MultiBatchProcessor implements IBatchProcessor { for (IBatchProcessor processor : processors) { try { // We do NOT want to edit blocks in post processing - if (processor.getScope() != ProcessorScope.READING_SET_BLOCKS) { + if (processor.getScope() != ProcessorScope.READING_BLOCKS) { continue; } processor.postProcess(chunk, get, set); @@ -187,6 +187,14 @@ public class MultiBatchProcessor implements IBatchProcessor { return true; } + @Override + public IChunkGet processGet(IChunkGet get) { + for (IBatchProcessor processor : this.processors) { + get = processor.processGet(get); + } + return get; + } + @Override public Extent construct(Extent child) { for (IBatchProcessor processor : processors) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java index 503351fb7..6e97800aa 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/ProcessorScope.java @@ -7,7 +7,7 @@ package com.fastasyncworldedit.core.extent.processor; * - CHANGING_BLOCKS (processors that may ADD or CHANGE blocks being set) * - REMOVING_BLOCKS (processors that may ADD, CHANGE or REMOVE blocks being set) * - CUSTOM (processors that do not specify a SCOPE) - * - READING_SET_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g. + * - READING_BLOCKS (processors that do not alter blocks at all, and read the blocks that are actually going to set, e.g. * history processors). There is no guarantee that changes made here will be stored in history. */ public enum ProcessorScope { @@ -15,6 +15,11 @@ public enum ProcessorScope { CHANGING_BLOCKS(1), REMOVING_BLOCKS(2), CUSTOM(3), + READING_BLOCKS(5), + /** + * @deprecated use {@link ProcessorScope#READING_BLOCKS} + */ + @Deprecated(forRemoval = true, since = "TODO") READING_SET_BLOCKS(4); private final int value; @@ -28,18 +33,13 @@ public enum ProcessorScope { } public static ProcessorScope valueOf(int value) { - switch (value) { - case 0: - return ProcessorScope.ADDING_BLOCKS; - case 1: - return ProcessorScope.CHANGING_BLOCKS; - case 2: - return ProcessorScope.REMOVING_BLOCKS; - case 4: - return ProcessorScope.READING_SET_BLOCKS; - case 3: - default: - return ProcessorScope.CUSTOM; - } + return switch (value) { + case 0 -> ProcessorScope.ADDING_BLOCKS; + case 1 -> ProcessorScope.CHANGING_BLOCKS; + case 2 -> ProcessorScope.REMOVING_BLOCKS; + case 4 -> ProcessorScope.READING_SET_BLOCKS; + case 5 -> ProcessorScope.READING_BLOCKS; + default -> ProcessorScope.CUSTOM; + }; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java index 5d8f81e39..dec8f46c0 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java @@ -142,7 +142,7 @@ public class HeightmapProcessor implements IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java index 6a9f16dee..6951ab4e0 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java @@ -9,8 +9,6 @@ import com.fastasyncworldedit.core.queue.IChunkSet; import com.sk89q.worldedit.extent.Extent; import javax.annotation.Nullable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; public class RelightProcessor implements IBatchProcessor { @@ -54,7 +52,7 @@ public class RelightProcessor implements IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java index 33931aac6..faf074e05 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java @@ -239,7 +239,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } @Deprecated(forRemoval = true, since = "2.11.2") diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVector2Set.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVector2Set.java new file mode 100644 index 000000000..7937f7813 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVector2Set.java @@ -0,0 +1,382 @@ +package com.fastasyncworldedit.core.math; + +import com.fastasyncworldedit.core.util.MathMan; +import com.sk89q.worldedit.math.BlockVector2; +import com.zaxxer.sparsebits.SparseBitSet; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * The LocalBlockVectorSet is a Memory and CPU optimized Set for storing BlockVectors which are all in a local region + * - All vectors must be in a 2048 * 512 * 2048 area centered around the first entry + * - This will use 8 bytes for every 64 BlockVectors (about 800x less than a HashSet) + */ +public class LocalBlockVector2Set implements Set { + + private final SparseBitSet set; + private int offsetX; + private int offsetZ; + + /** + * New LocalBlockVectorSet that will set the offset x and z to the first value given. The y offset will default to 128 to + * allow -64 -> 320 world height. + */ + public LocalBlockVector2Set() { + offsetX = offsetZ = Integer.MAX_VALUE; + this.set = new SparseBitSet(); + } + + /** + * New LocalBlockVectorSet with a given offset. Defaults y offset to 128. + * + * @param x x offset + * @param z z offset + */ + public LocalBlockVector2Set(int x, int z) { + this.offsetX = x; + this.offsetZ = z; + this.set = new SparseBitSet(); + } + + private LocalBlockVector2Set(int x, int z, SparseBitSet set) { + this.offsetX = x; + this.offsetZ = z; + this.set = set; + } + + @Override + public int size() { + return set.cardinality(); + } + + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + /** + * If the set contains a position + * + * @param x x position + * @param z z position + * @return if the set contains the position + */ + public boolean contains(int x, int z) { + if (offsetX == Integer.MAX_VALUE) { + return false; + } + short sx = (short) (x - offsetX); + short sz = (short) (z - offsetZ); + if (sx > 32767 || sx < -32768 || sz > 32767 || sz < -32768) { + return false; + } + return set.get(MathMan.pairSearchCoords(sx, sz)); + } + + @Override + public boolean contains(Object o) { + if (o instanceof BlockVector2 v) { + return contains(v.getBlockX(), v.getBlockZ()); + } + return false; + } + + @SuppressWarnings("MethodDoesntCallSuperMethod") + @Override + public LocalBlockVector2Set clone() { + return new LocalBlockVector2Set(offsetX, offsetZ, set.clone()); + } + + /** + * If a radius is contained by the set + * + * @param x x radius center + * @param z z radius center + * @return if radius is contained by the set + */ + public boolean containsRadius(int x, int z, int radius) { + if (radius <= 0) { + return contains(x, z); + } + int length = radius * 2; + if (size() < length * length * length) { + int index = -1; + while ((index = set.nextSetBit(index + 1)) != -1) { + int ix = offsetX + MathMan.unpairSearchCoordsX(index); + int iz = offsetZ + MathMan.unpairSearchCoordsY(index); + if (Math.abs(ix - x) <= radius && Math.abs(iz - z) <= radius) { + return true; + } + } + return false; + } + for (int xx = -radius; xx <= radius; xx++) { + for (int zz = -radius; zz <= radius; zz++) { + if (contains(x + xx, z + zz)) { + return true; + } + } + } + return false; + } + + /** + * Set the offset applied to values when storing and reading to keep the values within -1024 to 1023. Uses default y offset + * of 128 to allow -64 -> 320 world height use. + * + * @param x x offset + * @param z z offset + */ + public void setOffset(int x, int z) { + this.offsetX = x; + this.offsetZ = z; + } + + protected MutableBlockVector2 getIndex(int getIndex) { + int size = size(); + if (getIndex > size) { + return null; + } + int index = -1; + for (int i = 0; i <= getIndex; i++) { + index = set.nextSetBit(index + 1); + } + if (index != -1) { + int x = offsetX + MathMan.unpairSearchCoordsX(index); + int z = offsetZ + MathMan.unpairSearchCoordsY(index); + return MutableBlockVector2.get(x, z); + } + return null; + } + + @Nonnull + @Override + public Iterator iterator() { + return new Iterator<>() { + final MutableBlockVector2 mutable = new MutableBlockVector2(0, 0); + int index = set.nextSetBit(0); + int previous = -1; + + @Override + public void remove() { + set.clear(previous); + } + + @Override + public boolean hasNext() { + return index != -1; + } + + @Override + public BlockVector2 next() { + if (index != -1) { + int x = offsetX + MathMan.unpairSearchCoordsX(index); + int z = offsetZ + MathMan.unpairSearchCoordsY(index); + mutable.mutX(x); + mutable.mutZ(z); + previous = index; + index = set.nextSetBit(index + 1); + return mutable; + } + return null; + } + }; + } + + @Nonnull + @Override + public BlockVector2[] toArray() { + return toArray(new BlockVector2[0]); + } + + @SuppressWarnings("unchecked") + @Nonnull + @Override + public T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = Arrays.copyOf(array, size); + } else if (array.length > size) { + array[size] = null; // mark as end to comply with the method contract + } + int index = 0; + for (int i = 0; i < size; i++) { + int x = offsetX + MathMan.unpairSearchCoordsX(index); + int z = offsetZ + MathMan.unpairSearchCoordsY(index); + array[i] = (T) BlockVector2.at(x, z); + index++; + } + return array; + } + + /** + * If a position is contained by the bounds of the set + * + * @param x x position + * @param z z position + * @return true if position is contained by the bounds of the set + */ + public boolean canAdd(int x, int z) { + if (offsetX == Integer.MAX_VALUE) { + return false; + } + int relX = x - offsetX; + int relZ = z - offsetZ; + return relX <= 32767 && relX >= -32768 && relZ <= 32727 && relZ >= -32768; + } + + /** + * Add a position to the set if not present + * + * @param x x position + * @param z z position + * @return true if not already present + */ + public boolean add(int x, int z) { + if (offsetX == Integer.MAX_VALUE) { + offsetX = x; + offsetZ = z; + } + int relX = x - offsetX; + int relZ = z - offsetZ; + if (relX > 32767 || relX < -32768 || relZ > 32767 || relZ < -32768) { + throw new UnsupportedOperationException( + "LocalBlockVector2Set can only contain vectors within 32768 blocks (cuboid) of the first entry. Attempted " + "to set block at " + x + ", " + z + ". With origin " + offsetX + " " + offsetZ); + } + int index = getIndex(x, z); + if (set.get(index)) { + return false; + } else { + set.set(index); + return true; + } + } + + /** + * Add a position to the set if not present + * + * @param vector position + * @return true if not already present + */ + @Override + public boolean add(BlockVector2 vector) { + return add(vector.getBlockX(), vector.getBlockZ()); + } + + private int getIndex(BlockVector2 vector) { + return getIndex(vector.getX(), vector.getZ()); + } + + private int getIndex(int x, int z) { + return MathMan.pairSearchCoords((short) (x - offsetX), (short) (z - offsetZ)); + } + + /** + * Remove a position from the set. + * + * @param x x position + * @param z z position + * @return true if value was present. + */ + public boolean remove(int x, int z) { + int relX = x - offsetX; + int relZ = z - offsetZ; + if (relX > 1023 || relX < -1024 || relZ > 1023 || relZ < -1024) { + return false; + } + int index = MathMan.pairSearchCoords((short) (x - offsetX), (short) (z - offsetZ)); + boolean value = set.get(index); + set.clear(index); + return value; + } + + @Override + public boolean remove(Object o) { + if (o instanceof BlockVector2 v) { + return remove(v.getBlockX(), v.getBlockZ()); + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + boolean result = false; + for (BlockVector2 v : c) { + result |= add(v); + } + return result; + } + + @Override + public boolean retainAll(@Nonnull Collection c) { + boolean result = false; + int size = size(); + int index = -1; + MutableBlockVector2 mVec = MutableBlockVector2.get(0, 0); + for (int i = 0; i < size; i++) { + index = set.nextSetBit(index + 1); + int x = offsetX + MathMan.unpairSearchCoordsX(index); + int z = offsetZ + MathMan.unpairSearchCoordsY(index); + mVec.mutX(x); + mVec.mutZ(z); + if (!c.contains(mVec)) { + result = true; + set.clear(index); + } + } + return result; + } + + @Override + public boolean removeAll(Collection c) { + boolean result = false; + for (Object o : c) { + result |= remove(o); + } + return result; + } + + /** + * Visit each point contained in the set + * + * @param visitor visitor to use + */ + public void forEach(BlockVector2SetVisitor visitor) { + int size = size(); + int index = -1; + for (int i = 0; i < size; i++) { + index = set.nextSetBit(index + 1); + int x = offsetX + MathMan.unpairSearchCoordsX(index); + int z = offsetZ + MathMan.unpairSearchCoordsY(index); + visitor.run(x, z, index); + } + } + + @Override + public void clear() { + offsetZ = Integer.MAX_VALUE; + offsetX = Integer.MAX_VALUE; + set.clear(); + } + + public interface BlockVector2SetVisitor { + + void run(int x, int z, int index); + + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVectorSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVectorSet.java index 72287b0a1..e0c9b541a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVectorSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/LocalBlockVectorSet.java @@ -213,8 +213,8 @@ public class LocalBlockVectorSet implements BlockVector3Set { @Nonnull @Override - public Object[] toArray() { - return toArray((Object[]) null); + public BlockVector3[] toArray() { + return toArray(new BlockVector3[0]); } @SuppressWarnings("unchecked") diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector2.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector2.java index 0d755eec6..c6decdaff 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector2.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector2.java @@ -48,4 +48,13 @@ public class MutableBlockVector2 extends BlockVector2 { return this; } + /** + * Create a new {@link BlockVector2} with the current x and z. + * + * @since TODO + */ + public BlockVector2 toImmutable() { + return BlockVector2.at(x, z); + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java index f308d4806..4c89514cf 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java @@ -17,10 +17,7 @@ public interface Filter { // * @param chunkX the x coordinate in the chunk // * @param chunkZ the z coordinate in the chunk // */ -// default boolean appliesChunk( -// int chunkX, -// int chunkZ -// ) { +// default boolean appliesChunk(int chunkX, int chunkZ) { // return true; // } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java index faec2f9f5..30f3fd6ba 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java @@ -50,7 +50,18 @@ public interface IBatchProcessor { } /** - * Convert this processor into an Extent based processor instead of a queue batch based one. + * Process a chunk GET. Method typically only called when a chunk is loaded into memory (miss from + * {@link com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent cache}). + * + * @param get GET chunk loaded + * @return processed get chunk + */ + default IChunkGet processGet(IChunkGet get) { + return get; + } + + /** + * Convert this processor into an Extent based processor instead of a queue batch based on. */ @Nullable Extent construct(Extent child); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBlocks.java index bda12dbe3..9664d4324 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBlocks.java @@ -24,7 +24,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; /** - * A shared interface for IGetBlocks and ISetBlocks. + * A shared interface for IGetBlocks and ISetBlocks. Represents a chunk. */ public interface IBlocks extends Trimable { @@ -124,6 +124,16 @@ public interface IBlocks extends Trimable { */ int getMinSectionPosition(); + /** + * Get the chunk x coordinate + */ + int getX(); + + /** + * Get the chunk z coordinate + */ + int getZ(); + default byte[] toByteArray(boolean full, boolean stretched) { return toByteArray(null, getBitMask(), full, stretched); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java index b72690029..291899844 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java @@ -3,6 +3,7 @@ package com.fastasyncworldedit.core.queue; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.InputExtent; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.biome.BiomeType; @@ -10,6 +11,7 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import javax.annotation.Nullable; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Future; @@ -68,6 +70,13 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput { return IBlocks.super.getTile(x, y, z); } + /** + * Get the entities in the chunk as "full" entities. + * + * @since TODO; + */ + Set getFullEntities(); + boolean isCreateCopy(); /** diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index b0239a5a3..bf4db8099 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -92,7 +92,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen @Override public IChunkGet getCachedGet(int chunkX, int chunkZ) { - return cacheGet.get(chunkX, chunkZ); + return processGet(cacheGet.get(chunkX, chunkZ)); } @Override @@ -171,7 +171,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen }; } if (set == null) { - set = (x, z) -> CharSetBlocks.newInstance(); + set = (x, z) -> CharSetBlocks.newInstance(x, z); } this.cacheGet = get; this.cacheSet = set; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java index 76f2191ed..f8e4889ac 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java @@ -26,13 +26,25 @@ public class BitSetBlocks implements IChunkSet { private final int minSectionPosition; private final int maxSectionPosition; private final int layers; + private final int chunkX; + private final int chunkZ; + /** + * @deprecated use {@link BitSetBlocks#BitSetBlocks(BlockState, int, int, int, int)} + */ + @Deprecated(forRemoval = true, since = "TODO") public BitSetBlocks(BlockState blockState, int minSectionPosition, int maxSectionPosition) { + this(blockState, minSectionPosition, maxSectionPosition, 0, 0); + } + + public BitSetBlocks(BlockState blockState, int minSectionPosition, int maxSectionPosition, int chunkX, int chunkZ) { this.row = new MemBlockSet.RowZ(minSectionPosition, maxSectionPosition); this.blockState = blockState; this.minSectionPosition = minSectionPosition; this.maxSectionPosition = maxSectionPosition; this.layers = maxSectionPosition - minSectionPosition + 1; + this.chunkX = chunkX; + this.chunkZ = chunkZ; } @Override @@ -227,6 +239,16 @@ public class BitSetBlocks implements IChunkSet { return maxSectionPosition; } + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + @Override public boolean trim(boolean aggressive) { return false; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java index c338033f0..73140c09c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java @@ -71,6 +71,8 @@ public abstract class CharBlocks implements IBlocks { protected int minSectionPosition; protected int maxSectionPosition; protected int sectionCount; + private int chunkX; + private int chunkZ; /** * New instance given initial min/max section indices. Can be negative. @@ -88,6 +90,11 @@ public abstract class CharBlocks implements IBlocks { } } + public void init(int chunkX, int chunkZ) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + @Override public synchronized boolean trim(boolean aggressive) { boolean result = true; @@ -194,6 +201,16 @@ public abstract class CharBlocks implements IBlocks { return get(layer, index); } + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + /** * Default char value to be used when "updating"/resetting data arrays */ diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java index 8d7c059ed..87e6aa500 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java @@ -27,14 +27,30 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { private static final Pool POOL = FaweCache.INSTANCE.registerPool( CharSetBlocks.class, - CharSetBlocks::new, - Settings.settings().QUEUE.POOL + CharSetBlocks::new, Settings.settings().QUEUE.POOL ); + /** + * @deprecated Use {@link CharSetBlocks#newInstance(int, int)} + */ + @Deprecated(forRemoval = true, since = "TODO") public static CharSetBlocks newInstance() { return POOL.poll(); } + /** + * Create a new {@link CharSetBlocks} instance + * + * @param x chunk x + * @param z chunk z + * @return New pooled CharSetBlocks instance. + */ + public static CharSetBlocks newInstance(int x, int z) { + CharSetBlocks set = POOL.poll(); + set.init(x, z); + return set; + } + public BiomeType[][] biomes; public char[][] light; public char[][] skyLight; @@ -374,7 +390,9 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { heightMaps != null ? new EnumMap<>(heightMaps) : null, defaultOrdinal(), fastMode, - bitMask + bitMask, + getX(), + getZ() ); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java index 273f44952..ad20ed0c9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java @@ -6,6 +6,7 @@ import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; @@ -18,6 +19,7 @@ import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Future; @@ -63,6 +65,11 @@ public final class NullChunkGet implements IChunkGet { return Collections.emptyList(); } + @Nullable + public Set getFullEntities() { + return null; + } + @Override public int setCreateCopy(boolean createCopy) { return -1; @@ -105,6 +112,16 @@ public final class NullChunkGet implements IChunkGet { return 0; } + @Override + public int getX() { + return 0; + } + + @Override + public int getZ() { + return 0; + } + public boolean trim(boolean aggressive) { return true; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java index c21e932a9..9e5c4d3ef 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java @@ -38,6 +38,8 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); private final char defaultOrdinal; + private final int chunkX; + private final int chunkZ; private char[][] blocks; private int minSectionPosition; private int maxSectionPosition; @@ -71,7 +73,9 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { Map heightMaps, char defaultOrdinal, boolean fastMode, - int bitMask + int bitMask, + int chunkX, + int chunkZ ) { this.blocks = blocks; this.minSectionPosition = minSectionPosition; @@ -87,6 +91,8 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { this.defaultOrdinal = defaultOrdinal; this.fastMode = fastMode; this.bitMask = bitMask; + this.chunkX = chunkX; + this.chunkZ = chunkZ; } @Override @@ -178,6 +184,16 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { return minSectionPosition; } + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + public char get(int x, int y, int z) { int layer = (y >> 4); if (!hasSection(layer)) { @@ -480,7 +496,9 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { heightMaps != null ? new HashMap<>(heightMaps) : null, defaultOrdinal, fastMode, - bitMask + bitMask, + chunkX, + chunkZ ); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java index 7c36eb94c..192793cf2 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java @@ -12,6 +12,7 @@ import com.fastasyncworldedit.core.queue.IQueueChunk; import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; import com.fastasyncworldedit.core.util.MemUtil; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -1089,6 +1090,11 @@ public class ChunkHolder> implements IQueueChunk { return delegate.get(this).entities(); } + @Override + public Set getFullEntities() { + return delegate.get(this).getFullEntities(); + } + @Override public BaseBlock getFullBlock(int x, int y, int z) { return delegate.getFullBlock(this, x, y, z); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java index 14107e83a..f430a5b34 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java @@ -6,6 +6,7 @@ import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.Filter; import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.world.biome.BiomeType; @@ -187,6 +188,11 @@ public final class NullChunk implements IQueueChunk { return -1; } + @Override + public Set getFullEntities() { + return Collections.emptySet(); + } + @Override public boolean isCreateCopy() { return false; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java index 9f73c8f5e..45f604f71 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -889,7 +889,7 @@ public interface Extent extends InputExtent, OutputExtent { } default Extent addPostProcessor(IBatchProcessor processor) { - if (processor.getScope() != ProcessorScope.READING_SET_BLOCKS) { + if (processor.getScope() != ProcessorScope.READING_BLOCKS) { throw new IllegalArgumentException("You cannot alter blocks in a PostProcessor"); } return processor.construct(this); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java index f98d5f924..5403b3807 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java @@ -20,21 +20,29 @@ package com.sk89q.worldedit.function.operation; import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.BlockTranslateExtent; +import com.fastasyncworldedit.core.extent.OncePerChunkExtent; import com.fastasyncworldedit.core.extent.PositionTransformExtent; +import com.fastasyncworldedit.core.extent.processor.ExtentBatchProcessorHolder; import com.fastasyncworldedit.core.function.RegionMaskTestFunction; import com.fastasyncworldedit.core.function.block.BiomeCopy; import com.fastasyncworldedit.core.function.block.CombinedBlockCopy; import com.fastasyncworldedit.core.function.block.SimpleBlockCopy; import com.fastasyncworldedit.core.function.visitor.IntersectRegionFunction; +import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; import com.fastasyncworldedit.core.util.ExtentTraverser; import com.fastasyncworldedit.core.util.MaskTraverser; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.metadata.EntityProperties; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.CombinedRegionFunction; import com.sk89q.worldedit.function.RegionFunction; @@ -44,17 +52,24 @@ import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.function.visitor.EntityVisitor; import com.sk89q.worldedit.function.visitor.RegionVisitor; +import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.math.transform.Identity; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.FlatRegion; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -68,6 +83,8 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class ForwardExtentCopy implements Operation { + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private final Extent source; private final Extent destination; private final Region region; @@ -405,16 +422,53 @@ public class ForwardExtentCopy implements Operation { blockCopy = new RegionVisitor(region, copy, preloader); } - List entities; + Collection entities; if (copyingEntities) { - // filter players since they can't be copied - entities = source.getEntities(region); - entities.removeIf(entity -> { - EntityProperties properties = entity.getFacet(EntityProperties.class); - return properties != null && !properties.isPasteable(); - }); + IQueueExtent queue; + Extent ext = source instanceof AbstractDelegateExtent ex ? ex.getExtent() : source; + ParallelQueueExtent parallel = new ExtentTraverser<>(source).findAndGet(ParallelQueueExtent.class); + if (parallel != null) { + queue = parallel.getExtent(); + } else { + queue = new ExtentTraverser<>(source).findAndGet(SingleThreadQueueExtent.class); + } + if (Settings.settings().EXPERIMENTAL.IMPROVED_ENTITY_EDITS && queue != null) { + entities = new LinkedBlockingQueue<>(); + OncePerChunkExtent oncePer = new OncePerChunkExtent( + ext, + queue, + (get) -> { + if (region.containsChunk(get.getX(), get.getZ())) { + entities.addAll(get.getFullEntities()); + } else { + get.getFullEntities().forEach(e -> { + if (region.contains(e.getLocation().toBlockPoint())) { + entities.add(e); + } + }); + } + } + ); + ExtentBatchProcessorHolder batchExtent = + new ExtentTraverser<>(source).findAndGet(ExtentBatchProcessorHolder.class); + if (batchExtent != null) { + batchExtent.getProcessor().join(oncePer); + } else { + new ExtentTraverser(source).setNext(oncePer); + } + } else { + if (Settings.settings().EXPERIMENTAL.IMPROVED_ENTITY_EDITS) { + LOGGER.warn("Could not find IQueueExtent instance for entity retrieval, falling back to default method."); + } + // filter players since they can't be copied + entities = new HashSet<>(source.getEntities(region)); + entities.removeIf(entity -> { + EntityProperties properties = entity.getFacet(EntityProperties.class); + return properties != null && !properties.isPasteable(); + }); + } } else { - entities = Collections.emptyList(); + entities = Collections.emptySet(); } for (int i = 0; i < repetitions; i++) { @@ -476,4 +530,40 @@ public class ForwardExtentCopy implements Operation { ); } + private static final class EntityHolder implements Entity { + + @Nullable + @Override + public BaseEntity getState() { + return null; + } + + @Override + public boolean remove() { + return false; + } + + @Override + public Location getLocation() { + return null; + } + + @Override + public boolean setLocation(final Location location) { + return false; + } + + @Override + public Extent getExtent() { + return null; + } + + @Nullable + @Override + public T getFacet(final Class cls) { + return null; + } + + } + }