Conflicts:
	src/test/java/demo/PlayerInit.java
This commit is contained in:
Eoghanmc22 2020-11-21 13:23:16 -05:00
commit f2f0625666
14 changed files with 168 additions and 130 deletions

View File

@ -110,6 +110,7 @@ dependencies {
api 'io.netty:netty-codec:4.1.54.Final'
api 'io.netty:netty-transport-native-epoll:4.1.54.Final:linux-x86_64'
api 'io.netty:netty-transport-native-kqueue:4.1.54.Final:osx-x86_64'
api 'io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.1.Final:linux-x86_64'
// https://mvnrepository.com/artifact/it.unimi.dsi/fastutil
api 'it.unimi.dsi:fastutil:8.4.3'

View File

@ -129,6 +129,7 @@ public final class MinecraftServer {
private static int chunkViewDistance = 8;
private static int entityViewDistance = 5;
private static int compressionThreshold = 256;
private static boolean packetCaching = true;
private static ResponseDataConsumer responseDataConsumer;
private static String brandName = "Minestom";
private static Difficulty difficulty = Difficulty.NORMAL;
@ -521,6 +522,33 @@ public final class MinecraftServer {
MinecraftServer.compressionThreshold = compressionThreshold;
}
/**
* Gets if the packet caching feature is enabled.
* <p>
* This feature allows some packets (implementing the {@link net.minestom.server.utils.cache.CacheablePacket} to be cached
* in order to do not have to be written and compressed over and over again), this is especially useful for chunk and light packets.
* <p>
* It is enabled by default and it is our recommendation, you should only disable it if you want to focus on low memory usage
* at the cost of many packet writing and compression.
*
* @return true if the packet caching feature is enabled, false otherwise
*/
public static boolean hasPacketCaching() {
return packetCaching;
}
/**
* Enables or disable packet caching.
*
* @param packetCaching true to enable packet caching
* @throws IllegalStateException if this is called after the server started
* @see #hasPacketCaching()
*/
public static void setPacketCaching(boolean packetCaching) {
Check.stateCondition(started, "You cannot change the packet caching value after the server has been started.");
MinecraftServer.packetCaching = packetCaching;
}
/**
* Gets the consumer executed to show server-list data.
*

View File

@ -1,53 +0,0 @@
package net.minestom.server.data;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Map;
/**
* A data implementation backed by a {@link org.jglrxavpok.hephaistos.nbt.NBTCompound}.
*/
public class NbtDataImpl extends DataImpl {
// Used to know if a nbt key is from a Data object, should NOT be changed and used in a key name
public static final String KEY_PREFIX = "nbtdata_";
@NotNull
@Override
public Data copy() {
DataImpl data = new NbtDataImpl();
data.data.putAll(this.data);
data.dataType.putAll(this.dataType);
return data;
}
/**
* Writes all the data into a {@link NBTCompound}.
*
* @param nbtCompound the nbt compound to write to
* @throws NullPointerException if the type of a data is not a primitive nbt type and therefore not supported
* (you can use {@link DataType#encode(BinaryWriter, Object)} to use byte array instead)
*/
public void writeToNbt(@NotNull NBTCompound nbtCompound) {
for (Map.Entry<String, Object> entry : data.entrySet()) {
final String key = entry.getKey();
final Object value = entry.getValue();
final Class type = dataType.get(key);
final NBT nbt = NBTUtils.toNBT(value, type, false);
Check.notNull(nbt,
"The type '" + type + "' is not supported within NbtDataImpl, if you wish to use a custom type you can encode the value into a byte array using a DataType");
final String finalKey = KEY_PREFIX + key;
nbtCompound.set(finalKey, nbt);
}
}
}

View File

@ -81,7 +81,7 @@ public class Player extends LivingEntity implements CommandSender {
/**
* @see #getPlayerSynchronizationGroup()
*/
private static volatile int playerSynchronizationGroup = 50;
private static volatile int playerSynchronizationGroup = 75;
/**
* For the number of viewers that a player has, the position synchronization packet will be sent
@ -625,6 +625,14 @@ public class Player extends LivingEntity implements CommandSender {
}
}
// Item ownership cache
{
ItemStack[] itemStacks = inventory.getItemStacks();
for (ItemStack itemStack : itemStacks) {
ItemStack.DATA_OWNERSHIP.clearCache(itemStack.getIdentifier());
}
}
// Clear all viewable entities
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
// Clear all viewable chunks
@ -685,7 +693,7 @@ public class Player extends LivingEntity implements CommandSender {
// true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance)
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance);
if (needWorldRefresh) {
if (needWorldRefresh && !firstSpawn) {
// Remove all previous viewable chunks (from the previous instance)
for (Chunk viewableChunk : viewableChunks) {
viewableChunk.removeViewer(this);
@ -716,7 +724,7 @@ public class Player extends LivingEntity implements CommandSender {
final boolean isLast = counter.get() == length - 1;
if (isLast) {
// This is the last chunk to be loaded , spawn player
spawnPlayer(instance, firstSpawn);
spawnPlayer(instance, false);
} else {
// Increment the counter of current loaded chunks
counter.incrementAndGet();
@ -726,8 +734,13 @@ public class Player extends LivingEntity implements CommandSender {
// WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck.
instance.loadOptionalChunk(chunkX, chunkZ, callback);
}
} else if (firstSpawn) {
// The player always believe that his position is 0;0 so this is a pretty hacky fix
instance.loadOptionalChunk(0, 0, chunk -> spawnPlayer(instance, true));
} else {
spawnPlayer(instance, firstSpawn);
// The player already has the good version of all the chunks.
// We just need to refresh his entity viewing list and add him to the instance
spawnPlayer(instance, false);
}
}

View File

@ -23,7 +23,6 @@ import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@ -199,7 +198,7 @@ public class Inventory implements InventoryModifier, InventoryClickHandler, View
@NotNull
@Override
public ItemStack[] getItemStacks() {
return Arrays.copyOf(itemStacks, itemStacks.length);
return itemStacks.clone();
}
@Override

View File

@ -22,7 +22,6 @@ import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@ -59,7 +58,7 @@ public class PlayerInventory implements InventoryModifier, InventoryClickHandler
@NotNull
@Override
public ItemStack[] getItemStacks() {
return Arrays.copyOf(items, items.length);
return items.clone();
}
@NotNull

View File

@ -3,7 +3,6 @@ package net.minestom.server.item;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.data.Data;
import net.minestom.server.data.DataContainer;
import net.minestom.server.data.NbtDataImpl;
import net.minestom.server.entity.ItemEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.Inventory;
@ -17,6 +16,7 @@ import net.minestom.server.registry.Registries;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.NBTUtils;
import net.minestom.server.utils.ownership.OwnershipHandler;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -37,8 +37,12 @@ import java.util.*;
*/
public class ItemStack implements DataContainer {
public static final OwnershipHandler<Data> DATA_OWNERSHIP = new OwnershipHandler();
public static final String OWNERSHIP_DATA_KEY = "ownership_identifier";
private static final StackingRule VANILLA_STACKING_RULE = new VanillaStackingRule(64);
private UUID identifier;
private Material material;
private static StackingRule defaultStackingRule;
@ -48,6 +52,7 @@ public class ItemStack implements DataContainer {
private int damage;
public ItemStack(@NotNull Material material, byte amount, int damage) {
this.identifier = DATA_OWNERSHIP.generateIdentifier();
this.material = material;
this.amount = amount;
this.damage = damage;
@ -72,8 +77,6 @@ public class ItemStack implements DataContainer {
private StackingRule stackingRule;
private Data data;
private NBTConsumer nbtConsumer;
{
if (defaultStackingRule == null)
defaultStackingRule = VANILLA_STACKING_RULE;
@ -157,6 +160,10 @@ public class ItemStack implements DataContainer {
*/
public boolean isSimilar(@NotNull ItemStack itemStack) {
synchronized (ItemStack.class) {
if (itemStack.getIdentifier().equals(identifier)) {
return true;
}
final ColoredText itemDisplayName = itemStack.getDisplayName();
final boolean displayNameCheck = (displayName == null && itemDisplayName == null) ||
(displayName != null && displayName.equals(itemDisplayName));
@ -493,6 +500,19 @@ public class ItemStack implements DataContainer {
this.unbreakable = unbreakable;
}
/**
* Gets the unique identifier of this object.
* <p>
* This value is non persistent and will be randomized once this item is separated with a right-click,
* when copied and when the server restart. It is used internally by the data ownership system.
*
* @return this item unique identifier
*/
@NotNull
public UUID getIdentifier() {
return identifier;
}
/**
* Gets the item {@link Material}.
*
@ -527,7 +547,7 @@ public class ItemStack implements DataContainer {
hideFlag != 0 ||
customModelData != 0 ||
(itemMeta != null && itemMeta.hasNbt()) ||
(data instanceof NbtDataImpl && !data.isEmpty());
(data != null && !data.isEmpty());
}
/**
@ -571,35 +591,15 @@ public class ItemStack implements DataContainer {
/**
* Sets the data of this item.
* <p>
* It is recommended to use {@link NbtDataImpl} if you want the data to be passed to the client.
*
* @param data the new {@link Data} of this container, null to remove it
*/
@Override
public void setData(@Nullable Data data) {
DATA_OWNERSHIP.saveOwnObject(getIdentifier(), data);
this.data = data;
}
/**
* Gets the {@link NBTConsumer} called when the item is serialized into a packet.
*
* @return the item nbt consumer, null if not any
*/
@Nullable
public NBTConsumer getNBTConsumer() {
return nbtConsumer;
}
/**
* Changes the item {@link NBTConsumer}.
*
* @param nbtConsumer the new item nbt consumer, can be null
*/
public void setNBTConsumer(@Nullable NBTConsumer nbtConsumer) {
this.nbtConsumer = nbtConsumer;
}
/**
* Gets the item {@link StackingRule}.
*

View File

@ -1,9 +0,0 @@
package net.minestom.server.item;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.function.Consumer;
@FunctionalInterface
public interface NBTConsumer extends Consumer<NBTCompound> {
}

View File

@ -15,6 +15,9 @@ import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler;
import io.netty.handler.traffic.TrafficCounter;
import io.netty.incubator.channel.uring.IOUring;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.incubator.channel.uring.IOUringServerSocketChannel;
import net.minestom.server.MinecraftServer;
import net.minestom.server.network.PacketProcessor;
import net.minestom.server.network.netty.channel.ClientChannel;
@ -66,7 +69,12 @@ public final class NettyServer {
public NettyServer(@NotNull PacketProcessor packetProcessor) {
Class<? extends ServerChannel> channel;
if (Epoll.isAvailable()) {
if (IOUring.isAvailable()) {
boss = new IOUringEventLoopGroup(2);
worker = new IOUringEventLoopGroup(); // thread count = core * 2
channel = IOUringServerSocketChannel.class;
} else if (Epoll.isAvailable()) {
boss = new EpollEventLoopGroup(2);
worker = new EpollEventLoopGroup(); // thread count = core * 2

View File

@ -114,7 +114,7 @@ public class NettyPlayerConnection extends PlayerConnection {
if (shouldSendPacket(serverPacket)) {
if (getPlayer() != null) {
// Flush happen during #update()
if (serverPacket instanceof CacheablePacket) {
if (serverPacket instanceof CacheablePacket && MinecraftServer.hasPacketCaching()) {
CacheablePacket cacheablePacket = (CacheablePacket) serverPacket;
final UUID identifier = cacheablePacket.getIdentifier();

View File

@ -7,12 +7,10 @@ import net.minestom.server.chat.ChatParser;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.data.Data;
import net.minestom.server.data.DataType;
import net.minestom.server.data.NbtDataImpl;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.NBTConsumer;
import net.minestom.server.item.attribute.AttributeSlot;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.item.metadata.ItemMeta;
@ -192,18 +190,15 @@ public final class NBTUtils {
itemMeta.read(nbt);
}
NbtDataImpl customData = null;
for (String key : nbt.getKeys()) {
if (key.startsWith(NbtDataImpl.KEY_PREFIX)) {
if (customData == null) {
customData = new NbtDataImpl();
item.setData(customData);
// Ownership
{
if (nbt.containsKey(ItemStack.OWNERSHIP_DATA_KEY)) {
final String identifierString = nbt.getString(ItemStack.OWNERSHIP_DATA_KEY);
final UUID identifier = UUID.fromString(identifierString);
final Data data = ItemStack.DATA_OWNERSHIP.getOwnObject(identifier);
if (data != null) {
item.setData(data);
}
final NBT keyNbt = nbt.get(key);
final String dataKey = key.replaceFirst(NbtDataImpl.KEY_PREFIX, "");
final Object dataValue = fromNBT(keyNbt);
customData.set(dataKey, dataValue);
}
}
}
@ -239,12 +234,6 @@ public final class NBTUtils {
// Vanilla compound
saveDataIntoNBT(itemStack, itemNBT);
// Custom item nbt
final NBTConsumer nbtConsumer = itemStack.getNBTConsumer();
if (nbtConsumer != null) {
nbtConsumer.accept(itemNBT);
}
// End custom model data
packet.writeNBT("", itemNBT);
}
@ -349,15 +338,15 @@ public final class NBTUtils {
}
// End custom meta
// Start NbtData data
// Start ownership
{
final Data data = itemStack.getData();
if (data instanceof NbtDataImpl) {
NbtDataImpl nbtData = (NbtDataImpl) data;
nbtData.writeToNbt(itemNBT);
if (data != null && !data.isEmpty()) {
final UUID identifier = itemStack.getIdentifier();
itemNBT.setString(ItemStack.OWNERSHIP_DATA_KEY, identifier.toString());
}
}
// End NbtData
// End ownership
}
/**

View File

@ -57,6 +57,8 @@ public class TemporaryCache<T> {
* Retrieves an object from cache.
*
* @param identifier the object identifier
* @param lastUpdate the last update time of your identifier's object,
* used to see if the cached value is up-to-date
* @return the retrieved object or null if not found
*/
@Nullable
@ -70,7 +72,7 @@ public class TemporaryCache<T> {
}
/**
* Gets the time an object will be kept without being retrieved
* Gets the time an object will be kept without being retrieved.
*
* @return the keep time in milliseconds
*/

View File

@ -0,0 +1,59 @@
package net.minestom.server.utils.ownership;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Convenient class to keep trace of objects linked to an {@link UUID}.
*
* @param <T> the owned object type
*/
public class OwnershipHandler<T> {
// identifier = the object having an ownership
private ConcurrentHashMap<UUID, T> ownershipDataMap = new ConcurrentHashMap<>();
/**
* Generates a new unique identifier.
* <p>
* Does call {@link UUID#randomUUID()} internally.
*
* @return a new generated identifier
*/
public UUID generateIdentifier() {
return UUID.randomUUID();
}
/**
* Retrieves the owned object based on its identifier.
*
* @param identifier the object identifier
* @return the own object, null if not found
*/
@Nullable
public T getOwnObject(@NotNull UUID identifier) {
return ownershipDataMap.get(identifier);
}
/**
* Saves, replace or remove the own object of an identifier.
*
* @param identifier the identifier of the object
* @param value the value of the object, can override the previous value, null means removing the identifier
*/
public void saveOwnObject(@NotNull UUID identifier, @Nullable T value) {
if (value != null) {
this.ownershipDataMap.put(identifier, value);
} else {
clearCache(identifier);
}
}
public void clearCache(@NotNull UUID identifier) {
this.ownershipDataMap.remove(identifier);
}
}

View File

@ -5,7 +5,8 @@ import demo.generator.NoiseTestGenerator;
import net.minestom.server.MinecraftServer;
import net.minestom.server.benchmark.BenchmarkManager;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.data.NbtDataImpl;
import net.minestom.server.data.Data;
import net.minestom.server.data.DataImpl;
import net.minestom.server.entity.*;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.entity.type.monster.EntityZombie;
@ -49,8 +50,8 @@ public class PlayerInit {
instanceContainer.setChunkGenerator(noiseTestGenerator);
// Load some chunks beforehand
final int loopStart = -16;
final int loopEnd = 16;
final int loopStart = -10;
final int loopEnd = 10;
for (int x = loopStart; x < loopEnd; x++)
for (int z = loopStart; z < loopEnd; z++) {
//instanceContainer.loadChunk(x, z);
@ -174,22 +175,23 @@ public class PlayerInit {
event.setSpawningInstance(instanceContainer);
int x = Math.abs(ThreadLocalRandom.current().nextInt()) % 2000 - 1000;
int z = Math.abs(ThreadLocalRandom.current().nextInt()) % 2000 - 1000;
player.setRespawnPoint(new Position(x, 70f, z));
player.setRespawnPoint(new Position(0, 70f, 0));
/*player.getInventory().addInventoryCondition((p, slot, clickType, inventoryConditionResult) -> {
player.getInventory().addInventoryCondition((p, slot, clickType, inventoryConditionResult) -> {
if (slot == -999)
return;
inventoryConditionResult.setCancel(false);
ItemStack itemStack = p.getInventory().getItemStack(slot);
System.out.println("slot player: " + slot + " : " + itemStack.getMaterial() + " : " + (itemStack.getData() != null));
});*/
final int value = itemStack.getData() != null ? itemStack.getData().get("testc") : 0;
System.out.println("slot player: " + slot + " : " + itemStack.getMaterial() + " : " + value);
});
});
player.addEventCallback(PlayerSpawnEvent.class, event -> {
player.setGameMode(GameMode.CREATIVE);
ItemStack itemStack = new ItemStack(Material.DIAMOND_BLOCK, (byte) 64);
NbtDataImpl data = new NbtDataImpl();
Data data = new DataImpl();
data.set("testc", 2);
itemStack.setData(data);
player.getInventory().addItemStack(itemStack);