Skip to content
Permalink
Browse files

Make IDs ALWAYS present, only runtime-consistent. Use for memory effi…

…ciency in BlockMap
  • Loading branch information...
kenzierocks committed Jul 18, 2019
1 parent 6f59447 commit 6b4f6bba245c68c6cc4a6c2ea3bba53dd80c1349
@@ -20,9 +20,11 @@
package com.sk89q.worldedit.internal.block;

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.BitSet;
import java.util.OptionalInt;

import static com.google.common.base.Preconditions.checkState;
@@ -51,7 +53,19 @@ public static OptionalInt getBlockStateId(BlockState holder) {
return id < blockStates.length ? blockStates[id] : null;
}

/**
* 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
*/
public static int provideUnusedWorldEditId() {
return usedIds.nextClearBit(0);
}

private static BlockState[] blockStates = new BlockState[2 << 13];
private static final BitSet usedIds = new BitSet();

public static void register(BlockState blockState) {
OptionalInt id = getBlockStateId(blockState);
@@ -69,6 +83,7 @@ public static void register(BlockState blockState) {
"BlockState %s is using the same block ID (%s) as BlockState %s",
blockState, i, existing);
blockStates[i] = blockState;
usedIds.set(i);
}
}

@@ -105,7 +105,7 @@ private static BlockVector3 reconstructLocation(int group, int inner) {
return BlockVector3.at(x, y, z);
}

private final Int2ObjectMap<Int2ObjectMap<BaseBlock>> maps = new Int2ObjectOpenHashMap<>();
private final Int2ObjectMap<SubBlockMap> maps = new Int2ObjectOpenHashMap<>(64, 1f);
private Set<Entry<BlockVector3, BaseBlock>> entrySet;
private Collection<BaseBlock> values;

@@ -116,28 +116,28 @@ private BlockMap(Map<? extends BlockVector3, ? extends BaseBlock> source) {
putAll(source);
}

private Map<Integer, BaseBlock> getOrCreateMap(int groupKey) {
return maps.computeIfAbsent(groupKey, k -> new Int2ObjectOpenHashMap<>());
private SubBlockMap getOrCreateMap(int groupKey) {
return maps.computeIfAbsent(groupKey, k -> new SubBlockMap());
}

private Map<Integer, BaseBlock> getOrEmptyMap(int groupKey) {
return maps.getOrDefault(groupKey, Int2ObjectMaps.emptyMap());
private SubBlockMap getOrEmptyMap(int 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(int groupKey, Function<Int2ObjectMap<BaseBlock>, R> func) {
Int2ObjectMap<BaseBlock> map = maps.get(groupKey);
SubBlockMap map = maps.get(groupKey);
if (map != null) {
R result = func.apply(map);
if (map.isEmpty()) {
maps.remove(groupKey);
}
return result;
}
map = new Int2ObjectOpenHashMap<>();
map = new SubBlockMap();
R result = func.apply(map);
if (!map.isEmpty()) {
maps.put(groupKey, map);
@@ -231,7 +231,7 @@ public BaseBlock merge(BlockVector3 key, BaseBlock value, BiFunction<? super Bas
public Iterator<Entry<BlockVector3, BaseBlock>> iterator() {
return new AbstractIterator<Entry<BlockVector3, BaseBlock>>() {

private final ObjectIterator<Int2ObjectMap.Entry<Int2ObjectMap<BaseBlock>>> primaryIterator
private final ObjectIterator<Int2ObjectMap.Entry<SubBlockMap>> primaryIterator
= Int2ObjectMaps.fastIterator(maps);
private int currentGroupKey;
private ObjectIterator<Int2ObjectMap.Entry<BaseBlock>> secondaryIterator;
@@ -243,7 +243,7 @@ public BaseBlock merge(BlockVector3 key, BaseBlock value, BiFunction<? super Bas
return endOfData();
}

Int2ObjectMap.Entry<Int2ObjectMap<BaseBlock>> next = primaryIterator.next();
Int2ObjectMap.Entry<SubBlockMap> next = primaryIterator.next();
currentGroupKey = next.getIntKey();
secondaryIterator = Int2ObjectMaps.fastIterator(next.getValue());
}
@@ -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();
}
}
}
}
@@ -67,7 +67,8 @@
}

BlockState initializeId(BlockRegistry registry) {
this.internalId = registry.getInternalBlockStateId(this);
this.internalId = OptionalInt.of(registry.getInternalBlockStateId(this)
.orElseGet(BlockStateIdAccess::provideUnusedWorldEditId));
BlockStateIdAccess.register(this);
return this;
}
@@ -26,6 +26,7 @@
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;
@@ -42,6 +43,7 @@
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;
@@ -85,8 +87,11 @@ static void setupFakePlatform() {
}

@AfterAll
static void tearDownFakePlatform() {
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) {

0 comments on commit 6b4f6bb

Please sign in to comment.
You can’t perform that action at this time.