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
This commit is contained in:
Kenzie Togami
2019-08-12 05:06:40 -07:00
committed by Matthew Miller
parent ec5bc5a3b7
commit f472c20bfb
30 changed files with 2014 additions and 139 deletions

View File

@ -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,24 +89,21 @@ 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()) {
return null;
while (iterator.hasNext()) {
BlockVector3 position = iterator.next();
BaseBlock block = blockMap.get(position);
getExtent().setBlock(position, block);
}
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;
blockMap.clear();
return null;
}
@Override

View File

@ -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);