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;
+
+}