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:
Camotoy 2021-09-04 14:55:22 -04:00
parent f22d286ea1
commit 93f5298ee3
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
13 changed files with 667 additions and 50 deletions

View file

@ -214,7 +214,7 @@ public class GeyserSession implements CommandSender {
* See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
* for more information.
*/
private final Set<Vector3i> lecternCache = new ObjectOpenHashSet<>();
private final Set<Vector3i> lecternCache;
@Setter
private boolean droppingLecternBook;
@ -474,6 +474,13 @@ public class GeyserSession implements CommandSender {
this.spawned = 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) {
this.emotes = new HashSet<>();
// 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));
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
} catch (RequestException ex) {
ex.printStackTrace();
disconnect(ex.getMessage());
}
return null;
}).whenComplete((aVoid, ex) -> {
if (ex != null) {
disconnect(ex.toString());
}
if (this.closed) {
connector.getLogger().error("", ex);
// Client disconnected during the authentication attempt
return;
}

View file

@ -50,12 +50,14 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
}
}
// Do the same thing with lecterns
iterator = session.getLecternCache().iterator();
while (iterator.hasNext()) {
Vector3i position = iterator.next();
if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) {
iterator.remove();
if (!session.getConnector().getWorldManager().shouldExpectLecternHandled()) {
// Do the same thing with lecterns
iterator = session.getLecternCache().iterator();
while (iterator.hasNext()) {
Vector3i position = iterator.next();
if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) {
iterator.remove();
}
}
}
}

View file

@ -33,31 +33,31 @@ import org.geysermc.connector.registry.BlockRegistries;
import org.geysermc.connector.registry.type.BlockMapping;
import org.geysermc.connector.utils.Direction;
import org.geysermc.connector.utils.PistonBehavior;
import java.util.Map;
import java.util.function.BiFunction;
import org.geysermc.connector.utils.collections.FixedInt2ByteMap;
import org.geysermc.connector.utils.collections.FixedInt2IntMap;
import org.geysermc.connector.utils.collections.LecternHasBookMap;
/**
* Used for block entities if the Java block state contains Bedrock block information.
*/
public class BlockStateValues {
private static final Int2IntMap BANNER_COLORS = new Int2IntOpenHashMap();
private static final Int2ByteMap BED_COLORS = new Int2ByteOpenHashMap();
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_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 Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap();
private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap();
private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap();
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap();
private static final Object2IntMap<Direction> PISTON_HEADS = new Object2IntOpenHashMap<>();
private static final Int2ObjectMap<Direction> PISTON_ORIENTATION = new Int2ObjectOpenHashMap<>();
private static final IntSet ALL_PISTON_HEADS = 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 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();
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
*/
public static Map<Integer, Boolean> getLecternBookStates() {
public static LecternHasBookMap getLecternBookStates() {
return LECTERN_BOOK_STATES;
}
@ -407,7 +404,7 @@ public class BlockStateValues {
* @return The block's slipperiness
*/
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) {
case "minecraft:slime_block":
return 0.8f;

View file

@ -33,7 +33,7 @@ import org.geysermc.connector.registry.loader.RegistryLoaders;
import org.geysermc.connector.registry.populator.BlockRegistryPopulator;
import org.geysermc.connector.registry.type.BlockMapping;
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.

View file

@ -51,7 +51,6 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.player.SkullPlayerEntity;
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.entity.BedrockOnlyBlockEntity;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
@ -391,30 +390,7 @@ public class ChunkUtils {
session.sendUpstreamPacket(waterPacket);
}
BlockStateValues.getLecternBookStates().compute(blockState, (key, newLecternHasBook) -> {
// 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;
});
BlockStateValues.getLecternBookStates().handleBlockChange(session, blockState, position);
// Iterates through all Bedrock-only block entity translators and determines if a manual block entity packet
// needs to be sent

View file

@ -63,7 +63,9 @@ public class DimensionUtils {
session.getChunkCache().clear();
session.getEntityCache().removeAllEntities();
session.getItemFrameCache().clear();
session.getLecternCache().clear();
if (session.getLecternCache() != null) {
session.getLecternCache().clear();
}
session.getLodestoneCache().clear();
session.getPistonCache().clear();
session.getSkullCache().clear();

View file

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

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@
* @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.Int2ObjectOpenHashMap;

View file

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

View file

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