geforkt von Mirrors/FastAsyncWorldEdit
Memory optimizations (#505)
* Remove LocatedBlock overhead in LBL map * Add new space-efficient block map, with thourough testing * Drop ordering property, add full insertion test * Add licenses * Fix mocked platform conflicts * Disable full block map testing for faster builds * Re-implement BlockMap with fastutil maps * Re-write chunk batching to be memory efficient * Make MultiStageReorder use BlockMap * Increase LBL load factor, fix long-pack limit detection * Fix infinite loop in chunk batching * Save memory in history by cleaning up MSR * Re-implement LocatedBlockList in BlockMap * Fix data race with BlockType lazy fields * Make IDs ALWAYS present, only runtime-consistent. Use for memory efficiency in BlockMap * Remap inner structure of BlockMap for smaller maps * Remove containedBlocks fields, not very efficient * Fix minor de-optimizing bug in stage reorder * Make long packed y signed * Add extended Y limit configuration option * Add licenses * Store 3 ints for unoptimized BV list * Add final to BitMath * Correct int-cast for long-packing
Dieser Commit ist enthalten in:
Ursprung
ec5bc5a3b7
Commit
f472c20bfb
@ -46,6 +46,9 @@ fun Project.applyPlatformAndCoreConfiguration() {
|
||||
|
||||
dependencies {
|
||||
"testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT}")
|
||||
"testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT}")
|
||||
"testImplementation"("org.mockito:mockito-core:${Versions.MOCKITO}")
|
||||
"testImplementation"("org.mockito:mockito-junit-jupiter:${Versions.MOCKITO}")
|
||||
"testRuntime"("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT}")
|
||||
}
|
||||
|
||||
|
@ -4,4 +4,5 @@ object Versions {
|
||||
const val PISTON = "0.4.3"
|
||||
const val AUTO_VALUE = "1.6.5"
|
||||
const val JUNIT = "5.5.0"
|
||||
const val MOCKITO = "3.0.0"
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
<allow pkg="org.mozilla.javascript"/>
|
||||
<allow pkg="de.schlichtherle"/>
|
||||
<allow pkg="com.google.auto"/>
|
||||
<allow pkg="it.unimi.dsi.fastutil"/>
|
||||
|
||||
<subpackage name="bukkit">
|
||||
<allow pkg="org.bukkit"/>
|
||||
|
@ -63,6 +63,9 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
relocate("io.papermc.lib", "com.sk89q.worldedit.bukkit.paperlib") {
|
||||
include(dependency("io.papermc:paperlib:1.0.2"))
|
||||
}
|
||||
relocate("it.unimi.dsi.fastutil", "com.sk89q.worldedit.bukkit.fastutil") {
|
||||
include(dependency("it.unimi.dsi:fastutil"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ dependencies {
|
||||
"compile"("com.google.code.findbugs:jsr305:1.3.9")
|
||||
"compile"("com.google.code.gson:gson:2.8.0")
|
||||
"compile"("org.slf4j:slf4j-api:1.7.26")
|
||||
"compile"("it.unimi.dsi:fastutil:8.2.1")
|
||||
|
||||
"compileOnly"(project(":worldedit-libs:core:ap"))
|
||||
"annotationProcessor"(project(":worldedit-libs:core:ap"))
|
||||
@ -28,7 +29,6 @@ dependencies {
|
||||
"annotationProcessor"("com.google.guava:guava:21.0")
|
||||
"compileOnly"("com.google.auto.value:auto-value-annotations:${Versions.AUTO_VALUE}")
|
||||
"annotationProcessor"("com.google.auto.value:auto-value:${Versions.AUTO_VALUE}")
|
||||
"testCompile"("org.mockito:mockito-core:1.9.0-rc1")
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
|
@ -75,6 +75,7 @@ public abstract class LocalConfiguration {
|
||||
public int butcherMaxRadius = -1;
|
||||
public boolean allowSymlinks = false;
|
||||
public boolean serverSideCUI = true;
|
||||
public boolean extendedYLimit = false;
|
||||
|
||||
protected String[] getDefaultDisallowedBlocks() {
|
||||
List<BlockType> blockTypes = Lists.newArrayList(
|
||||
|
@ -19,25 +19,21 @@
|
||||
|
||||
package com.sk89q.worldedit.extent.reorder;
|
||||
|
||||
import com.google.common.collect.Table;
|
||||
import com.google.common.collect.TreeBasedTable;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.extent.AbstractBufferingExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.function.operation.Operation;
|
||||
import com.sk89q.worldedit.function.operation.RunContext;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.math.RegionOptimizedComparator;
|
||||
import com.sk89q.worldedit.util.collection.BlockMap;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A special extent that batches changes into Minecraft chunks. This helps
|
||||
@ -47,17 +43,7 @@ import java.util.Set;
|
||||
*/
|
||||
public class ChunkBatchingExtent extends AbstractBufferingExtent {
|
||||
|
||||
/**
|
||||
* Comparator optimized for sorting chunks by the region file they reside
|
||||
* in. This allows for file caches to be used while loading the chunk.
|
||||
*/
|
||||
private static final Comparator<BlockVector2> REGION_OPTIMIZED_SORT =
|
||||
Comparator.comparing((BlockVector2 vec) -> vec.shr(5), BlockVector2.COMPARING_GRID_ARRANGEMENT)
|
||||
.thenComparing(BlockVector2.COMPARING_GRID_ARRANGEMENT);
|
||||
|
||||
private final Table<BlockVector2, BlockVector3, BaseBlock> batches =
|
||||
TreeBasedTable.create(REGION_OPTIMIZED_SORT, BlockVector3.sortByCoordsYzx());
|
||||
private final Set<BlockVector3> containedBlocks = new HashSet<>();
|
||||
private final BlockMap blockMap = BlockMap.create();
|
||||
private boolean enabled;
|
||||
|
||||
public ChunkBatchingExtent(Extent extent) {
|
||||
@ -81,32 +67,18 @@ public class ChunkBatchingExtent extends AbstractBufferingExtent {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private BlockVector2 getChunkPos(BlockVector3 location) {
|
||||
return location.shr(4).toBlockVector2();
|
||||
}
|
||||
|
||||
private BlockVector3 getInChunkPos(BlockVector3 location) {
|
||||
return BlockVector3.at(location.getX() & 15, location.getY(), location.getZ() & 15);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 location, B block) throws WorldEditException {
|
||||
if (!enabled) {
|
||||
return setDelegateBlock(location, block);
|
||||
}
|
||||
BlockVector2 chunkPos = getChunkPos(location);
|
||||
BlockVector3 inChunkPos = getInChunkPos(location);
|
||||
batches.put(chunkPos, inChunkPos, block.toBaseBlock());
|
||||
containedBlocks.add(location);
|
||||
blockMap.put(location, block.toBaseBlock());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<BaseBlock> getBufferedBlock(BlockVector3 position) {
|
||||
if (!containedBlocks.contains(position)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(batches.get(getChunkPos(position), getInChunkPos(position)));
|
||||
return Optional.ofNullable(blockMap.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,25 +89,22 @@ public class ChunkBatchingExtent extends AbstractBufferingExtent {
|
||||
return new Operation() {
|
||||
|
||||
// we get modified between create/resume -- only create this on resume to prevent CME
|
||||
private Iterator<Map.Entry<BlockVector2, Map<BlockVector3, BaseBlock>>> batchIterator;
|
||||
private Iterator<BlockVector3> iterator;
|
||||
|
||||
@Override
|
||||
public Operation resume(RunContext run) throws WorldEditException {
|
||||
if (batchIterator == null) {
|
||||
batchIterator = batches.rowMap().entrySet().iterator();
|
||||
if (iterator == null) {
|
||||
iterator = ImmutableSortedSet.copyOf(RegionOptimizedComparator.INSTANCE,
|
||||
blockMap.keySet()).iterator();
|
||||
}
|
||||
if (!batchIterator.hasNext()) {
|
||||
while (iterator.hasNext()) {
|
||||
BlockVector3 position = iterator.next();
|
||||
BaseBlock block = blockMap.get(position);
|
||||
getExtent().setBlock(position, block);
|
||||
}
|
||||
blockMap.clear();
|
||||
return null;
|
||||
}
|
||||
Map.Entry<BlockVector2, Map<BlockVector3, BaseBlock>> next = batchIterator.next();
|
||||
BlockVector3 chunkOffset = next.getKey().toBlockVector3().shl(4);
|
||||
for (Map.Entry<BlockVector3, BaseBlock> block : next.getValue().entrySet()) {
|
||||
getExtent().setBlock(block.getKey().add(chunkOffset), block.getValue());
|
||||
containedBlocks.remove(block.getKey());
|
||||
}
|
||||
batchIterator.remove();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
@ -24,9 +24,10 @@ import com.sk89q.worldedit.extent.AbstractBufferingExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.function.operation.Operation;
|
||||
import com.sk89q.worldedit.function.operation.OperationQueue;
|
||||
import com.sk89q.worldedit.function.operation.SetLocatedBlocks;
|
||||
import com.sk89q.worldedit.function.operation.RunContext;
|
||||
import com.sk89q.worldedit.function.operation.SetBlockMap;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.util.collection.LocatedBlockList;
|
||||
import com.sk89q.worldedit.util.collection.BlockMap;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockCategories;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
@ -36,12 +37,10 @@ import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Re-orders blocks into several stages.
|
||||
@ -143,8 +142,7 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde
|
||||
priorityMap.put(BlockTypes.MOVING_PISTON, PlacementPriority.FINAL);
|
||||
}
|
||||
|
||||
private final Set<BlockVector3> containedBlocks = new HashSet<>();
|
||||
private Map<PlacementPriority, LocatedBlockList> stages = new HashMap<>();
|
||||
private Map<PlacementPriority, BlockMap> stages = new HashMap<>();
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
@ -178,7 +176,7 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde
|
||||
this.enabled = enabled;
|
||||
|
||||
for (PlacementPriority priority : PlacementPriority.values()) {
|
||||
stages.put(priority, new LocatedBlockList());
|
||||
stages.put(priority, BlockMap.create());
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +218,7 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde
|
||||
return setDelegateBlock(location, block);
|
||||
}
|
||||
|
||||
BlockState existing = getBlock(location);
|
||||
BlockState existing = getExtent().getBlock(location);
|
||||
PlacementPriority priority = getPlacementPriority(block);
|
||||
PlacementPriority srcPriority = getPlacementPriority(existing);
|
||||
|
||||
@ -229,13 +227,13 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde
|
||||
|
||||
switch (srcPriority) {
|
||||
case FINAL:
|
||||
stages.get(PlacementPriority.CLEAR_FINAL).add(location, replacement);
|
||||
stages.get(PlacementPriority.CLEAR_FINAL).put(location, replacement);
|
||||
break;
|
||||
case LATE:
|
||||
stages.get(PlacementPriority.CLEAR_LATE).add(location, replacement);
|
||||
stages.get(PlacementPriority.CLEAR_LATE).put(location, replacement);
|
||||
break;
|
||||
case LAST:
|
||||
stages.get(PlacementPriority.CLEAR_LAST).add(location, replacement);
|
||||
stages.get(PlacementPriority.CLEAR_LAST).put(location, replacement);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -244,16 +242,12 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde
|
||||
}
|
||||
}
|
||||
|
||||
stages.get(priority).add(location, block);
|
||||
containedBlocks.add(location);
|
||||
stages.get(priority).put(location, block.toBaseBlock());
|
||||
return !existing.equalsFuzzy(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<BaseBlock> getBufferedBlock(BlockVector3 position) {
|
||||
if (!containedBlocks.contains(position)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return stages.values().stream()
|
||||
.map(blocks -> blocks.get(position))
|
||||
.filter(Objects::nonNull)
|
||||
@ -267,7 +261,17 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde
|
||||
}
|
||||
List<Operation> operations = new ArrayList<>();
|
||||
for (PlacementPriority priority : PlacementPriority.values()) {
|
||||
operations.add(new SetLocatedBlocks(getExtent(), stages.get(priority)));
|
||||
BlockMap blocks = stages.get(priority);
|
||||
operations.add(new SetBlockMap(getExtent(), blocks) {
|
||||
@Override
|
||||
public Operation resume(RunContext run) throws WorldEditException {
|
||||
Operation operation = super.resume(run);
|
||||
if (operation == null) {
|
||||
blocks.clear();
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new OperationQueue(operations);
|
||||
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.function.operation;
|
||||
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.util.LocatedBlock;
|
||||
import com.sk89q.worldedit.util.collection.BlockMap;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
public class SetBlockMap implements Operation {
|
||||
|
||||
private final Extent extent;
|
||||
private final BlockMap blocks;
|
||||
|
||||
public SetBlockMap(Extent extent, BlockMap blocks) {
|
||||
this.extent = checkNotNull(extent);
|
||||
this.blocks = checkNotNull(blocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation resume(RunContext run) throws WorldEditException {
|
||||
for (Map.Entry<BlockVector3, BaseBlock> entry : blocks.entrySet()) {
|
||||
extent.setBlock(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStatusMessages(List<String> messages) {
|
||||
}
|
||||
|
||||
}
|
@ -22,10 +22,10 @@ package com.sk89q.worldedit.internal.block;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import com.sk89q.worldedit.world.registry.BlockRegistry;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.BitSet;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
@ -43,19 +43,32 @@ public final class BlockStateIdAccess {
|
||||
return ASSIGNED_IDS.inverse().get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* For platforms that don't have an internal ID system,
|
||||
* {@link BlockRegistry#getInternalBlockStateId(BlockState)} will return
|
||||
* {@link OptionalInt#empty()}. In those cases, we will use our own ID system,
|
||||
* since it's useful for other entries as well.
|
||||
* @return an unused ID in WorldEdit's ID tracker
|
||||
*/
|
||||
private static int provideUnusedWorldEditId() {
|
||||
return usedIds.nextClearBit(0);
|
||||
}
|
||||
|
||||
private static final BitSet usedIds = new BitSet();
|
||||
|
||||
public static void register(BlockState blockState, OptionalInt id) {
|
||||
if (id.isPresent()) {
|
||||
int i = id.getAsInt();
|
||||
int i = id.orElseGet(BlockStateIdAccess::provideUnusedWorldEditId);
|
||||
BlockState existing = ASSIGNED_IDS.inverse().get(i);
|
||||
checkState(existing == null || existing == blockState,
|
||||
"BlockState %s is using the same block ID (%s) as BlockState %s",
|
||||
blockState, i, existing);
|
||||
ASSIGNED_IDS.put(blockState, i);
|
||||
}
|
||||
usedIds.set(i);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
ASSIGNED_IDS.clear();
|
||||
usedIds.clear();
|
||||
}
|
||||
|
||||
private BlockStateIdAccess() {
|
||||
|
52
worldedit-core/src/main/java/com/sk89q/worldedit/math/BitMath.java
Normale Datei
52
worldedit-core/src/main/java/com/sk89q/worldedit/math/BitMath.java
Normale Datei
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.math;
|
||||
|
||||
public final class BitMath {
|
||||
|
||||
public static int mask(int bits) {
|
||||
return ~(~0 << bits);
|
||||
}
|
||||
|
||||
public static int unpackX(long packed) {
|
||||
return extractSigned(packed, 0, 26);
|
||||
}
|
||||
|
||||
public static int unpackZ(long packed) {
|
||||
return extractSigned(packed, 26, 26);
|
||||
}
|
||||
|
||||
public static int unpackY(long packed) {
|
||||
return extractSigned(packed, 26 + 26, 12);
|
||||
}
|
||||
|
||||
public static int extractSigned(long i, int shift, int bits) {
|
||||
return fixSign((int) (i >> shift) & mask(bits), bits);
|
||||
}
|
||||
|
||||
public static int fixSign(int i, int bits) {
|
||||
// Using https://stackoverflow.com/a/29266331/436524
|
||||
return i << (32 - bits) >> (32 - bits);
|
||||
}
|
||||
|
||||
private BitMath() {
|
||||
}
|
||||
|
||||
}
|
@ -24,6 +24,10 @@ import com.sk89q.worldedit.math.transform.AffineTransform;
|
||||
import java.util.Comparator;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.sk89q.worldedit.math.BitMath.mask;
|
||||
import static com.sk89q.worldedit.math.BitMath.unpackX;
|
||||
import static com.sk89q.worldedit.math.BitMath.unpackY;
|
||||
import static com.sk89q.worldedit.math.BitMath.unpackZ;
|
||||
|
||||
/**
|
||||
* An immutable 3-dimensional vector.
|
||||
@ -61,6 +65,31 @@ public final class BlockVector3 {
|
||||
return new BlockVector3(x, y, z);
|
||||
}
|
||||
|
||||
private static final int WORLD_XZ_MINMAX = 30_000_000;
|
||||
private static final int WORLD_Y_MAX = 4095;
|
||||
|
||||
private static boolean isHorizontallyInBounds(int h) {
|
||||
return -WORLD_XZ_MINMAX <= h && h <= WORLD_XZ_MINMAX;
|
||||
}
|
||||
|
||||
public static boolean isLongPackable(BlockVector3 location) {
|
||||
return isHorizontallyInBounds(location.getX()) &&
|
||||
isHorizontallyInBounds(location.getZ()) &&
|
||||
0 <= location.getY() && location.getY() <= WORLD_Y_MAX;
|
||||
}
|
||||
|
||||
public static void checkLongPackable(BlockVector3 location) {
|
||||
checkArgument(isLongPackable(location),
|
||||
"Location exceeds long packing limits: %s", location);
|
||||
}
|
||||
|
||||
private static final long BITS_26 = mask(26);
|
||||
private static final long BITS_12 = mask(12);
|
||||
|
||||
public static BlockVector3 fromLongPackedForm(long packed) {
|
||||
return at(unpackX(packed), unpackY(packed), unpackZ(packed));
|
||||
}
|
||||
|
||||
// thread-safe initialization idiom
|
||||
private static final class YzxOrderComparator {
|
||||
private static final Comparator<BlockVector3> YZX_ORDER =
|
||||
@ -94,6 +123,11 @@ public final class BlockVector3 {
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public long toLongPackedForm() {
|
||||
checkLongPackable(this);
|
||||
return (x & BITS_26) | ((z & BITS_26) << 26) | (((y & (long) BITS_12) << (26 + 26)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the X coordinate.
|
||||
*
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.math;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import static com.sk89q.worldedit.math.BlockVector2.COMPARING_GRID_ARRANGEMENT;
|
||||
|
||||
/**
|
||||
* Sort block positions by region, then chunk.
|
||||
*/
|
||||
public class RegionOptimizedChunkComparator {
|
||||
|
||||
private static final Comparator<BlockVector2> CHUNK_COMPARATOR =
|
||||
Comparator.comparing((BlockVector2 chunkPos) -> chunkPos.shr(5), COMPARING_GRID_ARRANGEMENT)
|
||||
.thenComparing(COMPARING_GRID_ARRANGEMENT);
|
||||
|
||||
public static final Comparator<BlockVector3> INSTANCE
|
||||
= Comparator.comparing(blockPos -> blockPos.toBlockVector2().shr(4), CHUNK_COMPARATOR);
|
||||
|
||||
private RegionOptimizedChunkComparator() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.math;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Sort block positions by region, chunk, and finally Y-Z-X.
|
||||
*/
|
||||
public class RegionOptimizedComparator {
|
||||
|
||||
public static final Comparator<BlockVector3> INSTANCE
|
||||
= RegionOptimizedChunkComparator.INSTANCE
|
||||
.thenComparing(BlockVector3.sortByCoordsYzx());
|
||||
|
||||
private RegionOptimizedComparator() {
|
||||
}
|
||||
|
||||
}
|
@ -118,6 +118,7 @@ public class PropertiesConfiguration extends LocalConfiguration {
|
||||
butcherMaxRadius = getInt("butcher-max-radius", butcherMaxRadius);
|
||||
allowSymlinks = getBool("allow-symbolic-links", allowSymlinks);
|
||||
serverSideCUI = getBool("server-side-cui", serverSideCUI);
|
||||
extendedYLimit = getBool("extended-y-limit", extendedYLimit);
|
||||
|
||||
LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15));
|
||||
|
||||
|
@ -124,6 +124,7 @@ public class YAMLConfiguration extends LocalConfiguration {
|
||||
String type = config.getString("shell-save-type", "").trim();
|
||||
shellSaveType = type.isEmpty() ? null : type;
|
||||
|
||||
extendedYLimit = config.getBoolean("compat.extended-y-limit", false);
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
|
@ -0,0 +1,427 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.sk89q.worldedit.math.BitMath.fixSign;
|
||||
import static com.sk89q.worldedit.math.BitMath.mask;
|
||||
|
||||
/**
|
||||
* A space-efficient map implementation for block locations.
|
||||
*/
|
||||
public class BlockMap extends AbstractMap<BlockVector3, BaseBlock> {
|
||||
|
||||
/* =========================
|
||||
IF YOU MAKE CHANGES TO THIS CLASS
|
||||
Re-run BlockMapTest with the blockmap.fulltesting=true system property.
|
||||
Or just temporarily remove the annotation disabling the related tests.
|
||||
========================= */
|
||||
|
||||
public static BlockMap create() {
|
||||
return new BlockMap();
|
||||
}
|
||||
|
||||
public static BlockMap copyOf(Map<? extends BlockVector3, ? extends BaseBlock> source) {
|
||||
return new BlockMap(source);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stores blocks by sub-dividing them into smaller groups.
|
||||
* A block location is 26 bits long for x + z, and usually
|
||||
* 8 bits for y, although mods such as cubic chunks may
|
||||
* expand this to infinite. We support up to 32 bits of y.
|
||||
*
|
||||
* Grouping key stores 20 bits x + z, 24 bits y.
|
||||
* Inner key stores 6 bits x + z, 8 bits y.
|
||||
* Order (lowest to highest) is x-z-y.
|
||||
*/
|
||||
|
||||
private static final long BITS_24 = mask(24);
|
||||
private static final long BITS_20 = mask(20);
|
||||
private static final int BITS_8 = mask(8);
|
||||
private static final int BITS_6 = mask(6);
|
||||
|
||||
private static long toGroupKey(BlockVector3 location) {
|
||||
return ((location.getX() >>> 6) & BITS_20)
|
||||
| (((location.getZ() >>> 6) & BITS_20) << 20)
|
||||
| (((location.getY() >>> 8) & BITS_24) << (20 + 20));
|
||||
}
|
||||
|
||||
private static int toInnerKey(BlockVector3 location) {
|
||||
return (location.getX() & BITS_6)
|
||||
| ((location.getZ() & BITS_6) << 6)
|
||||
| ((location.getY() & BITS_8) << (6 + 6));
|
||||
}
|
||||
|
||||
private static final long GROUP_X = BITS_20;
|
||||
private static final long GROUP_Z = BITS_20 << 20;
|
||||
private static final long GROUP_Y = BITS_24 << (20 + 20);
|
||||
private static final int INNER_X = BITS_6;
|
||||
private static final int INNER_Z = BITS_6 << 6;
|
||||
private static final int INNER_Y = BITS_8 << (6 + 6);
|
||||
|
||||
private static BlockVector3 reconstructLocation(long group, int inner) {
|
||||
int groupX = (int) ((group & GROUP_X) << 6);
|
||||
int x = fixSign(groupX | (inner & INNER_X), 26);
|
||||
int groupZ = (int) ((group & GROUP_Z) >>> (20 - 6));
|
||||
int z = fixSign(groupZ | ((inner & INNER_Z) >>> 6), 26);
|
||||
int groupY = (int) ((group & GROUP_Y) >>> (20 + 20 - 8));
|
||||
int y = groupY | ((inner & INNER_Y) >>> (6 + 6));
|
||||
return BlockVector3.at(x, y, z);
|
||||
}
|
||||
|
||||
private final Long2ObjectMap<SubBlockMap> maps = new Long2ObjectOpenHashMap<>(4, 1f);
|
||||
private Set<Entry<BlockVector3, BaseBlock>> entrySet;
|
||||
private Collection<BaseBlock> values;
|
||||
|
||||
private BlockMap() {
|
||||
}
|
||||
|
||||
private BlockMap(Map<? extends BlockVector3, ? extends BaseBlock> source) {
|
||||
putAll(source);
|
||||
}
|
||||
|
||||
private SubBlockMap getOrCreateMap(long groupKey) {
|
||||
return maps.computeIfAbsent(groupKey, k -> new SubBlockMap());
|
||||
}
|
||||
|
||||
private SubBlockMap getOrEmptyMap(long groupKey) {
|
||||
return maps.getOrDefault(groupKey, SubBlockMap.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the function the the map at {@code groupKey}, and if the function empties the map,
|
||||
* delete it from {@code maps}.
|
||||
*/
|
||||
private <R> R cleanlyModifyMap(long groupKey, Function<Int2ObjectMap<BaseBlock>, R> func) {
|
||||
SubBlockMap map = maps.get(groupKey);
|
||||
if (map != null) {
|
||||
R result = func.apply(map);
|
||||
if (map.isEmpty()) {
|
||||
maps.remove(groupKey);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
map = new SubBlockMap();
|
||||
R result = func.apply(map);
|
||||
if (!map.isEmpty()) {
|
||||
maps.put(groupKey, map);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock put(BlockVector3 key, BaseBlock value) {
|
||||
return getOrCreateMap(toGroupKey(key)).put(toInnerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock getOrDefault(Object key, BaseBlock defaultValue) {
|
||||
BlockVector3 vec = (BlockVector3) key;
|
||||
return getOrEmptyMap(toGroupKey(vec))
|
||||
.getOrDefault(toInnerKey(vec), defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super BlockVector3, ? super BaseBlock> action) {
|
||||
maps.forEach((groupKey, m) ->
|
||||
m.forEach((innerKey, block) ->
|
||||
action.accept(reconstructLocation(groupKey, innerKey), block)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super BlockVector3, ? super BaseBlock, ? extends BaseBlock> function) {
|
||||
maps.forEach((groupKey, m) ->
|
||||
m.replaceAll((innerKey, block) ->
|
||||
function.apply(reconstructLocation(groupKey, innerKey), block)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock putIfAbsent(BlockVector3 key, BaseBlock value) {
|
||||
return getOrCreateMap(toGroupKey(key)).putIfAbsent(toInnerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
BlockVector3 vec = (BlockVector3) key;
|
||||
return cleanlyModifyMap(toGroupKey(vec),
|
||||
map -> map.remove(toInnerKey(vec), value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(BlockVector3 key, BaseBlock oldValue, BaseBlock newValue) {
|
||||
return cleanlyModifyMap(toGroupKey(key),
|
||||
map -> map.replace(toInnerKey(key), oldValue, newValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock replace(BlockVector3 key, BaseBlock value) {
|
||||
return getOrCreateMap(toGroupKey(key)).replace(toInnerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock computeIfAbsent(BlockVector3 key, Function<? super BlockVector3, ? extends BaseBlock> mappingFunction) {
|
||||
return cleanlyModifyMap(toGroupKey(key),
|
||||
map -> map.computeIfAbsent(toInnerKey(key), ik -> mappingFunction.apply(key)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock computeIfPresent(BlockVector3 key, BiFunction<? super BlockVector3, ? super BaseBlock, ? extends BaseBlock> remappingFunction) {
|
||||
return cleanlyModifyMap(toGroupKey(key),
|
||||
map -> map.computeIfPresent(toInnerKey(key), (ik, block) -> remappingFunction.apply(key, block)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock compute(BlockVector3 key, BiFunction<? super BlockVector3, ? super BaseBlock, ? extends BaseBlock> remappingFunction) {
|
||||
return cleanlyModifyMap(toGroupKey(key),
|
||||
map -> map.compute(toInnerKey(key), (ik, block) -> remappingFunction.apply(key, block)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock merge(BlockVector3 key, BaseBlock value, BiFunction<? super BaseBlock, ? super BaseBlock, ? extends BaseBlock> remappingFunction) {
|
||||
return cleanlyModifyMap(toGroupKey(key),
|
||||
map -> map.merge(toInnerKey(key), value, remappingFunction));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<BlockVector3, BaseBlock>> entrySet() {
|
||||
Set<Entry<BlockVector3, BaseBlock>> es = entrySet;
|
||||
if (es == null) {
|
||||
entrySet = es = new AbstractSet<Entry<BlockVector3, BaseBlock>>() {
|
||||
@Override
|
||||
public Iterator<Entry<BlockVector3, BaseBlock>> iterator() {
|
||||
return new AbstractIterator<Entry<BlockVector3, BaseBlock>>() {
|
||||
|
||||
private final ObjectIterator<Long2ObjectMap.Entry<SubBlockMap>> primaryIterator
|
||||
= Long2ObjectMaps.fastIterator(maps);
|
||||
private long currentGroupKey;
|
||||
private ObjectIterator<Int2ObjectMap.Entry<BaseBlock>> secondaryIterator;
|
||||
|
||||
@Override
|
||||
protected Entry<BlockVector3, BaseBlock> computeNext() {
|
||||
if (secondaryIterator == null || !secondaryIterator.hasNext()) {
|
||||
if (!primaryIterator.hasNext()) {
|
||||
return endOfData();
|
||||
}
|
||||
|
||||
Long2ObjectMap.Entry<SubBlockMap> next = primaryIterator.next();
|
||||
currentGroupKey = next.getLongKey();
|
||||
secondaryIterator = Int2ObjectMaps.fastIterator(next.getValue());
|
||||
}
|
||||
Int2ObjectMap.Entry<BaseBlock> next = secondaryIterator.next();
|
||||
return new LazyEntry(currentGroupKey, next.getIntKey(), next.getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return BlockMap.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
return es;
|
||||
}
|
||||
|
||||
private final class LazyEntry implements Map.Entry<BlockVector3, BaseBlock> {
|
||||
|
||||
private final long groupKey;
|
||||
private final int innerKey;
|
||||
private BlockVector3 lazyKey;
|
||||
private BaseBlock value;
|
||||
|
||||
private LazyEntry(long groupKey, int innerKey, BaseBlock value) {
|
||||
this.groupKey = groupKey;
|
||||
this.innerKey = innerKey;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockVector3 getKey() {
|
||||
BlockVector3 result = lazyKey;
|
||||
if (result == null) {
|
||||
lazyKey = result = reconstructLocation(groupKey, innerKey);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock setValue(BaseBlock value) {
|
||||
this.value = value;
|
||||
return getOrCreateMap(groupKey).put(innerKey, value);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Map.Entry))
|
||||
return false;
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
if (o instanceof LazyEntry) {
|
||||
LazyEntry otherE = (LazyEntry) o;
|
||||
return otherE.groupKey == groupKey
|
||||
&& otherE.innerKey == innerKey
|
||||
&& Objects.equals(value, e.getValue());
|
||||
}
|
||||
return Objects.equals(getKey(), e.getKey()) && Objects.equals(value, e.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(getKey()) ^ Objects.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getKey() + "=" + getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return maps.values().stream().anyMatch(m -> m.containsValue(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
BlockVector3 vec = (BlockVector3) key;
|
||||
Map<Integer, BaseBlock> activeMap = maps.get(toGroupKey(vec));
|
||||
if (activeMap == null) {
|
||||
return false;
|
||||
}
|
||||
return activeMap.containsKey(toInnerKey(vec));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock get(Object key) {
|
||||
BlockVector3 vec = (BlockVector3) key;
|
||||
Map<Integer, BaseBlock> activeMap = maps.get(toGroupKey(vec));
|
||||
if (activeMap == null) {
|
||||
return null;
|
||||
}
|
||||
return activeMap.get(toInnerKey(vec));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock remove(Object key) {
|
||||
BlockVector3 vec = (BlockVector3) key;
|
||||
Map<Integer, BaseBlock> activeMap = maps.get(toGroupKey(vec));
|
||||
if (activeMap == null) {
|
||||
return null;
|
||||
}
|
||||
BaseBlock removed = activeMap.remove(toInnerKey(vec));
|
||||
if (activeMap.isEmpty()) {
|
||||
maps.remove(toGroupKey(vec));
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends BlockVector3, ? extends BaseBlock> m) {
|
||||
if (m instanceof BlockMap) {
|
||||
// optimize insertions:
|
||||
((BlockMap) m).maps.forEach((groupKey, map) ->
|
||||
getOrCreateMap(groupKey).putAll(map)
|
||||
);
|
||||
} else {
|
||||
super.putAll(m);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
maps.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return maps.values().stream().mapToInt(Map::size).sum();
|
||||
}
|
||||
|
||||
// no keySet override, since we can't really optimize it.
|
||||
// we can optimize values access though, by skipping BV construction.
|
||||
|
||||
@Override
|
||||
public Collection<BaseBlock> values() {
|
||||
Collection<BaseBlock> vs = values;
|
||||
if (vs == null) {
|
||||
values = vs = new AbstractCollection<BaseBlock>() {
|
||||
@Override
|
||||
public Iterator<BaseBlock> iterator() {
|
||||
return maps.values().stream()
|
||||
.flatMap(m -> m.values().stream())
|
||||
.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return BlockMap.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
return vs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof BlockMap) {
|
||||
// optimize by skipping entry translations:
|
||||
return maps.equals(((BlockMap) o).maps);
|
||||
}
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
// satisfy checkstyle
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
}
|
@ -19,74 +19,74 @@
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.util.LocatedBlock;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Wrapper around a list of blocks located in the world.
|
||||
*/
|
||||
public class LocatedBlockList implements Iterable<LocatedBlock> {
|
||||
|
||||
private final Map<BlockVector3, LocatedBlock> map = new LinkedHashMap<>();
|
||||
private final BlockMap blocks = BlockMap.create();
|
||||
private final PositionList order = PositionList.create(
|
||||
WorldEdit.getInstance().getConfiguration().extendedYLimit
|
||||
);
|
||||
|
||||
public LocatedBlockList() {
|
||||
}
|
||||
|
||||
public LocatedBlockList(Collection<? extends LocatedBlock> collection) {
|
||||
for (LocatedBlock locatedBlock : collection) {
|
||||
map.put(locatedBlock.getLocation(), locatedBlock);
|
||||
add(locatedBlock.getLocation(), locatedBlock.getBlock());
|
||||
}
|
||||
}
|
||||
|
||||
public void add(LocatedBlock setBlockCall) {
|
||||
checkNotNull(setBlockCall);
|
||||
map.put(setBlockCall.getLocation(), setBlockCall);
|
||||
add(setBlockCall.getLocation(), setBlockCall.getBlock());
|
||||
}
|
||||
|
||||
public <B extends BlockStateHolder<B>> void add(BlockVector3 location, B block) {
|
||||
add(new LocatedBlock(location, block.toBaseBlock()));
|
||||
blocks.put(location, block.toBaseBlock());
|
||||
order.add(location);
|
||||
}
|
||||
|
||||
public boolean containsLocation(BlockVector3 location) {
|
||||
return map.containsKey(location);
|
||||
return blocks.containsKey(location);
|
||||
}
|
||||
|
||||
public @Nullable BaseBlock get(BlockVector3 location) {
|
||||
return map.get(location).getBlock();
|
||||
return blocks.get(location);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
return order.size();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
map.clear();
|
||||
blocks.clear();
|
||||
order.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<LocatedBlock> iterator() {
|
||||
return map.values().iterator();
|
||||
return Iterators.transform(order.iterator(), position ->
|
||||
new LocatedBlock(position, blocks.get(position)));
|
||||
}
|
||||
|
||||
public Iterator<LocatedBlock> reverseIterator() {
|
||||
List<LocatedBlock> data = new ArrayList<>(map.values());
|
||||
Collections.reverse(data);
|
||||
return data.iterator();
|
||||
return Iterators.transform(order.reverseIterator(), position ->
|
||||
new LocatedBlock(position, blocks.get(position)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import it.unimi.dsi.fastutil.longs.LongListIterator;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.ToLongFunction;
|
||||
|
||||
class LongPositionList implements PositionList {
|
||||
|
||||
private final LongList delegate = new LongArrayList();
|
||||
|
||||
@Override
|
||||
public BlockVector3 get(int index) {
|
||||
return BlockVector3.fromLongPackedForm(delegate.getLong(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(BlockVector3 vector) {
|
||||
delegate.add(vector.toLongPackedForm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockVector3> iterator() {
|
||||
return new PositionIterator(delegate.iterator(),
|
||||
LongListIterator::hasNext,
|
||||
LongListIterator::nextLong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockVector3> reverseIterator() {
|
||||
return new PositionIterator(delegate.listIterator(size()),
|
||||
LongListIterator::hasPrevious,
|
||||
LongListIterator::previousLong);
|
||||
}
|
||||
|
||||
private static final class PositionIterator extends AbstractIterator<BlockVector3> {
|
||||
|
||||
private final LongListIterator iterator;
|
||||
private final Predicate<LongListIterator> hasNext;
|
||||
private final ToLongFunction<LongListIterator> next;
|
||||
|
||||
private PositionIterator(LongListIterator iterator,
|
||||
Predicate<LongListIterator> hasNext,
|
||||
ToLongFunction<LongListIterator> next) {
|
||||
this.iterator = iterator;
|
||||
this.hasNext = hasNext;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BlockVector3 computeNext() {
|
||||
return hasNext.test(iterator)
|
||||
? BlockVector3.fromLongPackedForm(next.applyAsLong(iterator))
|
||||
: endOfData();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
interface PositionList {
|
||||
|
||||
static PositionList create(boolean extendedYLimit) {
|
||||
if (extendedYLimit) {
|
||||
return new VectorPositionList();
|
||||
}
|
||||
return new LongPositionList();
|
||||
}
|
||||
|
||||
BlockVector3 get(int index);
|
||||
|
||||
void add(BlockVector3 vector);
|
||||
|
||||
int size();
|
||||
|
||||
void clear();
|
||||
|
||||
Iterator<BlockVector3> iterator();
|
||||
|
||||
Iterator<BlockVector3> reverseIterator();
|
||||
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockState;
|
||||
import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.AbstractObjectSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Int-to-BaseBlock map, but with optimizations for common cases.
|
||||
*/
|
||||
class SubBlockMap extends AbstractInt2ObjectMap<BaseBlock> {
|
||||
|
||||
private static boolean hasInt(BlockState b) {
|
||||
return BlockStateIdAccess.getBlockStateId(b).isPresent();
|
||||
}
|
||||
|
||||
private static boolean isUncommon(BaseBlock block) {
|
||||
return block.hasNbtData() || !hasInt(block.toImmutableState());
|
||||
}
|
||||
|
||||
private static int assumeAsInt(BlockState b) {
|
||||
return BlockStateIdAccess.getBlockStateId(b)
|
||||
.orElseThrow(() -> new IllegalStateException("Block state " + b + " did not have an ID"));
|
||||
}
|
||||
|
||||
private static BaseBlock assumeAsBlock(int id) {
|
||||
if (id == Integer.MIN_VALUE) {
|
||||
return null;
|
||||
}
|
||||
BlockState state = BlockStateIdAccess.getBlockStateById(id);
|
||||
if (state == null) {
|
||||
throw new IllegalStateException("No state for ID " + id);
|
||||
}
|
||||
return state.toBaseBlock();
|
||||
}
|
||||
|
||||
static final SubBlockMap EMPTY = new SubBlockMap();
|
||||
|
||||
private final Int2IntMap commonMap = new Int2IntOpenHashMap(64, 1f);
|
||||
private final Int2ObjectMap<BaseBlock> uncommonMap = new Int2ObjectOpenHashMap<>(1, 1f);
|
||||
|
||||
{
|
||||
commonMap.defaultReturnValue(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return commonMap.size() + uncommonMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectSet<Entry<BaseBlock>> int2ObjectEntrySet() {
|
||||
return new AbstractObjectSet<Entry<BaseBlock>>() {
|
||||
@Override
|
||||
public ObjectIterator<Entry<BaseBlock>> iterator() {
|
||||
return new ObjectIterator<Entry<BaseBlock>>() {
|
||||
|
||||
private final ObjectIterator<Int2IntMap.Entry> commonIter
|
||||
= Int2IntMaps.fastIterator(commonMap);
|
||||
private final ObjectIterator<Int2ObjectMap.Entry<BaseBlock>> uncommonIter
|
||||
= Int2ObjectMaps.fastIterator(uncommonMap);
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return commonIter.hasNext() || uncommonIter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<BaseBlock> next() {
|
||||
if (commonIter.hasNext()) {
|
||||
Int2IntMap.Entry e = commonIter.next();
|
||||
return new BasicEntry<>(
|
||||
e.getIntKey(), assumeAsBlock(e.getIntValue())
|
||||
);
|
||||
}
|
||||
if (uncommonIter.hasNext()) {
|
||||
return uncommonIter.next();
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return SubBlockMap.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock get(int key) {
|
||||
int oldId = commonMap.get(key);
|
||||
if (oldId == Integer.MIN_VALUE) {
|
||||
return uncommonMap.get(key);
|
||||
}
|
||||
return assumeAsBlock(oldId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(int k) {
|
||||
return commonMap.containsKey(k) || uncommonMap.containsKey(k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object v) {
|
||||
BaseBlock block = (BaseBlock) v;
|
||||
if (isUncommon(block)) {
|
||||
return uncommonMap.containsValue(block);
|
||||
}
|
||||
return commonMap.containsValue(assumeAsInt(block.toImmutableState()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock put(int key, BaseBlock value) {
|
||||
if (isUncommon(value)) {
|
||||
BaseBlock old = uncommonMap.put(key, value);
|
||||
if (old == null) {
|
||||
// ensure common doesn't have the entry too
|
||||
int oldId = commonMap.remove(key);
|
||||
return assumeAsBlock(oldId);
|
||||
}
|
||||
return old;
|
||||
}
|
||||
int oldId = commonMap.put(key, assumeAsInt(value.toImmutableState()));
|
||||
return assumeAsBlock(oldId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBlock remove(int key) {
|
||||
int removed = commonMap.remove(key);
|
||||
if (removed == Integer.MIN_VALUE) {
|
||||
return uncommonMap.remove(key);
|
||||
}
|
||||
return assumeAsBlock(removed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super Integer, ? super BaseBlock, ? extends BaseBlock> function) {
|
||||
for (ObjectIterator<Int2IntMap.Entry> iter = Int2IntMaps.fastIterator(commonMap);
|
||||
iter.hasNext(); ) {
|
||||
Int2IntMap.Entry next = iter.next();
|
||||
BaseBlock value = function.apply(next.getIntKey(), assumeAsBlock(next.getIntValue()));
|
||||
if (isUncommon(value)) {
|
||||
uncommonMap.put(next.getIntKey(), value);
|
||||
iter.remove();
|
||||
} else {
|
||||
next.setValue(assumeAsInt(value.toImmutableState()));
|
||||
}
|
||||
}
|
||||
for (ObjectIterator<Int2ObjectMap.Entry<BaseBlock>> iter = Int2ObjectMaps.fastIterator(uncommonMap);
|
||||
iter.hasNext(); ) {
|
||||
Int2ObjectMap.Entry<BaseBlock> next = iter.next();
|
||||
BaseBlock value = function.apply(next.getIntKey(), next.getValue());
|
||||
if (isUncommon(value)) {
|
||||
next.setValue(value);
|
||||
} else {
|
||||
commonMap.put(next.getIntKey(), assumeAsInt(value.toImmutableState()));
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.ints.IntListIterator;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
class VectorPositionList implements PositionList {
|
||||
|
||||
private final IntList delegate = new IntArrayList();
|
||||
|
||||
@Override
|
||||
public BlockVector3 get(int index) {
|
||||
int ri = index * 3;
|
||||
return BlockVector3.at(
|
||||
delegate.getInt(ri),
|
||||
delegate.getInt(ri + 1),
|
||||
delegate.getInt(ri + 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(BlockVector3 vector) {
|
||||
delegate.add(vector.getX());
|
||||
delegate.add(vector.getY());
|
||||
delegate.add(vector.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockVector3> iterator() {
|
||||
return new AbstractIterator<BlockVector3>() {
|
||||
|
||||
private final IntIterator iterator = delegate.iterator();
|
||||
|
||||
@Override
|
||||
protected BlockVector3 computeNext() {
|
||||
if (!iterator.hasNext()) {
|
||||
return endOfData();
|
||||
}
|
||||
return BlockVector3.at(
|
||||
iterator.nextInt(),
|
||||
iterator.nextInt(),
|
||||
iterator.nextInt());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockVector3> reverseIterator() {
|
||||
return new AbstractIterator<BlockVector3>() {
|
||||
|
||||
private final IntListIterator iterator = delegate.listIterator(delegate.size());
|
||||
|
||||
@Override
|
||||
protected BlockVector3 computeNext() {
|
||||
if (!iterator.hasPrevious()) {
|
||||
return endOfData();
|
||||
}
|
||||
return BlockVector3.at(
|
||||
iterator.previousInt(),
|
||||
iterator.previousInt(),
|
||||
iterator.previousInt());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.concurrency;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Thread-safe lazy reference.
|
||||
*/
|
||||
public class LazyReference<T> {
|
||||
|
||||
public static <T> LazyReference<T> from(Supplier<T> valueComputation) {
|
||||
return new LazyReference<>(valueComputation);
|
||||
}
|
||||
|
||||
// Memory saving technique: hold the computation info in the same reference field that we'll
|
||||
// put the value into, so the memory possibly retained by those parts is GC'able as soon as
|
||||
// it's no longer needed.
|
||||
|
||||
private static final class RefInfo<T> {
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Supplier<T> valueComputation;
|
||||
|
||||
private RefInfo(Supplier<T> valueComputation) {
|
||||
this.valueComputation = valueComputation;
|
||||
}
|
||||
}
|
||||
|
||||
private Object value;
|
||||
|
||||
private LazyReference(Supplier<T> valueComputation) {
|
||||
this.value = new RefInfo<>(valueComputation);
|
||||
}
|
||||
|
||||
// casts are safe, value is either RefInfo or T
|
||||
@SuppressWarnings("unchecked")
|
||||
public T getValue() {
|
||||
Object v = value;
|
||||
if (!(v instanceof RefInfo)) {
|
||||
return (T) v;
|
||||
}
|
||||
RefInfo<T> refInfo = (RefInfo<T>) v;
|
||||
refInfo.lock.lock();
|
||||
try {
|
||||
v = value;
|
||||
if (!(v instanceof RefInfo)) {
|
||||
return (T) v;
|
||||
}
|
||||
value = v = refInfo.valueComputation.get();
|
||||
return (T) v;
|
||||
} finally {
|
||||
refInfo.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -26,9 +26,7 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import com.sk89q.worldedit.registry.state.Property;
|
||||
import com.sk89q.worldedit.world.registry.BlockRegistry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -60,7 +58,6 @@ public class BlockState implements BlockStateHolder<BlockState> {
|
||||
}
|
||||
|
||||
static Map<Map<Property<?>, Object>, BlockState> generateStateMap(BlockType blockType) {
|
||||
BlockRegistry registry = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getRegistries().getBlockRegistry();
|
||||
Map<Map<Property<?>, Object>, BlockState> stateMap = new LinkedHashMap<>();
|
||||
List<? extends Property<?>> properties = blockType.getProperties();
|
||||
|
||||
|
@ -19,28 +19,26 @@
|
||||
|
||||
package com.sk89q.worldedit.world.block;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import com.sk89q.worldedit.registry.Keyed;
|
||||
import com.sk89q.worldedit.registry.NamespacedRegistry;
|
||||
import com.sk89q.worldedit.registry.state.Property;
|
||||
import com.sk89q.worldedit.util.concurrency.LazyReference;
|
||||
import com.sk89q.worldedit.world.item.ItemType;
|
||||
import com.sk89q.worldedit.world.item.ItemTypes;
|
||||
import com.sk89q.worldedit.world.registry.BlockMaterial;
|
||||
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
public class BlockType implements Keyed {
|
||||
|
||||
@ -48,11 +46,18 @@ public class BlockType implements Keyed {
|
||||
|
||||
private final String id;
|
||||
private final Function<BlockState, BlockState> values;
|
||||
private final AtomicReference<BlockState> defaultState = new AtomicReference<>();
|
||||
private final AtomicReference<FuzzyBlockState> emptyFuzzy = new AtomicReference<>();
|
||||
private final AtomicReference<Map<String, ? extends Property<?>>> properties = new AtomicReference<>();
|
||||
private final AtomicReference<BlockMaterial> blockMaterial = new AtomicReference<>();
|
||||
private final AtomicReference<Map<Map<Property<?>, Object>, BlockState>> blockStatesMap = new AtomicReference<>();
|
||||
private final LazyReference<BlockState> defaultState
|
||||
= LazyReference.from(this::computeDefaultState);
|
||||
private final LazyReference<FuzzyBlockState> emptyFuzzy
|
||||
= LazyReference.from(() -> new FuzzyBlockState(this));
|
||||
private final LazyReference<Map<String, ? extends Property<?>>> properties
|
||||
= LazyReference.from(() -> ImmutableMap.copyOf(WorldEdit.getInstance().getPlatformManager()
|
||||
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getProperties(this)));
|
||||
private final LazyReference<BlockMaterial> blockMaterial
|
||||
= LazyReference.from(() -> WorldEdit.getInstance().getPlatformManager()
|
||||
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getMaterial(this));
|
||||
private final LazyReference<Map<Map<Property<?>, Object>, BlockState>> blockStatesMap
|
||||
= LazyReference.from(() -> BlockState.generateStateMap(this));
|
||||
|
||||
public BlockType(String id) {
|
||||
this(id, null);
|
||||
@ -67,24 +72,16 @@ public class BlockType implements Keyed {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
private <T> T updateField(AtomicReference<T> field, Supplier<T> value) {
|
||||
T result = field.get();
|
||||
if (result == null) {
|
||||
// swap in new value, if someone doesn't beat us
|
||||
T update = value.get();
|
||||
if (field.compareAndSet(null, update)) {
|
||||
// use ours
|
||||
result = update;
|
||||
} else {
|
||||
// update to real value
|
||||
result = field.get();
|
||||
private BlockState computeDefaultState() {
|
||||
BlockState defaultState = Iterables.getFirst(getBlockStatesMap().values(), null);
|
||||
if (values != null) {
|
||||
defaultState = values.apply(defaultState);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return defaultState;
|
||||
}
|
||||
|
||||
private Map<Map<Property<?>, Object>, BlockState> getBlockStatesMap() {
|
||||
return updateField(blockStatesMap, () -> BlockState.generateStateMap(this));
|
||||
return blockStatesMap.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,8 +114,7 @@ public class BlockType implements Keyed {
|
||||
* @return The properties map
|
||||
*/
|
||||
public Map<String, ? extends Property<?>> getPropertyMap() {
|
||||
return updateField(properties, () -> ImmutableMap.copyOf(WorldEdit.getInstance().getPlatformManager()
|
||||
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getProperties(this)));
|
||||
return properties.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,17 +146,11 @@ public class BlockType implements Keyed {
|
||||
* @return The default state
|
||||
*/
|
||||
public BlockState getDefaultState() {
|
||||
return updateField(defaultState, () -> {
|
||||
BlockState defaultState = new ArrayList<>(getBlockStatesMap().values()).get(0);
|
||||
if (values != null) {
|
||||
defaultState = values.apply(defaultState);
|
||||
}
|
||||
return defaultState;
|
||||
});
|
||||
return defaultState.getValue();
|
||||
}
|
||||
|
||||
public FuzzyBlockState getFuzzyMatcher() {
|
||||
return updateField(emptyFuzzy, () -> new FuzzyBlockState(this));
|
||||
return emptyFuzzy.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,8 +198,7 @@ public class BlockType implements Keyed {
|
||||
* @return The material
|
||||
*/
|
||||
public BlockMaterial getMaterial() {
|
||||
return updateField(blockMaterial, () -> WorldEdit.getInstance().getPlatformManager()
|
||||
.queryCapability(Capability.GAME_HOOKS).getRegistries().getBlockRegistry().getMaterial(this));
|
||||
return blockMaterial.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,9 +27,9 @@ import com.sk89q.worldedit.internal.expression.parser.ParserException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import static java.lang.Math.atan2;
|
||||
import static java.lang.Math.sin;
|
||||
@ -37,12 +37,16 @@ import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ExpressionTest {
|
||||
|
||||
private Platform mockPlat = mock(Platform.class);
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
Platform mockPlat = Mockito.mock(Platform.class);
|
||||
Mockito.when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() {
|
||||
when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() {
|
||||
@Override
|
||||
public void load() {
|
||||
}
|
||||
@ -50,6 +54,11 @@ public class ExpressionTest {
|
||||
WorldEdit.getInstance().getPlatformManager().register(mockPlat);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
WorldEdit.getInstance().getPlatformManager().unregister(mockPlat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluate() throws ExpressionException {
|
||||
// check
|
||||
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util;
|
||||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
import org.junit.jupiter.params.support.AnnotationConsumer;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Argument provider for various vectors.
|
||||
*/
|
||||
public final class VariedVectorsProvider implements ArgumentsProvider, AnnotationConsumer<VariedVectorsProvider.Test> {
|
||||
|
||||
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@ArgumentsSource(VariedVectorsProvider.class)
|
||||
@ParameterizedTest(name = ParameterizedTest.ARGUMENTS_PLACEHOLDER)
|
||||
public @interface Test {
|
||||
|
||||
/**
|
||||
* If {@code true}, provide a non-matching vector from
|
||||
* the existing vectors set as well. This will nearly
|
||||
* square the number of tests executed, since it will
|
||||
* test <em>every</em> non-matching vector.
|
||||
*/
|
||||
boolean provideNonMatching() default false;
|
||||
|
||||
}
|
||||
|
||||
private static final int WORLD_XZ_MINMAX = 30_000_000;
|
||||
private static final long WORLD_Y_MAX = Integer.MAX_VALUE;
|
||||
private static final long WORLD_Y_MIN = Integer.MIN_VALUE;
|
||||
|
||||
// For better coverage assurance, increase these values for a local Gradle run.
|
||||
// Don't do it for IntelliJ, it'll probably run out of memory.
|
||||
private static final int DIVISIONS_XZ = Integer.getInteger("variedvecs.divisions.xz", 5);
|
||||
private static final int DIVISIONS_Y = Integer.getInteger("variedvecs.divisions.y", 5);
|
||||
|
||||
private static final int XZ_STEP = (WORLD_XZ_MINMAX * 2) / DIVISIONS_XZ;
|
||||
private static final long Y_STEP = (WORLD_Y_MAX * 2) / DIVISIONS_Y;
|
||||
|
||||
private static final Set<BlockVector3> ALWAYS_INCLUDE =
|
||||
ImmutableSet.of(BlockVector3.ZERO, BlockVector3.ONE,
|
||||
BlockVector3.at(-WORLD_XZ_MINMAX, 0, -WORLD_XZ_MINMAX),
|
||||
BlockVector3.at(WORLD_XZ_MINMAX, 0, WORLD_XZ_MINMAX),
|
||||
BlockVector3.at(-WORLD_XZ_MINMAX, WORLD_Y_MAX, -WORLD_XZ_MINMAX),
|
||||
BlockVector3.at(WORLD_XZ_MINMAX, WORLD_Y_MAX, WORLD_XZ_MINMAX));
|
||||
|
||||
private boolean provideNonMatching;
|
||||
|
||||
@Override
|
||||
public void accept(Test test) {
|
||||
provideNonMatching = test.provideNonMatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||
if (provideNonMatching) {
|
||||
return makeVectorsStream()
|
||||
.flatMap(vec -> makeVectorsStream().filter(v -> !v.equals(vec))
|
||||
.map(v -> Arguments.of(vec, v)));
|
||||
}
|
||||
return makeVectorsStream().map(Arguments::of);
|
||||
}
|
||||
|
||||
public static Stream<BlockVector3> makeVectorsStream() {
|
||||
return Stream.concat(
|
||||
ALWAYS_INCLUDE.stream(),
|
||||
Streams.stream(generateVectors()).filter(v -> !ALWAYS_INCLUDE.contains(v))
|
||||
);
|
||||
}
|
||||
|
||||
private static Iterator<BlockVector3> generateVectors() {
|
||||
return new AbstractIterator<BlockVector3>() {
|
||||
|
||||
private int x = -WORLD_XZ_MINMAX + 1;
|
||||
private int z = -WORLD_XZ_MINMAX + 1;
|
||||
private long y = WORLD_Y_MAX;
|
||||
|
||||
@Override
|
||||
protected BlockVector3 computeNext() {
|
||||
if (x > WORLD_XZ_MINMAX) {
|
||||
return endOfData();
|
||||
}
|
||||
BlockVector3 newVector = BlockVector3.at(x, (int) y, z);
|
||||
y += Y_STEP;
|
||||
if (y > WORLD_Y_MAX) {
|
||||
y = 0;
|
||||
z += XZ_STEP;
|
||||
if (z > WORLD_XZ_MINMAX) {
|
||||
z = -WORLD_XZ_MINMAX;
|
||||
x += XZ_STEP;
|
||||
}
|
||||
}
|
||||
return newVector;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,588 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.util.collection;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import com.sk89q.worldedit.extension.platform.Platform;
|
||||
import com.sk89q.worldedit.extension.platform.PlatformManager;
|
||||
import com.sk89q.worldedit.extension.platform.Preference;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.registry.Registry;
|
||||
import com.sk89q.worldedit.util.VariedVectorsProvider;
|
||||
import com.sk89q.worldedit.world.block.BaseBlock;
|
||||
import com.sk89q.worldedit.world.block.BlockType;
|
||||
import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import com.sk89q.worldedit.world.registry.BundledRegistries;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeFalse;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("An ordered block map")
|
||||
class BlockMapTest {
|
||||
|
||||
private static Platform mockedPlatform = mock(Platform.class);
|
||||
|
||||
@BeforeAll
|
||||
static void setupFakePlatform() {
|
||||
when(mockedPlatform.getRegistries()).thenReturn(new BundledRegistries() {
|
||||
});
|
||||
when(mockedPlatform.getCapabilities()).thenReturn(ImmutableMap.of(
|
||||
Capability.WORLD_EDITING, Preference.PREFERRED,
|
||||
Capability.GAME_HOOKS, Preference.PREFERRED
|
||||
));
|
||||
PlatformManager platformManager = WorldEdit.getInstance().getPlatformManager();
|
||||
platformManager.register(mockedPlatform);
|
||||
|
||||
registerBlock("minecraft:air");
|
||||
registerBlock("minecraft:oak_wood");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownFakePlatform() throws Exception {
|
||||
WorldEdit.getInstance().getPlatformManager().unregister(mockedPlatform);
|
||||
Field map = Registry.class.getDeclaredField("map");
|
||||
map.setAccessible(true);
|
||||
((Map<?, ?>) map.get(BlockType.REGISTRY)).clear();
|
||||
}
|
||||
|
||||
private static void registerBlock(String id) {
|
||||
BlockType.REGISTRY.register(id, new BlockType(id));
|
||||
}
|
||||
|
||||
@Mock
|
||||
private Function<? super BlockVector3, ? extends BaseBlock> function;
|
||||
@Mock
|
||||
private BiFunction<? super BlockVector3, ? super BaseBlock, ? extends BaseBlock> biFunction;
|
||||
@Mock
|
||||
private BiFunction<? super BaseBlock, ? super BaseBlock, ? extends BaseBlock> mergeFunction;
|
||||
@Mock
|
||||
private BiConsumer<? super BlockVector3, ? super BaseBlock> biConsumer;
|
||||
|
||||
private final BaseBlock air = checkNotNull(BlockTypes.AIR).getDefaultState().toBaseBlock();
|
||||
private final BaseBlock oakWood = checkNotNull(BlockTypes.OAK_WOOD).getDefaultState().toBaseBlock();
|
||||
|
||||
private BlockMap map = BlockMap.create();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("throws ClassCastException if invalid argument to get")
|
||||
void throwsFromGetOnInvalidArgument() {
|
||||
assertThrows(ClassCastException.class, () -> map.get(""));
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("when created")
|
||||
class WhenCreated {
|
||||
|
||||
@Test
|
||||
@DisplayName("is empty")
|
||||
void isEmpty() {
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("is equal to another empty map")
|
||||
void isEqualToEmptyMap() {
|
||||
assertEquals(ImmutableMap.of(), map);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("has the same hashCode as another empty map")
|
||||
void isHashCodeEqualToEmptyMap() {
|
||||
assertEquals(ImmutableMap.of().hashCode(), map.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("returns `null` from get")
|
||||
void returnsNullFromGet() {
|
||||
assertNull(map.get(BlockVector3.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("contains no keys")
|
||||
void containsNoKeys() {
|
||||
assertEquals(0, map.keySet().size());
|
||||
assertFalse(map.containsKey(BlockVector3.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("contains no values")
|
||||
void containsNoValues() {
|
||||
assertEquals(0, map.values().size());
|
||||
assertFalse(map.containsValue(air));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("contains no entries")
|
||||
void containsNoEntries() {
|
||||
assertEquals(0, map.entrySet().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("returns the default value from getOrDefault")
|
||||
void returnsDefaultFromGetOrDefault() {
|
||||
assertEquals(air, map.getOrDefault(BlockVector3.ZERO, air));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("never calls the forEach action")
|
||||
void neverCallsForEachAction() {
|
||||
map.forEach(biConsumer);
|
||||
verifyZeroInteractions(biConsumer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("never calls the replaceAll function")
|
||||
void neverCallsReplaceAllFunction() {
|
||||
map.replaceAll(biFunction);
|
||||
verifyZeroInteractions(biFunction);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("inserts on putIfAbsent")
|
||||
void insertOnPutIfAbsent() {
|
||||
assertNull(map.putIfAbsent(BlockVector3.ZERO, air));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(BlockVector3.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("remove(key) returns null")
|
||||
void removeKeyReturnsNull() {
|
||||
assertNull(map.remove(BlockVector3.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("remove(key, value) returns false")
|
||||
void removeKeyValueReturnsFalse() {
|
||||
assertFalse(map.remove(BlockVector3.ZERO, air));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("does nothing on replace")
|
||||
void doesNothingOnReplace() {
|
||||
assertNull(map.replace(BlockVector3.ZERO, air));
|
||||
assertEquals(0, map.size());
|
||||
assertFalse(map.replace(BlockVector3.ZERO, null, air));
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("inserts on computeIfAbsent")
|
||||
void insertOnComputeIfAbsent() {
|
||||
assertEquals(air, map.computeIfAbsent(BlockVector3.ZERO, k -> air));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(BlockVector3.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("inserts on compute")
|
||||
void insertOnCompute() {
|
||||
assertEquals(air, map.compute(BlockVector3.ZERO, (k, v) -> air));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(BlockVector3.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("does nothing on computeIfPresent")
|
||||
void doesNothingOnComputeIfPresent() {
|
||||
assertNull(map.computeIfPresent(BlockVector3.ZERO, (k, v) -> air));
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("inserts on merge, without calling merge function")
|
||||
void insertsOnMerge() {
|
||||
assertEquals(air, map.merge(BlockVector3.ZERO, air, mergeFunction));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(BlockVector3.ZERO));
|
||||
verifyZeroInteractions(mergeFunction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("after having an entry added")
|
||||
@EnabledIfSystemProperty(named = "blockmap.fulltesting", matches = "true")
|
||||
class AfterEntryAdded {
|
||||
|
||||
// Note: This section of tests would really benefit from
|
||||
// being able to parameterize classes. It's not part of JUnit
|
||||
// yet though: https://github.com/junit-team/junit5/issues/878
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("has a size of one")
|
||||
void hasSizeOne(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(1, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("is equal to another map with the same entry")
|
||||
void isEqualToSimilarMap(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(ImmutableMap.of(vec, air), map);
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("is not equal to another map with a different key")
|
||||
void isNotEqualToDifferentKeyMap(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertNotEquals(ImmutableMap.of(nonMatch, air), map);
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("is not equal to another map with a different value")
|
||||
void isNotEqualToDifferentValueMap(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertNotEquals(ImmutableMap.of(vec, oakWood), map);
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("is not equal to an empty map")
|
||||
void isNotEqualToEmptyMap(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertNotEquals(ImmutableMap.of(), map);
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("has the same hashCode as another map with the same entry")
|
||||
void isHashCodeEqualToSimilarMap(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(ImmutableMap.of(vec, air).hashCode(), map.hashCode());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("has a different hashCode from another map with a different key")
|
||||
void isHashCodeNotEqualToDifferentKeyMap(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
assumeFalse(vec.hashCode() == nonMatch.hashCode(),
|
||||
"Vectors have equivalent hashCodes, maps will too.");
|
||||
map.put(vec, air);
|
||||
assertNotEquals(ImmutableMap.of(nonMatch, air).hashCode(), map.hashCode());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("has a different hashCode from another map with a different value")
|
||||
void isHashCodeNotEqualToDifferentValueMap(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertNotEquals(ImmutableMap.of(vec, oakWood).hashCode(), map.hashCode());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("has a different hashCode from an empty map")
|
||||
void isHashCodeNotEqualToEmptyMap(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertNotEquals(ImmutableMap.of().hashCode(), map.hashCode());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("returns value from get")
|
||||
void returnsValueFromGet(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(air, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("returns `null` from get with different key")
|
||||
void returnsValueFromGet(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertNotEquals(air, map.get(nonMatch));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("contains the key")
|
||||
void containsTheKey(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(1, map.keySet().size());
|
||||
assertTrue(map.keySet().contains(vec));
|
||||
assertTrue(map.containsKey(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("contains the value")
|
||||
void containsTheValue(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(1, map.values().size());
|
||||
assertTrue(map.values().contains(air));
|
||||
assertTrue(map.containsValue(air));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("contains the entry")
|
||||
void containsTheEntry(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(1, map.entrySet().size());
|
||||
assertEquals(new AbstractMap.SimpleImmutableEntry<>(vec, air), map.entrySet().iterator().next());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("returns the provided value from getOrDefault")
|
||||
void returnsProvidedFromGetOrDefault(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(air, map.getOrDefault(vec, oakWood));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("returns the default value from getOrDefault with a different key")
|
||||
void returnsDefaultFromGetOrDefaultWrongKey(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertEquals(oakWood, map.getOrDefault(nonMatch, oakWood));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("calls the forEach action once")
|
||||
void neverCallsForEachAction(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
map.forEach(biConsumer);
|
||||
verify(biConsumer).accept(vec, air);
|
||||
verifyNoMoreInteractions(biConsumer);
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("replaces value using replaceAll")
|
||||
void neverCallsReplaceAllFunction(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
map.replaceAll((v, b) -> oakWood);
|
||||
assertEquals(oakWood, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("does not insert on `putIfAbsent`")
|
||||
void noInsertOnPutIfAbsent(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(air, map.putIfAbsent(vec, oakWood));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("inserts on `putIfAbsent` to a different key")
|
||||
void insertOnPutIfAbsentDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertNull(map.putIfAbsent(nonMatch, oakWood));
|
||||
assertEquals(2, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
assertEquals(oakWood, map.get(nonMatch));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("remove(key) returns the old value")
|
||||
void removeKeyReturnsOldValue(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(air, map.remove(vec));
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("remove(nonMatch) returns null")
|
||||
void removeNonMatchingKeyReturnsNull(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertNull(map.remove(nonMatch));
|
||||
assertEquals(1, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("remove(key, value) returns true")
|
||||
void removeKeyValueReturnsTrue(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertTrue(map.remove(vec, air));
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("remove(key, value) returns false for wrong value")
|
||||
void removeKeyValueReturnsFalseWrongValue(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertFalse(map.remove(vec, oakWood));
|
||||
assertEquals(1, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("replaces value at key")
|
||||
void replacesValueAtKey(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
|
||||
assertEquals(air, map.replace(vec, oakWood));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(oakWood, map.get(vec));
|
||||
|
||||
assertTrue(map.replace(vec, oakWood, air));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("does not replace value at different key")
|
||||
void doesNotReplaceAtDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
|
||||
assertNull(map.replace(nonMatch, oakWood));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
|
||||
assertFalse(map.replace(nonMatch, air, oakWood));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("does not insert on computeIfAbsent")
|
||||
void doesNotInsertComputeIfAbsent(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(air, map.computeIfAbsent(vec, k -> {
|
||||
assertEquals(vec, k);
|
||||
return oakWood;
|
||||
}));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("inserts on computeIfAbsent with different key")
|
||||
void insertsOnComputeIfAbsentDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertEquals(oakWood, map.computeIfAbsent(nonMatch, k -> {
|
||||
assertEquals(nonMatch, k);
|
||||
return oakWood;
|
||||
}));
|
||||
assertEquals(2, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
assertEquals(oakWood, map.get(nonMatch));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("replaces on compute")
|
||||
void replaceOnCompute(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(oakWood, map.compute(vec, (k, v) -> {
|
||||
assertEquals(vec, k);
|
||||
assertEquals(air, v);
|
||||
return oakWood;
|
||||
}));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(oakWood, map.get(vec));
|
||||
assertNull(map.compute(vec, (k, v) -> null));
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test(provideNonMatching = true)
|
||||
@DisplayName("inserts on compute with different key")
|
||||
void insertOnComputeDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) {
|
||||
map.put(vec, air);
|
||||
assertEquals(oakWood, map.compute(nonMatch, (k, v) -> {
|
||||
assertEquals(nonMatch, k);
|
||||
assertNull(v);
|
||||
return oakWood;
|
||||
}));
|
||||
assertEquals(2, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
assertEquals(oakWood, map.get(nonMatch));
|
||||
assertNull(map.compute(nonMatch, (k, v) -> null));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(air, map.get(vec));
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("replaces on computeIfPresent")
|
||||
void replacesOnComputeIfPresent(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(oakWood, map.computeIfPresent(vec, (k, v) -> {
|
||||
assertEquals(vec, k);
|
||||
assertEquals(air, v);
|
||||
return oakWood;
|
||||
}));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(oakWood, map.get(vec));
|
||||
assertNull(map.computeIfPresent(vec, (k, v) -> null));
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@VariedVectorsProvider.Test
|
||||
@DisplayName("inserts on merge, with call to merge function")
|
||||
void insertsOnMerge(BlockVector3 vec) {
|
||||
map.put(vec, air);
|
||||
assertEquals(oakWood, map.merge(vec, oakWood, (o, n) -> {
|
||||
assertEquals(air, o);
|
||||
assertEquals(oakWood, n);
|
||||
return n;
|
||||
}));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(oakWood, map.get(vec));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("contains all inserted vectors")
|
||||
void containsAllInsertedVectors() {
|
||||
Set<BlockVector3> allVectors = VariedVectorsProvider.makeVectorsStream().collect(Collectors.toSet());
|
||||
for (BlockVector3 vec : allVectors) {
|
||||
map.put(vec, air);
|
||||
}
|
||||
assertEquals(allVectors.size(), map.size());
|
||||
assertEquals(allVectors, map.keySet());
|
||||
for (Map.Entry<BlockVector3, BaseBlock> entry : map.entrySet()) {
|
||||
assertTrue(allVectors.contains(entry.getKey()));
|
||||
assertEquals(air, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
5
worldedit-core/src/test/resources/junit-platform.properties
Normale Datei
5
worldedit-core/src/test/resources/junit-platform.properties
Normale Datei
@ -0,0 +1,5 @@
|
||||
junit.jupiter.execution.parallel.enabled=true
|
||||
junit.jupiter.execution.parallel.mode.default=concurrent
|
||||
junit.jupiter.execution.parallel.mode.classes.default=same_thread
|
||||
junit.jupiter.execution.parallel.config.strategy=dynamic
|
||||
junit.jupiter.execution.parallel.config.dynamic.factor=4
|
@ -129,5 +129,7 @@ public class ConfigurateConfiguration extends LocalConfiguration {
|
||||
|
||||
String type = node.getNode("shell-save-type").getString("").trim();
|
||||
shellSaveType = type.equals("") ? null : type;
|
||||
|
||||
extendedYLimit = node.getNode("compat", "extended-y-limit").getBoolean(false);
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren