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

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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