mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Introduce specialized maps for block state operations
By introducing fixed maps, we are able to reduce the overhead of needing to store arbitrary numbers; fixed maps start at one number and must be sequential. This commit also reduces some overhead of lecterns in block updating as we are able to utilize our own maps to reduce object boxing and removing the function of Map#compute of trying to re-insert a value.
This commit is contained in:
parent
f22d286ea1
commit
93f5298ee3
13 changed files with 667 additions and 50 deletions
|
@ -214,7 +214,7 @@ public class GeyserSession implements CommandSender {
|
||||||
* See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
|
* See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
|
||||||
* for more information.
|
* for more information.
|
||||||
*/
|
*/
|
||||||
private final Set<Vector3i> lecternCache = new ObjectOpenHashSet<>();
|
private final Set<Vector3i> lecternCache;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private boolean droppingLecternBook;
|
private boolean droppingLecternBook;
|
||||||
|
@ -474,6 +474,13 @@ public class GeyserSession implements CommandSender {
|
||||||
this.spawned = false;
|
this.spawned = false;
|
||||||
this.loggedIn = false;
|
this.loggedIn = false;
|
||||||
|
|
||||||
|
if (connector.getWorldManager().shouldExpectLecternHandled()) {
|
||||||
|
// Unneeded on these platforms
|
||||||
|
this.lecternCache = null;
|
||||||
|
} else {
|
||||||
|
this.lecternCache = new ObjectOpenHashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
if (connector.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) {
|
if (connector.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) {
|
||||||
this.emotes = new HashSet<>();
|
this.emotes = new HashSet<>();
|
||||||
// Make a copy to prevent ConcurrentModificationException
|
// Make a copy to prevent ConcurrentModificationException
|
||||||
|
@ -603,12 +610,15 @@ public class GeyserSession implements CommandSender {
|
||||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
|
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
|
||||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
||||||
} catch (RequestException ex) {
|
} catch (RequestException ex) {
|
||||||
ex.printStackTrace();
|
|
||||||
disconnect(ex.getMessage());
|
disconnect(ex.getMessage());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}).whenComplete((aVoid, ex) -> {
|
}).whenComplete((aVoid, ex) -> {
|
||||||
|
if (ex != null) {
|
||||||
|
disconnect(ex.toString());
|
||||||
|
}
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
|
connector.getLogger().error("", ex);
|
||||||
// Client disconnected during the authentication attempt
|
// Client disconnected during the authentication attempt
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!session.getConnector().getWorldManager().shouldExpectLecternHandled()) {
|
||||||
// Do the same thing with lecterns
|
// Do the same thing with lecterns
|
||||||
iterator = session.getLecternCache().iterator();
|
iterator = session.getLecternCache().iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
|
@ -60,3 +61,4 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,31 +33,31 @@ import org.geysermc.connector.registry.BlockRegistries;
|
||||||
import org.geysermc.connector.registry.type.BlockMapping;
|
import org.geysermc.connector.registry.type.BlockMapping;
|
||||||
import org.geysermc.connector.utils.Direction;
|
import org.geysermc.connector.utils.Direction;
|
||||||
import org.geysermc.connector.utils.PistonBehavior;
|
import org.geysermc.connector.utils.PistonBehavior;
|
||||||
|
import org.geysermc.connector.utils.collections.FixedInt2ByteMap;
|
||||||
import java.util.Map;
|
import org.geysermc.connector.utils.collections.FixedInt2IntMap;
|
||||||
import java.util.function.BiFunction;
|
import org.geysermc.connector.utils.collections.LecternHasBookMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for block entities if the Java block state contains Bedrock block information.
|
* Used for block entities if the Java block state contains Bedrock block information.
|
||||||
*/
|
*/
|
||||||
public class BlockStateValues {
|
public class BlockStateValues {
|
||||||
private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap();
|
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
|
||||||
private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap();
|
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
|
||||||
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
|
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
|
||||||
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
||||||
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
||||||
private static final Int2BooleanMap LECTERN_BOOK_STATES = new Int2BooleanOpenHashMap();
|
private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap();
|
||||||
private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap();
|
private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap();
|
||||||
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
|
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
|
||||||
private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap();
|
private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap();
|
||||||
private static final Object2IntMap<Direction> PISTON_HEADS = new Object2IntOpenHashMap<>();
|
private static final Object2IntMap<Direction> PISTON_HEADS = new Object2IntOpenHashMap<>();
|
||||||
private static final Int2ObjectMap<Direction> PISTON_ORIENTATION = new Int2ObjectOpenHashMap<>();
|
private static final Int2ObjectMap<Direction> PISTON_ORIENTATION = new Int2ObjectOpenHashMap<>();
|
||||||
private static final IntSet ALL_PISTON_HEADS = new IntOpenHashSet();
|
private static final IntSet ALL_PISTON_HEADS = new IntOpenHashSet();
|
||||||
private static final IntSet MOVING_PISTONS = new IntOpenHashSet();
|
private static final IntSet MOVING_PISTONS = new IntOpenHashSet();
|
||||||
private static final Int2ByteMap SKULL_VARIANTS = new Int2ByteOpenHashMap();
|
private static final Int2ByteMap SKULL_VARIANTS = new FixedInt2ByteMap();
|
||||||
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
|
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
|
||||||
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
|
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
|
||||||
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap();
|
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new FixedInt2ByteMap();
|
||||||
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
|
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
|
||||||
|
|
||||||
public static final int JAVA_AIR_ID = 0;
|
public static final int JAVA_AIR_ID = 0;
|
||||||
|
@ -237,12 +237,9 @@ public class BlockStateValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This returns a Map interface so IntelliJ doesn't complain about {@link Int2BooleanMap#compute(int, BiFunction)}
|
|
||||||
* not returning null.
|
|
||||||
*
|
|
||||||
* @return the lectern book state map pointing to book present state
|
* @return the lectern book state map pointing to book present state
|
||||||
*/
|
*/
|
||||||
public static Map<Integer, Boolean> getLecternBookStates() {
|
public static LecternHasBookMap getLecternBookStates() {
|
||||||
return LECTERN_BOOK_STATES;
|
return LECTERN_BOOK_STATES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +404,7 @@ public class BlockStateValues {
|
||||||
* @return The block's slipperiness
|
* @return The block's slipperiness
|
||||||
*/
|
*/
|
||||||
public static float getSlipperiness(int state) {
|
public static float getSlipperiness(int state) {
|
||||||
String blockIdentifier = BlockRegistries.JAVA_BLOCKS.get(state).getJavaIdentifier();
|
String blockIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(state, BlockMapping.AIR).getJavaIdentifier();
|
||||||
switch (blockIdentifier) {
|
switch (blockIdentifier) {
|
||||||
case "minecraft:slime_block":
|
case "minecraft:slime_block":
|
||||||
return 0.8f;
|
return 0.8f;
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.geysermc.connector.registry.loader.RegistryLoaders;
|
||||||
import org.geysermc.connector.registry.populator.BlockRegistryPopulator;
|
import org.geysermc.connector.registry.populator.BlockRegistryPopulator;
|
||||||
import org.geysermc.connector.registry.type.BlockMapping;
|
import org.geysermc.connector.registry.type.BlockMapping;
|
||||||
import org.geysermc.connector.registry.type.BlockMappings;
|
import org.geysermc.connector.registry.type.BlockMappings;
|
||||||
import org.geysermc.connector.utils.Object2IntBiMap;
|
import org.geysermc.connector.utils.collections.Object2IntBiMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds all the block registries in Geyser.
|
* Holds all the block registries in Geyser.
|
||||||
|
|
|
@ -51,7 +51,6 @@ import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||||
import org.geysermc.connector.entity.player.SkullPlayerEntity;
|
import org.geysermc.connector.entity.player.SkullPlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
|
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||||
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
|
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
|
||||||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||||
|
@ -391,30 +390,7 @@ public class ChunkUtils {
|
||||||
session.sendUpstreamPacket(waterPacket);
|
session.sendUpstreamPacket(waterPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockStateValues.getLecternBookStates().compute(blockState, (key, newLecternHasBook) -> {
|
BlockStateValues.getLecternBookStates().handleBlockChange(session, blockState, position);
|
||||||
// Determine if this block is a lectern
|
|
||||||
if (newLecternHasBook != null) {
|
|
||||||
boolean lecternCachedHasBook = session.getLecternCache().contains(position);
|
|
||||||
if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) {
|
|
||||||
// Refresh the block entirely - it either has a book or no longer has a book
|
|
||||||
NbtMap newLecternTag;
|
|
||||||
if (newLecternHasBook) {
|
|
||||||
newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
|
||||||
} else {
|
|
||||||
session.getLecternCache().remove(position);
|
|
||||||
newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
|
|
||||||
}
|
|
||||||
BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
|
|
||||||
} else {
|
|
||||||
// As of right now, no tag can be added asynchronously
|
|
||||||
session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Lectern has been destroyed, if it existed
|
|
||||||
session.getLecternCache().remove(position);
|
|
||||||
}
|
|
||||||
return newLecternHasBook;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet
|
// Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet
|
||||||
// needs to be sent
|
// needs to be sent
|
||||||
|
|
|
@ -63,7 +63,9 @@ public class DimensionUtils {
|
||||||
session.getChunkCache().clear();
|
session.getChunkCache().clear();
|
||||||
session.getEntityCache().removeAllEntities();
|
session.getEntityCache().removeAllEntities();
|
||||||
session.getItemFrameCache().clear();
|
session.getItemFrameCache().clear();
|
||||||
|
if (session.getLecternCache() != null) {
|
||||||
session.getLecternCache().clear();
|
session.getLecternCache().clear();
|
||||||
|
}
|
||||||
session.getLodestoneCache().clear();
|
session.getLodestoneCache().clear();
|
||||||
session.getPistonCache().clear();
|
session.getPistonCache().clear();
|
||||||
session.getSkullCache().clear();
|
session.getSkullCache().clear();
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.utils.collections;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.AbstractInt2BooleanMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||||
|
|
||||||
|
public class FixedInt2BooleanMap extends AbstractInt2BooleanMap {
|
||||||
|
protected boolean[] value;
|
||||||
|
protected int start = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectSet<Entry> int2BooleanEntrySet() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean get(int i) {
|
||||||
|
return getOrDefault(i, defRetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getOrDefault(int key, boolean defaultValue) {
|
||||||
|
int offset = key - start;
|
||||||
|
if (offset < 0 || offset >= value.length) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(int key, boolean value) {
|
||||||
|
if (start == -1) {
|
||||||
|
start = key;
|
||||||
|
this.value = new boolean[] {value};
|
||||||
|
} else {
|
||||||
|
int offset = key - start;
|
||||||
|
if (offset >= 0 && offset < this.value.length) {
|
||||||
|
boolean curr = this.value[offset];
|
||||||
|
this.value[offset] = value;
|
||||||
|
return curr;
|
||||||
|
} else if (offset != this.value.length) {
|
||||||
|
throw new IndexOutOfBoundsException("Expected: " + (this.value.length + start) + ", got " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean[] newValue = new boolean[offset + 1];
|
||||||
|
System.arraycopy(this.value, 0, newValue, 0, this.value.length);
|
||||||
|
this.value = newValue;
|
||||||
|
this.value[offset] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.defRetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(int k) {
|
||||||
|
int offset = k - start;
|
||||||
|
return offset >= 0 && offset < value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(boolean v) {
|
||||||
|
for (boolean b : value) {
|
||||||
|
if (b == v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder("{");
|
||||||
|
int index = start;
|
||||||
|
for (boolean b : value) {
|
||||||
|
builder.append(index++).append("=>").append(b);
|
||||||
|
if (index < value.length + start) {
|
||||||
|
// Add commas while there are still more entries in the list
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.append('}').toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.utils.collections;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.AbstractInt2ByteMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ByteMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||||
|
|
||||||
|
public class FixedInt2ByteMap extends AbstractInt2ByteMap {
|
||||||
|
protected byte[] value;
|
||||||
|
protected int start = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectSet<Int2ByteMap.Entry> int2ByteEntrySet() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte get(int i) {
|
||||||
|
return getOrDefault(i, defRetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getOrDefault(int key, byte defaultValue) {
|
||||||
|
int offset = key - start;
|
||||||
|
if (offset < 0 || offset >= value.length) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte put(int key, byte value) {
|
||||||
|
if (start == -1) {
|
||||||
|
start = key;
|
||||||
|
this.value = new byte[] {value};
|
||||||
|
} else {
|
||||||
|
int offset = key - start;
|
||||||
|
if (offset >= 0 && offset < this.value.length) {
|
||||||
|
byte curr = this.value[offset];
|
||||||
|
this.value[offset] = value;
|
||||||
|
return curr;
|
||||||
|
} else if (offset != this.value.length) {
|
||||||
|
throw new IndexOutOfBoundsException("Expected: " + (this.value.length + start) + ", got " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] newValue = new byte[offset + 1];
|
||||||
|
System.arraycopy(this.value, 0, newValue, 0, this.value.length);
|
||||||
|
this.value = newValue;
|
||||||
|
this.value[offset] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.defRetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(int k) {
|
||||||
|
int offset = k - start;
|
||||||
|
return offset >= 0 && offset < value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(byte v) {
|
||||||
|
for (byte i : value) {
|
||||||
|
if (i == v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder("{");
|
||||||
|
int index = start;
|
||||||
|
for (byte b : value) {
|
||||||
|
builder.append(index++).append("=>").append(b);
|
||||||
|
if (index < value.length + start) {
|
||||||
|
// Add commas while there are still more entries in the list
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.append('}').toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.utils.collections;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.AbstractInt2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||||
|
|
||||||
|
public class FixedInt2IntMap extends AbstractInt2IntMap {
|
||||||
|
protected int[] value;
|
||||||
|
protected int start = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectSet<Int2IntMap.Entry> int2IntEntrySet() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int get(int i) {
|
||||||
|
return getOrDefault(i, defRetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrDefault(int key, int defaultValue) {
|
||||||
|
int offset = key - start;
|
||||||
|
if (offset < 0 || offset >= value.length) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int put(int key, int value) {
|
||||||
|
if (start == -1) {
|
||||||
|
start = key;
|
||||||
|
this.value = new int[] {value};
|
||||||
|
} else {
|
||||||
|
int offset = key - start;
|
||||||
|
if (offset >= 0 && offset < this.value.length) {
|
||||||
|
int curr = this.value[offset];
|
||||||
|
this.value[offset] = value;
|
||||||
|
return curr;
|
||||||
|
} else if (offset != this.value.length) {
|
||||||
|
throw new IndexOutOfBoundsException("Expected: " + (this.value.length + start) + ", got " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] newValue = new int[offset + 1];
|
||||||
|
System.arraycopy(this.value, 0, newValue, 0, this.value.length);
|
||||||
|
this.value = newValue;
|
||||||
|
this.value[offset] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.defRetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(int k) {
|
||||||
|
int offset = k - start;
|
||||||
|
return offset >= 0 && offset < value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(int v) {
|
||||||
|
for (int i : value) {
|
||||||
|
if (i == v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder("{");
|
||||||
|
int index = start;
|
||||||
|
for (int i : value) {
|
||||||
|
builder.append(index++).append("=>").append(i);
|
||||||
|
if (index < value.length + start) {
|
||||||
|
// Add commas while there are still more entries in the list
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.append('}').toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.utils.collections;
|
||||||
|
|
||||||
|
import com.nukkitx.math.vector.Vector3i;
|
||||||
|
import com.nukkitx.nbt.NbtMap;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.world.WorldManager;
|
||||||
|
import org.geysermc.connector.utils.BlockEntityUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map that takes advantage of its internals for fast operations on block states to determine if they are lecterns.
|
||||||
|
*/
|
||||||
|
public class LecternHasBookMap extends FixedInt2BooleanMap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a potential lectern within the world. This is a map method so it can use the internal fields to
|
||||||
|
* optimize lectern determining.
|
||||||
|
*/
|
||||||
|
public void handleBlockChange(GeyserSession session, int blockState, Vector3i position) {
|
||||||
|
WorldManager worldManager = session.getConnector().getWorldManager();
|
||||||
|
|
||||||
|
int offset = blockState - this.start;
|
||||||
|
if (offset < 0 || offset > this.value.length) {
|
||||||
|
// Block state is out of bounds of this map - lectern has been destroyed, if it existed
|
||||||
|
if (!worldManager.shouldExpectLecternHandled()) {
|
||||||
|
session.getLecternCache().remove(position);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean newLecternHasBook;
|
||||||
|
if (worldManager.shouldExpectLecternHandled()) {
|
||||||
|
// As of right now, no tag can be added asynchronously
|
||||||
|
worldManager.getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
||||||
|
} else if ((newLecternHasBook = this.value[offset]) != this.get(worldManager.getBlockAt(session, position))) {
|
||||||
|
// If the lectern block was updated, or it previously had a book
|
||||||
|
NbtMap newLecternTag;
|
||||||
|
// newLecternHasBook = the new lectern block state's "has book" toggle.
|
||||||
|
if (newLecternHasBook) {
|
||||||
|
newLecternTag = worldManager.getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);
|
||||||
|
} else {
|
||||||
|
session.getLecternCache().remove(position);
|
||||||
|
newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build();
|
||||||
|
}
|
||||||
|
BlockEntityUtils.updateBlockEntity(session, newLecternTag, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@
|
||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.connector.utils;
|
package org.geysermc.connector.utils.collections;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains useful collections for use in Geyser.
|
||||||
|
*
|
||||||
|
* Of note are the fixed int maps. Designed for use with block states that are positive and sequential, they do not allow keys to be
|
||||||
|
* added that are not greater by one versus the previous key. Because of this, speedy operations of {@link java.util.Map#get(java.lang.Object)}
|
||||||
|
* and {@link java.util.Map#containsKey(java.lang.Object)} can be performed by simply checking the bounds of the map
|
||||||
|
* size and its "start" integer.
|
||||||
|
*/
|
||||||
|
package org.geysermc.connector.utils.collections;
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.utils.collections;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class GeyserCollectionsTest {
|
||||||
|
private final byte[] bytes = new byte[] {(byte) 5, (byte) 4, (byte) 3, (byte) 2, (byte) 2, (byte) 1};
|
||||||
|
private final boolean[] booleans = new boolean[] {true, false, false, true};
|
||||||
|
private final int[] ints = new int[] {76, 3006, 999, 2323, 888, 0, 111, 999};
|
||||||
|
|
||||||
|
private final int[] startBlockRanges = new int[] {0, 70, 600, 450, 787, 1980};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBytes() {
|
||||||
|
for (int startRange : startBlockRanges) {
|
||||||
|
testBytes(startRange, new FixedInt2ByteMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testBytes(final int start, final FixedInt2ByteMap map) {
|
||||||
|
int index = start;
|
||||||
|
for (byte b : bytes) {
|
||||||
|
map.put(index++, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastKey = index;
|
||||||
|
|
||||||
|
// Easy, understandable out-of-bounds checks
|
||||||
|
Assert.assertFalse("Map contains key bigger by one!", map.containsKey(lastKey));
|
||||||
|
Assert.assertTrue("Map doesn't contain final key!", map.containsKey(lastKey - 1));
|
||||||
|
|
||||||
|
// Ensure the first and last values do not throw an exception on get, and test getOrDefault
|
||||||
|
map.get(start - 1);
|
||||||
|
map.get(lastKey);
|
||||||
|
Assert.assertEquals(map.getOrDefault(start - 1, Byte.MAX_VALUE), Byte.MAX_VALUE);
|
||||||
|
Assert.assertEquals(map.getOrDefault(lastKey, Byte.MAX_VALUE), Byte.MAX_VALUE);
|
||||||
|
Assert.assertEquals(map.getOrDefault(lastKey, Byte.MIN_VALUE), Byte.MIN_VALUE);
|
||||||
|
|
||||||
|
Assert.assertEquals(map.size(), bytes.length);
|
||||||
|
|
||||||
|
for (int i = start; i < bytes.length; i++) {
|
||||||
|
Assert.assertTrue(map.containsKey(i));
|
||||||
|
Assert.assertEquals(map.get(i), bytes[i - start]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = start - 1; i >= (start - 6); i--) {
|
||||||
|
// Lower than expected check
|
||||||
|
Assert.assertFalse(i + " is in a map that starts with " + start, map.containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = bytes.length + start; i < bytes.length + 5 + start; i++) {
|
||||||
|
// Higher than expected check
|
||||||
|
Assert.assertFalse(i + " is in a map that ends with " + (start + bytes.length), map.containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (byte b : bytes) {
|
||||||
|
Assert.assertTrue(map.containsValue(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBooleans() {
|
||||||
|
for (int startRange : startBlockRanges) {
|
||||||
|
testBooleans(startRange, new FixedInt2BooleanMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testBooleans(final int start, final FixedInt2BooleanMap map) {
|
||||||
|
int index = start;
|
||||||
|
for (boolean b : booleans) {
|
||||||
|
map.put(index++, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastKey = index;
|
||||||
|
|
||||||
|
// Easy, understandable out-of-bounds checks
|
||||||
|
Assert.assertFalse("Map contains key bigger by one!", map.containsKey(lastKey));
|
||||||
|
Assert.assertTrue("Map doesn't contain final key!", map.containsKey(lastKey - 1));
|
||||||
|
|
||||||
|
// Ensure the first and last values do not throw an exception on get
|
||||||
|
map.get(start - 1);
|
||||||
|
map.get(lastKey);
|
||||||
|
Assert.assertTrue(map.getOrDefault(lastKey, true));
|
||||||
|
|
||||||
|
Assert.assertEquals(map.size(), booleans.length);
|
||||||
|
|
||||||
|
for (int i = start; i < booleans.length; i++) {
|
||||||
|
Assert.assertTrue(map.containsKey(i));
|
||||||
|
Assert.assertEquals(map.get(i), booleans[i - start]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = start - 1; i >= (start - 6); i--) {
|
||||||
|
// Lower than expected check
|
||||||
|
Assert.assertFalse(i + " is in a map that starts with " + start, map.containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = booleans.length + start; i < booleans.length + start + 5; i++) {
|
||||||
|
// Higher than expected check
|
||||||
|
Assert.assertFalse(i + " is in a map that ends with " + (start + booleans.length), map.containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (boolean b : booleans) {
|
||||||
|
Assert.assertTrue(map.containsValue(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInts() {
|
||||||
|
for (int startRange : startBlockRanges) {
|
||||||
|
testInts(startRange, new FixedInt2IntMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testInts(final int start, final FixedInt2IntMap map) {
|
||||||
|
int index = start;
|
||||||
|
for (int i : ints) {
|
||||||
|
map.put(index++, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastKey = index;
|
||||||
|
|
||||||
|
// Easy, understandable out-of-bounds checks
|
||||||
|
Assert.assertFalse("Map contains key bigger by one!", map.containsKey(lastKey));
|
||||||
|
Assert.assertTrue("Map doesn't contain final key!", map.containsKey(lastKey - 1));
|
||||||
|
|
||||||
|
// Ensure the first and last values do not throw an exception on get, and test getOrDefault
|
||||||
|
map.get(start - 1);
|
||||||
|
map.get(lastKey);
|
||||||
|
Assert.assertEquals(map.getOrDefault(start - 1, Integer.MAX_VALUE), Integer.MAX_VALUE);
|
||||||
|
Assert.assertEquals(map.getOrDefault(lastKey, Integer.MAX_VALUE), Integer.MAX_VALUE);
|
||||||
|
Assert.assertEquals(map.getOrDefault(lastKey, Integer.MIN_VALUE), Integer.MIN_VALUE);
|
||||||
|
|
||||||
|
Assert.assertEquals(map.size(), ints.length);
|
||||||
|
|
||||||
|
for (int i = start; i < ints.length; i++) {
|
||||||
|
Assert.assertTrue(map.containsKey(i));
|
||||||
|
Assert.assertEquals(map.get(i), ints[i - start]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = start - 1; i >= (start - 6); i--) {
|
||||||
|
// Lower than expected check
|
||||||
|
Assert.assertFalse(i + " is in a map that starts with " + start, map.containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = ints.length + start; i < ints.length + 5 + start; i++) {
|
||||||
|
// Higher than expected check
|
||||||
|
Assert.assertFalse(i + " is in a map that ends with " + (start + ints.length), map.containsKey(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i : ints) {
|
||||||
|
Assert.assertTrue(map.containsValue(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue