Rewrite the packet metadata API based on the NMS packet

Meta, if created, will persist between creation and listening.
This commit is contained in:
Dan Mulloy 2017-08-13 22:47:45 -04:00
parent 1f5692a0c7
commit 1b23e9ec22
2 changed files with 203 additions and 75 deletions

View File

@ -17,37 +17,15 @@
package com.comphenix.protocol.events;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.injector.StructureCache;
@ -55,41 +33,15 @@ import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.cloning.AggregateCloner;
import com.comphenix.protocol.reflect.cloning.*;
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
import com.comphenix.protocol.reflect.cloning.BukkitCloner;
import com.comphenix.protocol.reflect.cloning.Cloner;
import com.comphenix.protocol.reflect.cloning.CollectionCloner;
import com.comphenix.protocol.reflect.cloning.FieldCloner;
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
import com.comphenix.protocol.reflect.cloning.OptionalCloner;
import com.comphenix.protocol.reflect.cloning.SerializableCloner;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.EnumWrappers.ChatType;
import com.comphenix.protocol.wrappers.EnumWrappers.ChatVisibility;
import com.comphenix.protocol.wrappers.EnumWrappers.ClientCommand;
import com.comphenix.protocol.wrappers.EnumWrappers.CombatEventType;
import com.comphenix.protocol.wrappers.EnumWrappers.Difficulty;
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.EnumConverter;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
import com.comphenix.protocol.wrappers.EnumWrappers.ItemSlot;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import com.comphenix.protocol.wrappers.EnumWrappers.Particle;
import com.comphenix.protocol.wrappers.EnumWrappers.PlayerAction;
import com.comphenix.protocol.wrappers.EnumWrappers.PlayerDigType;
import com.comphenix.protocol.wrappers.EnumWrappers.PlayerInfoAction;
import com.comphenix.protocol.wrappers.EnumWrappers.ResourcePackStatus;
import com.comphenix.protocol.wrappers.EnumWrappers.ScoreboardAction;
import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory;
import com.comphenix.protocol.wrappers.EnumWrappers.TitleAction;
import com.comphenix.protocol.wrappers.EnumWrappers.WorldBorderAction;
import com.comphenix.protocol.wrappers.EnumWrappers.*;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
@ -101,6 +53,15 @@ import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
/**
* Represents a Minecraft packet indirectly.
*
@ -1201,21 +1162,52 @@ public class PacketContainer implements Serializable {
}
// ---- Metadata
// This map will only be initialized if it is actually used
private Map<String, Object> metadata;
/**
* Gets the metadata value for a given key if it exists. Packet metadata expires after a minute, which is far longer
* than a packet will ever be held in processing.
*
* @param key Metadata key
* @param <T> Metadata type
* @return The metadata value, or an empty optional
*/
public <T> Optional<T> getMeta(String key) {
return PacketMetadata.get(handle, key);
}
/**
* Sets the metadata value at a given key. Packet metadata expires after a minute, which is far longer than a packet
* will ever be held in processing.
*
* @param key Metadata key
* @param value Metadata value
* @param <T> Metadata type
*/
public <T> void setMeta(String key, T value) {
PacketMetadata.set(handle, key, value);
}
/**
* Removes the metadata for a given key if it exists.
* @param key Key to remove meta for
*/
public void removeMeta(String key) {
PacketMetadata.remove(handle, key);
}
// ---- Old Metadata API
// Scheduled for removal in 4.5
/**
* Gets the metadata value for a given key.
*
* @param key Metadata key
* @return Metadata value, or null if nonexistent.
* @deprecated Replaced with {@link #getMeta(String)}
*/
@Deprecated
public Object getMetadata(String key) {
if (metadata != null) {
return metadata.get(key);
}
return null;
return getMeta(key).orElse(null);
}
/**
@ -1225,13 +1217,11 @@ public class PacketContainer implements Serializable {
*
* @param key Metadata key
* @param value Metadata value
* @deprecated Replaced by {@link #setMeta(String, Object)}
*/
@Deprecated
public void addMetadata(String key, Object value) {
if (metadata == null) {
metadata = new HashMap<>();
}
metadata.put(key, value);
setMeta(key, value);
}
/**
@ -1241,18 +1231,11 @@ public class PacketContainer implements Serializable {
*
* @param key Metadata key
* @return The previous value, or null if nonexistant.
* @deprecated Replaced by {@link #removeMeta(String)}. This one was pretty much just for naming consistency.
*/
@Deprecated
public Object removeMetadata(String key) {
if (metadata != null) {
Object value = metadata.remove(key);
if (metadata.isEmpty()) {
metadata = null;
}
return value;
}
return null;
return PacketMetadata.remove(handle, key).orElseGet(null);
}
/**
@ -1260,9 +1243,11 @@ public class PacketContainer implements Serializable {
*
* @param key Metadata key
* @return True if this packet has metadata for the key, false if not.
* @deprecated Replaced with {@code getMeta(key).isPresent()}
*/
@Deprecated
public boolean hasMetadata(String key) {
return metadata != null && metadata.containsKey(key);
return getMeta(key).isPresent();
}
/**

View File

@ -0,0 +1,143 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang3.Validate;
/**
* Stores and retrieves metadata for applicable packet objects.
* @author dmulloy2
*/
class PacketMetadata {
private static class MetaObject<T> {
private final String key;
private final T value;
private MetaObject(String key, T value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof MetaObject) {
MetaObject that = (MetaObject) o;
return that.key.equals(this.key) &&
that.value.equals(this.value);
}
return false;
}
@Override
public String toString() {
return "MetaObject[" + key + "=" + value + "]";
}
}
// Packet meta cache
private static Cache<Object, List<MetaObject>> META_CACHE;
public static <T> Optional<T> get(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
if (meta == null) {
return Optional.empty();
}
for (MetaObject object : meta) {
if (object.key.equals(key)) {
return Optional.of((T) object.value);
}
}
return Optional.empty();
}
private static void createCache() {
META_CACHE = CacheBuilder
.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
}
public static <T> void set(Object packet, String key, T value) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
createCache();
}
List<MetaObject> packetMeta;
try {
packetMeta = META_CACHE.get(packet, ArrayList::new);
} catch (ExecutionException ex) {
// Not possible, but let's humor the array list constructor having an issue
packetMeta = new ArrayList<>();
}
packetMeta.removeIf(meta -> meta.key.equals(key));
packetMeta.add(new MetaObject<>(key, value));
META_CACHE.put(packet, packetMeta);
}
public static <T> Optional<T> remove(Object packet, String key) {
Validate.notNull(key, "Null keys are not permitted!");
if (META_CACHE == null) {
return Optional.empty();
}
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
if (packetMeta == null) {
return Optional.empty();
}
Optional<T> value = Optional.empty();
Iterator<MetaObject> iter = packetMeta.iterator();
while (iter.hasNext()) {
MetaObject meta = iter.next();
if (meta.key.equals(key)) {
value = Optional.of((T) meta.value);
iter.remove();
}
}
return value;
}
}