From dec5e208beadc458490cc94e924039ec0bd7bf83 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 | 55 ++- .../v1_20_R2/PaperweightGetBlocks_Copy.java | 19 + .../v1_20_R2/PaperweightPostProcessor.java | 2 +- .../fawe/v1_20_R3/PaperweightGetBlocks.java | 55 ++- .../v1_20_R3/PaperweightGetBlocks_Copy.java | 19 + .../v1_20_R3/PaperweightPostProcessor.java | 2 +- .../fawe/v1_20_R4/PaperweightGetBlocks.java | 55 ++- .../v1_20_R4/PaperweightGetBlocks_Copy.java | 19 + .../v1_20_R4/PaperweightPostProcessor.java | 2 +- .../fawe/v1_21_R1/PaperweightGetBlocks.java | 57 ++- .../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 | 11 + .../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 | 16 + .../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, 1135 insertions(+), 64 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 93f84a1ec4..05c6dd11aa 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 @@ -19,6 +19,7 @@ import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.internal.Constants; @@ -137,11 +138,13 @@ public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -359,8 +362,7 @@ public CompoundTag getEntity(UUID uuid) { @Override public Set getEntities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptySet(); } @@ -404,6 +406,51 @@ public Iterator iterator() { }; } + @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 b6f4c7d947..4157422a78 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 @@ -43,6 +43,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; @@ -53,6 +55,8 @@ protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) { 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) { @@ -90,6 +94,11 @@ public Set getEntities() { return this.entities; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public CompoundTag getEntity(UUID uuid) { for (CompoundTag tag : entities) { @@ -142,6 +151,16 @@ public int getMinSectionPosition() { 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 5fafdc4398..aff4f8fe79 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 Extent construct(final Extent child) { @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 d2d18968aa..8c6fcbb272 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 @@ -19,6 +19,7 @@ import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.internal.Constants; @@ -136,11 +137,13 @@ public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -358,8 +361,7 @@ public CompoundTag getEntity(UUID uuid) { @Override public Set getEntities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptySet(); } @@ -403,6 +405,51 @@ public Iterator iterator() { }; } + @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 23c8822842..508eeb16f2 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 @@ -43,6 +43,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; @@ -53,6 +55,8 @@ protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) { 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) { @@ -90,6 +94,11 @@ public Set getEntities() { return this.entities; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public CompoundTag getEntity(UUID uuid) { for (CompoundTag tag : entities) { @@ -142,6 +151,16 @@ public int getMinSectionPosition() { 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 cfd9e27536..6ed321a8d7 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 Extent construct(final Extent child) { @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 47466ac5e7..78b2a5522e 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 @@ -19,6 +19,7 @@ import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.internal.Constants; @@ -139,11 +140,13 @@ public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -361,8 +364,7 @@ public CompoundTag getEntity(UUID uuid) { @Override public Set getEntities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptySet(); } @@ -406,6 +408,51 @@ public Iterator iterator() { }; } + @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 1467600201..d4b78e26b2 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 @@ -44,6 +44,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; @@ -54,6 +56,8 @@ protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) { 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) { @@ -91,6 +95,11 @@ public Set getEntities() { return this.entities; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public CompoundTag getEntity(UUID uuid) { for (CompoundTag tag : entities) { @@ -144,6 +153,16 @@ public int getMinSectionPosition() { } @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 bc094a916f..301ed8912b 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 Extent construct(final Extent child) { @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 bdd9d0648c..1bd9d82117 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 @@ -19,6 +19,7 @@ import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.internal.Constants; @@ -139,11 +140,13 @@ public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); } - public int getChunkX() { + @Override + public int getX() { return chunkX; } - public int getChunkZ() { + @Override + public int getZ() { return chunkZ; } @@ -361,8 +364,7 @@ public CompoundTag getEntity(UUID uuid) { @Override public Set getEntities() { - ensureLoaded(serverLevel, chunkX, chunkZ); - List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + List entities = PaperweightPlatformAdapter.getEntities(ensureLoaded(serverLevel, chunkX, chunkZ)); if (entities.isEmpty()) { return Collections.emptySet(); } @@ -406,6 +408,53 @@ public Iterator iterator() { }; } + @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 a0a4d02d92..7978c14689 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 @@ -44,6 +44,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; @@ -54,6 +56,8 @@ protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) { 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) { @@ -91,6 +95,11 @@ public Set getEntities() { return this.entities; } + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + @Override public CompoundTag getEntity(UUID uuid) { for (CompoundTag tag : entities) { @@ -143,6 +152,16 @@ public int getMinSectionPosition() { 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 3b4c47087e..9b1e4f3b6d 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 Extent construct(final Extent child) { @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 4db4691870..ce0bf00fb7 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 @@ -663,6 +663,13 @@ public static class EXPERIMENTAL { }) 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 0000000000..cd2d68f9a6 --- /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 540e933b0a..221117260e 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 void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) { 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 c266fd5441..4d42feb0ed 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 Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { 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 void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) { 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 boolean processGet(int chunkX, int chunkZ) { 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 503351fb70..6e97800aa6 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 @@ * - 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 int intValue() { } 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 5d8f81e39d..dec8f46c03 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 Extent construct(Extent child) { @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 6a9f16dee3..6951ab4e09 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.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 @@ Extent construct(Extent child) { @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 84c1193eb2..fe77f81288 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 @@ -234,7 +234,7 @@ public Future postProcessSet(final IChunk chunk, final IChunkGet get, final I @Override public ProcessorScope getScope() { - return ProcessorScope.READING_SET_BLOCKS; + return ProcessorScope.READING_BLOCKS; } public abstract void addTileCreate(CompoundTag tag); 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 0000000000..7937f78136 --- /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 72287b0a1e..e0c9b541ab 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 BlockVector3 next() { @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 0d755eec6f..c6decdaff0 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 MutableBlockVector2 mutZ(int z) { 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 366faa8c5a..9ad858bf72 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 @@ -16,10 +16,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 6b81b61f75..30f3fd6ba4 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 @@ -49,6 +49,17 @@ default boolean processGet(int chunkX, int chunkZ) { return true; } + /** + * 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. */ 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 ff6842c0ac..94e5b39c25 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 @@ -19,7 +19,7 @@ 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 { @@ -92,6 +92,16 @@ default int getBitMask() { */ 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 b2ac59cb2c..c740b4dd4e 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 @@ -2,6 +2,7 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; 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; @@ -9,6 +10,7 @@ import com.sk89q.worldedit.world.block.BlockState; import javax.annotation.Nullable; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Future; @@ -48,6 +50,13 @@ default void optimize() { CompoundTag getEntity(UUID uuid); + /** + * 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 b0239a5a3c..bf4db8099f 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 void disableQueue() { @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 synchronized void init(Extent extent, IChunkCache get, IChunkC }; } 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 ae976777c4..eb707aab87 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 @@ -25,13 +25,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 @@ -226,6 +238,16 @@ public int getMinSectionPosition() { 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 c338033f0a..73140c09c7 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 boolean isFull() { 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 CharBlocks(int minSectionPosition, int maxSectionPosition) { } } + 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 char get(int x, int y, int z) { 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 da28e59254..f0ef4910c6 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 @@ -25,14 +25,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; @@ -372,7 +388,9 @@ public ThreadUnsafeCharBlocks createCopy() { 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 0428706660..c8ccd254d4 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.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; import com.sk89q.jnbt.CompoundTag; +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; @@ -68,6 +69,11 @@ public CompoundTag getEntity(@Nonnull UUID uuid) { return null; } + @Nullable + public Set getFullEntities() { + return null; + } + @Override public int setCreateCopy(boolean createCopy) { return -1; @@ -110,6 +116,16 @@ public int getMinSectionPosition() { 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 90ae6b32e9..a33a3aa74d 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 @@ -37,6 +37,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; @@ -70,7 +72,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; @@ -86,6 +90,8 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { this.defaultOrdinal = defaultOrdinal; this.fastMode = fastMode; this.bitMask = bitMask; + this.chunkX = chunkX; + this.chunkZ = chunkZ; } @Override @@ -177,6 +183,16 @@ public int getMinSectionPosition() { 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)) { @@ -479,7 +495,9 @@ public IChunkSet createCopy() { 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 a7417eddf5..3a584fc1cb 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.implementation.ParallelQueueExtent; import com.fastasyncworldedit.core.util.MemUtil; import com.sk89q.jnbt.CompoundTag; +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; @@ -889,6 +890,11 @@ public Set getEntities() { return delegate.get(this).getEntities(); } + @Override + public Set getFullEntities() { + return delegate.get(this).getFullEntities(); + } + @Override public boolean hasSection(int layer) { return chunkExisting != null && chunkExisting.hasSection(layer); 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 8cc6471ba3..7d7932c7c4 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.queue.IChunkSet; import com.fastasyncworldedit.core.queue.IQueueChunk; import com.sk89q.jnbt.CompoundTag; +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; @@ -188,6 +189,11 @@ public int setCreateCopy(boolean createCopy) { 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 1b4420d27c..1e0cb12664 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 @@ -890,7 +890,7 @@ default Extent addProcessor(IBatchProcessor processor) { } 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 f98d5f9241..5403b38074 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.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 @@ */ 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 Operation resume(RunContext run) throws WorldEditException { 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 Iterable getStatusMessages() { ); } + 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; + } + + } + }