diff --git a/gradle.properties b/gradle.properties index 56b1a5a..6c0127f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Group and version group = org.machinemc.paklet -version = 1.1.1 +version = 1.2 # Dependency versions jetbrainsAnnotations = 24.1.0 diff --git a/paklet-api/src/main/java/org/machinemc/paklet/Packet.java b/paklet-api/src/main/java/org/machinemc/paklet/Packet.java index 895c741..bb9bac3 100644 --- a/paklet-api/src/main/java/org/machinemc/paklet/Packet.java +++ b/paklet-api/src/main/java/org/machinemc/paklet/Packet.java @@ -59,10 +59,13 @@ *

* Compare to packet IDs, packets do not store information about their groups when * they are serialized using {@link PacketFactory}. + *

+ * One packet can have multiple different groups, for registering the packet to each group + * under different ID, see {@link Packet#DYNAMIC_PACKET} and {@link PacketRegistrationContext}. * * @return group of the packet */ - String group() default DEFAULT; + String[] group() default DEFAULT; /** * Specifies class that is used as catalogue (identifier) for the packet. diff --git a/paklet-api/src/main/java/org/machinemc/paklet/PacketFactory.java b/paklet-api/src/main/java/org/machinemc/paklet/PacketFactory.java index fbe9af5..268a2bf 100644 --- a/paklet-api/src/main/java/org/machinemc/paklet/PacketFactory.java +++ b/paklet-api/src/main/java/org/machinemc/paklet/PacketFactory.java @@ -3,7 +3,9 @@ import org.jetbrains.annotations.Unmodifiable; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -46,7 +48,7 @@ public interface PacketFactory { * @param reader reader for the packet * @param writer writer for the packet * @param packetID packet ID used to register this packet - * @param group group name of this packet + * @param group group the packet should be registered to * @param packet * * @throws IllegalArgumentException if packet with the same ID and group is already registered @@ -82,15 +84,19 @@ public interface PacketFactory { * Removes packet with given type. * * @param packetClass class of the packet - * @return whether the packet has been removed successfully + * @return array of groups from where the packet has been removed * @param packet */ - default boolean removePacket(Class packetClass) { - int id = getPacketID(packetClass); - if (id == -1) return false; - String group = getPacketGroup(packetClass).orElse(null); - if (group == null) return false; - return removePacket(id, group); + default String[] removePacket(Class packetClass) { + String[] groups = getPacketGroup(packetClass).orElse(new String[0]); + List removed = new ArrayList<>(); + for (String group : groups) { + int id = getPacketID(packetClass, group); + if (id == -1) continue; + if (!removePacket(id, group)) continue; + removed.add(group); + } + return removed.toArray(String[]::new); } /** @@ -115,11 +121,12 @@ default boolean removePacket(Class packetClass) { * Returns ID for given registered packet class. * * @param packetClass packet class + * @param group group of the packet * @return packet ID of given packet class, or {@code -1} if the class is * not registered * @param packet */ - int getPacketID(Class packetClass); + int getPacketID(Class packetClass, String group); /** * Returns group for given registered packet class. @@ -128,17 +135,18 @@ default boolean removePacket(Class packetClass) { * @return packet class of given packet class * @param packet */ - Optional getPacketGroup(Class packetClass); + Optional getPacketGroup(Class packetClass); /** - * Checks whether the given packet class is registered. + * Checks whether the given packet class is registered in given group. * * @param packetClass packet class - * @return whether the packet class is registered + * @param group group + * @return whether the packet class is registered in the group * @param packet */ - default boolean isRegistered(Class packetClass) { - return getPacketID(packetClass) != -1; + default boolean isRegistered(Class packetClass, String group) { + return getPacketID(packetClass, group) != -1; } /** @@ -186,9 +194,10 @@ default boolean isRegistered(int packetID, String group) { * Writes packet to the provided data visitor. * * @param packet packet to write + * @param group group * @param visitor visitor * @param packet */ - void write(PacketType packet, DataVisitor visitor); + void write(PacketType packet, String group, DataVisitor visitor); } diff --git a/paklet-api/src/main/java/org/machinemc/paklet/PacketID.java b/paklet-api/src/main/java/org/machinemc/paklet/PacketID.java index 9cbbaa4..5b1b2ad 100644 --- a/paklet-api/src/main/java/org/machinemc/paklet/PacketID.java +++ b/paklet-api/src/main/java/org/machinemc/paklet/PacketID.java @@ -6,12 +6,12 @@ import java.lang.annotation.Target; /** - * Used to annotate static int fields of packet classes with - * dynamic packet IDs. + * Used to annotate static int fields (or no argument methods returning int) + * of packet classes to compute dynamic packet IDs. * * @see Packet#DYNAMIC_PACKET */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) public @interface PacketID { } diff --git a/paklet-api/src/main/java/org/machinemc/paklet/PacketRegistrationContext.java b/paklet-api/src/main/java/org/machinemc/paklet/PacketRegistrationContext.java new file mode 100644 index 0000000..9578e2b --- /dev/null +++ b/paklet-api/src/main/java/org/machinemc/paklet/PacketRegistrationContext.java @@ -0,0 +1,51 @@ +package org.machinemc.paklet; + +import java.util.Objects; + +/** + * Allows to access the additional information during dynamic packet registration + * for packets with {@link Packet#DYNAMIC_PACKET} ID. + */ +public class PacketRegistrationContext { + + protected static final ThreadLocal threadLocal = ThreadLocal.withInitial(PacketRegistrationContext::new); + + private final String group; + + /** + * Returns the current packet registration context. + *

+ * This can be called only within methods annotated with {@link PacketID}, resolving + * packet IDs for packets with {@link Packet#DYNAMIC_PACKET} ID. + * + * @return current packet registration context + */ + public static PacketRegistrationContext get() { + PacketRegistrationContext context = threadLocal.get(); + if (context.group == null) throw new RuntimeException("Called outside of dynamic packet registration context"); + return context; + } + + private PacketRegistrationContext() { + group = null; + } + + /** + * Creates new packet registration context with given group. + * + * @param group group + */ + protected PacketRegistrationContext(String group) { + this.group = Objects.requireNonNull(group, "Packet group can not be null"); + } + + /** + * Returns packet group used to register the packet. + * + * @return current packet group + */ + public String getPacketGroup() { + return group; + } + +} diff --git a/paklet-core/src/main/java/org/machinemc/paklet/PacketFactoryImpl.java b/paklet-core/src/main/java/org/machinemc/paklet/PacketFactoryImpl.java index 0d6d5cd..f2f57e8 100644 --- a/paklet-core/src/main/java/org/machinemc/paklet/PacketFactoryImpl.java +++ b/paklet-core/src/main/java/org/machinemc/paklet/PacketFactoryImpl.java @@ -10,9 +10,12 @@ import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Exchanger; import java.util.function.Function; /** @@ -23,7 +26,7 @@ public class PacketFactoryImpl implements PacketFactory { private final PacketEncoder encoder; private final SerializerProvider serializerProvider; - private final Map, PacketGroup> packet2Group = new ConcurrentHashMap<>(); + private final Map, List> packet2Group = new ConcurrentHashMap<>(); private final Map groups = new ConcurrentHashMap<>(); public PacketFactoryImpl(PacketEncoder encoder, SerializerProvider serializerProvider) { @@ -54,7 +57,8 @@ public void addPacket(Class packetClass) { public void addPacket(Class packetClass, PacketReader reader, PacketWriter writer) { Packet annotation = packetClass.getAnnotation(Packet.class); if (annotation == null) throw new IllegalArgumentException("Class " + packetClass.getName() + " is not a valid packet class"); - addPacket(packetClass, reader, writer, computePacketID(packetClass), annotation.group()); + for (String group : annotation.group()) + addPacket(packetClass, reader, writer, computePacketID(packetClass, group), group); } @Override @@ -62,9 +66,14 @@ public void addPacket(Class packetClass, PacketReader

groupsList = new CopyOnWriteArrayList<>(packet2Group.computeIfAbsent(packetClass, __ -> new ArrayList<>())); + groupsList.add(packetGroup); + + packet2Group.put(packetClass, Collections.unmodifiableList(groupsList)); } @Override @@ -96,15 +105,15 @@ public Optional> getPacketClass(int packetID, Str } @Override - public int getPacketID(Class packetClass) { - PacketGroup packetGroup = packet2Group.get(packetClass); + public int getPacketID(Class packetClass, String group) { + PacketGroup packetGroup = groups.get(group); if (packetGroup == null) return -1; return packetGroup.getID(packetClass); } @Override - public Optional getPacketGroup(Class packetClass) { - return Optional.ofNullable(packet2Group.get(packetClass)).map(PacketGroup::getName); + public Optional getPacketGroup(Class packetClass) { + return Optional.ofNullable(packet2Group.get(packetClass)).map(l -> l.stream().map(PacketGroup::getName).toArray(String[]::new)); } @Override @@ -138,10 +147,10 @@ public PacketType create(int packetID, String group, DataVisitor vi @Override @SuppressWarnings("unchecked") - public void write(PacketType packet, DataVisitor visitor) { + public void write(PacketType packet, String group, DataVisitor visitor) { Class packetClass = packet.getClass(); - PacketGroup packetGroup = packet2Group.get(packetClass); - if (packetGroup == null) throw new NullPointerException("Packet " + packetClass.getName() + " is not assigned to any group"); + PacketGroup packetGroup = groups.get(group); + if (packetGroup == null) throw new NullPointerException("Group " + group + " is not registered"); int packetID = packetGroup.getID(packetClass); if (packetID < 0) throw new IllegalArgumentException("Invalid packet ID: " + packetID); @@ -156,26 +165,64 @@ public void write(PacketType packet, DataVisitor visitor) { encoder.encode(visitor, serializerProvider, packetGroup.getName(), new PacketEncoder.Encoded(packetID, packetData)); } - private int computePacketID(Class packetClass) { + private int computePacketID(Class packetClass, String group) { Packet annotation = packetClass.getAnnotation(Packet.class); if (annotation == null) throw new IllegalArgumentException("Class " + packetClass.getName() + " is not a valid packet class"); + if (annotation.id() == Packet.INVALID_PACKET) return Packet.INVALID_PACKET; + if (annotation.id() == Packet.DYNAMIC_PACKET) { + Field[] packetIDFields = Arrays.stream(packetClass.getDeclaredFields()) .filter(f -> Modifier.isStatic(f.getModifiers())) .filter(f -> f.getType().equals(int.class)) .filter(f -> f.isAnnotationPresent(PacketID.class)) .toArray(Field[]::new); - if (packetIDFields.length == 0) throw new IllegalStateException("Class " + packetClass.getName() + " is missing packet ID field"); if (packetIDFields.length > 1) throw new IllegalStateException("Class " + packetClass.getName() + " has more than one packet ID field"); + if (packetIDFields.length == 1) { + try { + packetIDFields[0].setAccessible(true); + return checkPacketID((int) packetIDFields[0].get(null)); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + Method[] packetIDMethods = Arrays.stream(packetClass.getDeclaredMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers())) + .filter(m -> m.getReturnType().equals(int.class)) + .filter(m -> m.getParameterTypes().length == 0) + .filter(m -> m.isAnnotationPresent(PacketID.class)) + .toArray(Method[]::new); + if (packetIDMethods.length == 0) throw new IllegalStateException("Class " + packetClass.getName() + " is missing packet ID field or method"); + if (packetIDMethods.length > 1) throw new IllegalStateException("Class " + packetClass.getName() + " has more than one packet ID method"); try { - packetIDFields[0].setAccessible(true); - return (int) packetIDFields[0].get(null); + packetIDMethods[0].setAccessible(true); + Exchanger idResolver = new Exchanger<>(); + Thread.ofVirtual().start(() -> { + try { + PacketRegistrationContext.threadLocal.set(new PacketRegistrationContext(group)); + idResolver.exchange((int) packetIDMethods[0].invoke(null)); + } catch (Throwable throwable) { + try { + idResolver.exchange(Packet.DYNAMIC_PACKET); + } catch (InterruptedException exception) { + throw new RuntimeException(exception); + } + } + }); + int resolved = idResolver.exchange(null); + return checkPacketID(resolved); } catch (Exception exception) { throw new RuntimeException(exception); } } - return annotation.id(); + return checkPacketID(annotation.id()); + } + + private int checkPacketID(int id) { + if (id > 0 || id == Packet.INVALID_PACKET) return id; + throw new RuntimeException("Invalid packet ID: " + id); } static class PacketGroup { diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/CollectionLengthTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/CollectionLengthTest.java index 1fa9655..2739f1f 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/CollectionLengthTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/CollectionLengthTest.java @@ -24,7 +24,7 @@ public void doNotPrefixTest() { packet.signature[1] = 2; packet.signature[2] = 3; - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); assert visitor.writerIndex() == 257; diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/CustomSerializerTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/CustomSerializerTest.java index 58c22e6..9d96405 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/CustomSerializerTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/CustomSerializerTest.java @@ -19,7 +19,7 @@ public void customSerializerTest() { CustomSerializerPacket packet = new CustomSerializerPacket(); packet.content = "Hello World"; - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); CustomSerializerPacket packetClone = factory.create(Packet.DEFAULT, visitor); assert packetClone.content.equals(packet.content); diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/DynamicPacketTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/DynamicPacketTest.java new file mode 100644 index 0000000..39e3187 --- /dev/null +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/DynamicPacketTest.java @@ -0,0 +1,37 @@ +package org.machinemc.paklet.test; + +import io.netty.buffer.Unpooled; +import org.junit.jupiter.api.Test; +import org.machinemc.paklet.DataVisitor; +import org.machinemc.paklet.PacketFactory; +import org.machinemc.paklet.netty.NettyDataVisitor; +import org.machinemc.paklet.serialization.VarIntSerializer; +import org.machinemc.paklet.test.packet.DynamicPacket; + +public class DynamicPacketTest { + + @Test + public void testDynamicPacketIDs() { + PacketFactory factory = TestUtil.createFactory(); + + assert factory.getPacketID(DynamicPacket.class, "one") == 21; + assert factory.getPacketID(DynamicPacket.class, "two") == 22; + assert factory.getPacketID(DynamicPacket.class, "three") == 23; + + DataVisitor visitor = new NettyDataVisitor(Unpooled.buffer()); + + DynamicPacket packet = new DynamicPacket(); + packet.value = 15; + + factory.write(packet, "two", visitor); + + assert visitor.read(null, new VarIntSerializer()) == 22; + + visitor.readerIndex(0); + + DynamicPacket packetClone = factory.create("two", visitor); + + assert packetClone.value == packet.value; + } + +} diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/IgnoreTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/IgnoreTest.java index 7e3136f..5890c29 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/IgnoreTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/IgnoreTest.java @@ -18,7 +18,7 @@ public void ignoreTest() { packet.ignore = "Foo"; packet.value = 10; - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); IgnorePacket packetClone = factory.create(Packet.DEFAULT, visitor); assert packetClone.value == packet.value; diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/ParameterTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/ParameterTest.java index 9a5b58f..522eff2 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/ParameterTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/ParameterTest.java @@ -32,7 +32,7 @@ public void parameterTest() { packet.treeMap.put("Hi", "World"); packet.treeMap.put("Hello", "World"); - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); CollectionsPacket packetClone = factory.create(Packet.DEFAULT, visitor); assert packetClone.contents.equals(packet.contents); diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/ProcessorsTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/ProcessorsTest.java index b630e8b..4289a8c 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/ProcessorsTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/ProcessorsTest.java @@ -38,7 +38,7 @@ public void basicTest() { testPacket.height = 20; testPacket.name = "Foo"; testPacket.value = 5; - factory.write(testPacket, visitor); + factory.write(testPacket, Packet.DEFAULT, visitor); TestPacket testPacketClone = factory.create(Packet.DEFAULT, visitor); assert testPacketClone.height == testPacket.height; @@ -47,7 +47,7 @@ public void basicTest() { TestCustomLogicCustomPacket testCustomLogicPacket = new TestCustomLogicCustomPacket(); testCustomLogicPacket.value = 20; - factory.write(testCustomLogicPacket, visitor); + factory.write(testCustomLogicPacket, Packet.DEFAULT, visitor); TestCustomLogicCustomPacket testCustomLogicPacketClone = factory.create(Packet.DEFAULT, visitor); assert testCustomLogicPacketClone.value == testCustomLogicPacket.value; @@ -64,7 +64,7 @@ public void arrayTest() { arrayPacket.nestedArray = new String[][]{new String[]{"Hello", "World"}, new String[]{"foo", "bar"}}; arrayPacket.optionalElements = new String[]{"Hello", "optional", null}; - factory.write(arrayPacket, visitor); + factory.write(arrayPacket, Packet.DEFAULT, visitor); ArrayPacket arrayPacketClone = factory.create(Packet.DEFAULT, visitor); assert Arrays.compare(arrayPacketClone.stringArray, arrayPacket.stringArray) == 0; diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/SerializationTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/SerializationTest.java index 46e07fe..fea85ab 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/SerializationTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/SerializationTest.java @@ -25,7 +25,7 @@ public void arrayTest() { packet.currency = Currency.getInstance(Locale.US); packet.state = RulesTestingPacket.State.COMPLETED; - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); RulesTestingPacket packetClone = factory.create(Packet.DEFAULT, visitor); assert packetClone.date.equals(packet.date); @@ -42,7 +42,7 @@ public void dynamicPacketIDTest() { CustomIDPacket packet = new CustomIDPacket(); packet.message = "Hello world"; - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); CustomIDPacket packetClone = factory.create(Packet.DEFAULT, visitor); assert packet.message.equals(packetClone.message); diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/SerializerAliasesTest.java b/paklet-core/src/test/java/org/machinemc/paklet/test/SerializerAliasesTest.java index 7c477af..864b6c8 100644 --- a/paklet-core/src/test/java/org/machinemc/paklet/test/SerializerAliasesTest.java +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/SerializerAliasesTest.java @@ -19,7 +19,7 @@ public void aliasesTest() { AliasPacket packet = new AliasPacket(); packet.foo = 1; - factory.write(packet, visitor); + factory.write(packet, Packet.DEFAULT, visitor); AliasPacket packetClone = factory.create(Packet.DEFAULT, visitor); assert packetClone.foo == packet.foo; diff --git a/paklet-core/src/test/java/org/machinemc/paklet/test/packet/DynamicPacket.java b/paklet-core/src/test/java/org/machinemc/paklet/test/packet/DynamicPacket.java new file mode 100644 index 0000000..7db2e2f --- /dev/null +++ b/paklet-core/src/test/java/org/machinemc/paklet/test/packet/DynamicPacket.java @@ -0,0 +1,24 @@ +package org.machinemc.paklet.test.packet; + +import org.machinemc.paklet.Packet; +import org.machinemc.paklet.PacketID; +import org.machinemc.paklet.PacketRegistrationContext; +import org.machinemc.paklet.test.TestPackets; + +@Packet(id = Packet.DYNAMIC_PACKET, group = {"one", "two", "three"}, catalogue = TestPackets.class) +public class DynamicPacket { + + @PacketID + private static int id() { + String group = PacketRegistrationContext.get().getPacketGroup(); + return switch (group) { + case "one" -> 21; + case "two" -> 22; + case "three" -> 23; + default -> Packet.INVALID_PACKET; + }; + } + + public int value; + +}