Skip to content

Commit 23b1d1c

Browse files
committed
added option to register packets to multiple groups with different IDs
1 parent b096773 commit 23b1d1c

File tree

15 files changed

+217
-46
lines changed

15 files changed

+217
-46
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Group and version
22
group = org.machinemc.paklet
3-
version = 1.1.1
3+
version = 1.2
44

55
# Dependency versions
66
jetbrainsAnnotations = 24.1.0

paklet-api/src/main/java/org/machinemc/paklet/Packet.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,13 @@
5959
* <p>
6060
* Compare to packet IDs, packets do not store information about their groups when
6161
* they are serialized using {@link PacketFactory}.
62+
* <p>
63+
* One packet can have multiple different groups, for registering the packet to each group
64+
* under different ID, see {@link Packet#DYNAMIC_PACKET} and {@link PacketRegistrationContext}.
6265
*
6366
* @return group of the packet
6467
*/
65-
String group() default DEFAULT;
68+
String[] group() default DEFAULT;
6669

6770
/**
6871
* Specifies class that is used as catalogue (identifier) for the packet.

paklet-api/src/main/java/org/machinemc/paklet/PacketFactory.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import org.jetbrains.annotations.Unmodifiable;
44

55
import java.io.InputStream;
6+
import java.util.ArrayList;
67
import java.util.Collection;
8+
import java.util.List;
79
import java.util.Optional;
810
import java.util.function.Function;
911

@@ -46,7 +48,7 @@ public interface PacketFactory {
4648
* @param reader reader for the packet
4749
* @param writer writer for the packet
4850
* @param packetID packet ID used to register this packet
49-
* @param group group name of this packet
51+
* @param group group the packet should be registered to
5052
* @param <PacketType> packet
5153
*
5254
* @throws IllegalArgumentException if packet with the same ID and group is already registered
@@ -82,15 +84,19 @@ public interface PacketFactory {
8284
* Removes packet with given type.
8385
*
8486
* @param packetClass class of the packet
85-
* @return whether the packet has been removed successfully
87+
* @return array of groups from where the packet has been removed
8688
* @param <PacketType> packet
8789
*/
88-
default <PacketType> boolean removePacket(Class<PacketType> packetClass) {
89-
int id = getPacketID(packetClass);
90-
if (id == -1) return false;
91-
String group = getPacketGroup(packetClass).orElse(null);
92-
if (group == null) return false;
93-
return removePacket(id, group);
90+
default <PacketType> String[] removePacket(Class<PacketType> packetClass) {
91+
String[] groups = getPacketGroup(packetClass).orElse(new String[0]);
92+
List<String> removed = new ArrayList<>();
93+
for (String group : groups) {
94+
int id = getPacketID(packetClass, group);
95+
if (id == -1) continue;
96+
if (!removePacket(id, group)) continue;
97+
removed.add(group);
98+
}
99+
return removed.toArray(String[]::new);
94100
}
95101

96102
/**
@@ -115,11 +121,12 @@ default <PacketType> boolean removePacket(Class<PacketType> packetClass) {
115121
* Returns ID for given registered packet class.
116122
*
117123
* @param packetClass packet class
124+
* @param group group of the packet
118125
* @return packet ID of given packet class, or {@code -1} if the class is
119126
* not registered
120127
* @param <PacketType> packet
121128
*/
122-
<PacketType> int getPacketID(Class<PacketType> packetClass);
129+
<PacketType> int getPacketID(Class<PacketType> packetClass, String group);
123130

124131
/**
125132
* Returns group for given registered packet class.
@@ -128,17 +135,18 @@ default <PacketType> boolean removePacket(Class<PacketType> packetClass) {
128135
* @return packet class of given packet class
129136
* @param <PacketType> packet
130137
*/
131-
<PacketType> Optional<String> getPacketGroup(Class<PacketType> packetClass);
138+
<PacketType> Optional<String[]> getPacketGroup(Class<PacketType> packetClass);
132139

133140
/**
134-
* Checks whether the given packet class is registered.
141+
* Checks whether the given packet class is registered in given group.
135142
*
136143
* @param packetClass packet class
137-
* @return whether the packet class is registered
144+
* @param group group
145+
* @return whether the packet class is registered in the group
138146
* @param <PacketType> packet
139147
*/
140-
default <PacketType> boolean isRegistered(Class<PacketType> packetClass) {
141-
return getPacketID(packetClass) != -1;
148+
default <PacketType> boolean isRegistered(Class<PacketType> packetClass, String group) {
149+
return getPacketID(packetClass, group) != -1;
142150
}
143151

144152
/**
@@ -186,9 +194,10 @@ default boolean isRegistered(int packetID, String group) {
186194
* Writes packet to the provided data visitor.
187195
*
188196
* @param packet packet to write
197+
* @param group group
189198
* @param visitor visitor
190199
* @param <PacketType> packet
191200
*/
192-
<PacketType> void write(PacketType packet, DataVisitor visitor);
201+
<PacketType> void write(PacketType packet, String group, DataVisitor visitor);
193202

194203
}

paklet-api/src/main/java/org/machinemc/paklet/PacketID.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
import java.lang.annotation.Target;
77

88
/**
9-
* Used to annotate static int fields of packet classes with
10-
* dynamic packet IDs.
9+
* Used to annotate static int fields (or no argument methods returning int)
10+
* of packet classes to compute dynamic packet IDs.
1111
*
1212
* @see Packet#DYNAMIC_PACKET
1313
*/
1414
@Retention(RetentionPolicy.RUNTIME)
15-
@Target(ElementType.FIELD)
15+
@Target({ElementType.FIELD, ElementType.METHOD})
1616
public @interface PacketID {
1717
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.machinemc.paklet;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* Allows to access the additional information during dynamic packet registration
7+
* for packets with {@link Packet#DYNAMIC_PACKET} ID.
8+
*/
9+
public class PacketRegistrationContext {
10+
11+
protected static final ThreadLocal<PacketRegistrationContext> threadLocal = ThreadLocal.withInitial(PacketRegistrationContext::new);
12+
13+
private final String group;
14+
15+
/**
16+
* Returns the current packet registration context.
17+
* <p>
18+
* This can be called only within methods annotated with {@link PacketID}, resolving
19+
* packet IDs for packets with {@link Packet#DYNAMIC_PACKET} ID.
20+
*
21+
* @return current packet registration context
22+
*/
23+
public static PacketRegistrationContext get() {
24+
PacketRegistrationContext context = threadLocal.get();
25+
if (context.group == null) throw new RuntimeException("Called outside of dynamic packet registration context");
26+
return context;
27+
}
28+
29+
private PacketRegistrationContext() {
30+
group = null;
31+
}
32+
33+
/**
34+
* Creates new packet registration context with given group.
35+
*
36+
* @param group group
37+
*/
38+
protected PacketRegistrationContext(String group) {
39+
this.group = Objects.requireNonNull(group, "Packet group can not be null");
40+
}
41+
42+
/**
43+
* Returns packet group used to register the packet.
44+
*
45+
* @return current packet group
46+
*/
47+
public String getPacketGroup() {
48+
return group;
49+
}
50+
51+
}

paklet-core/src/main/java/org/machinemc/paklet/PacketFactoryImpl.java

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
import java.io.InputStream;
1212
import java.lang.reflect.Field;
13+
import java.lang.reflect.Method;
1314
import java.lang.reflect.Modifier;
1415
import java.util.*;
1516
import java.util.concurrent.ConcurrentHashMap;
17+
import java.util.concurrent.CopyOnWriteArrayList;
18+
import java.util.concurrent.Exchanger;
1619
import java.util.function.Function;
1720

1821
/**
@@ -23,7 +26,7 @@ public class PacketFactoryImpl implements PacketFactory {
2326
private final PacketEncoder encoder;
2427
private final SerializerProvider serializerProvider;
2528

26-
private final Map<Class<?>, PacketGroup> packet2Group = new ConcurrentHashMap<>();
29+
private final Map<Class<?>, List<PacketGroup>> packet2Group = new ConcurrentHashMap<>();
2730
private final Map<String, PacketGroup> groups = new ConcurrentHashMap<>();
2831

2932
public PacketFactoryImpl(PacketEncoder encoder, SerializerProvider serializerProvider) {
@@ -54,17 +57,23 @@ public <PacketType> void addPacket(Class<PacketType> packetClass) {
5457
public <PacketType> void addPacket(Class<PacketType> packetClass, PacketReader<PacketType> reader, PacketWriter<PacketType> writer) {
5558
Packet annotation = packetClass.getAnnotation(Packet.class);
5659
if (annotation == null) throw new IllegalArgumentException("Class " + packetClass.getName() + " is not a valid packet class");
57-
addPacket(packetClass, reader, writer, computePacketID(packetClass), annotation.group());
60+
for (String group : annotation.group())
61+
addPacket(packetClass, reader, writer, computePacketID(packetClass, group), group);
5862
}
5963

6064
@Override
6165
public <PacketType> void addPacket(Class<PacketType> packetClass, PacketReader<PacketType> reader, PacketWriter<PacketType> writer, int packetID, String group) {
6266
if (packetClass == null || reader == null || writer == null) throw new NullPointerException();
6367
if (packetID == Packet.INVALID_PACKET) return; // invalid packets should be ignored
6468
if (packetID < 0) throw new IllegalArgumentException("Invalid packet ID for packet " + packetClass.getName());
65-
PacketGroup packetGroup = groups.computeIfAbsent(group, PacketGroup::new);
69+
70+
PacketGroup packetGroup = this.groups.computeIfAbsent(group, PacketGroup::new);
6671
packetGroup.addPacket(packetID, packetClass, reader, writer); // throws illegal exception if packet ID already exists
67-
packet2Group.put(packetClass, packetGroup);
72+
73+
List<PacketGroup> groupsList = new CopyOnWriteArrayList<>(packet2Group.computeIfAbsent(packetClass, __ -> new ArrayList<>()));
74+
groupsList.add(packetGroup);
75+
76+
packet2Group.put(packetClass, Collections.unmodifiableList(groupsList));
6877
}
6978

7079
@Override
@@ -96,15 +105,15 @@ public <PacketType> Optional<Class<PacketType>> getPacketClass(int packetID, Str
96105
}
97106

98107
@Override
99-
public <PacketType> int getPacketID(Class<PacketType> packetClass) {
100-
PacketGroup packetGroup = packet2Group.get(packetClass);
108+
public <PacketType> int getPacketID(Class<PacketType> packetClass, String group) {
109+
PacketGroup packetGroup = groups.get(group);
101110
if (packetGroup == null) return -1;
102111
return packetGroup.getID(packetClass);
103112
}
104113

105114
@Override
106-
public <PacketType> Optional<String> getPacketGroup(Class<PacketType> packetClass) {
107-
return Optional.ofNullable(packet2Group.get(packetClass)).map(PacketGroup::getName);
115+
public <PacketType> Optional<String[]> getPacketGroup(Class<PacketType> packetClass) {
116+
return Optional.ofNullable(packet2Group.get(packetClass)).map(l -> l.stream().map(PacketGroup::getName).toArray(String[]::new));
108117
}
109118

110119
@Override
@@ -138,10 +147,10 @@ public <PacketType> PacketType create(int packetID, String group, DataVisitor vi
138147

139148
@Override
140149
@SuppressWarnings("unchecked")
141-
public <PacketType> void write(PacketType packet, DataVisitor visitor) {
150+
public <PacketType> void write(PacketType packet, String group, DataVisitor visitor) {
142151
Class<?> packetClass = packet.getClass();
143-
PacketGroup packetGroup = packet2Group.get(packetClass);
144-
if (packetGroup == null) throw new NullPointerException("Packet " + packetClass.getName() + " is not assigned to any group");
152+
PacketGroup packetGroup = groups.get(group);
153+
if (packetGroup == null) throw new NullPointerException("Group " + group + " is not registered");
145154

146155
int packetID = packetGroup.getID(packetClass);
147156
if (packetID < 0) throw new IllegalArgumentException("Invalid packet ID: " + packetID);
@@ -156,26 +165,64 @@ public <PacketType> void write(PacketType packet, DataVisitor visitor) {
156165
encoder.encode(visitor, serializerProvider, packetGroup.getName(), new PacketEncoder.Encoded(packetID, packetData));
157166
}
158167

159-
private int computePacketID(Class<?> packetClass) {
168+
private int computePacketID(Class<?> packetClass, String group) {
160169
Packet annotation = packetClass.getAnnotation(Packet.class);
161170
if (annotation == null) throw new IllegalArgumentException("Class " + packetClass.getName() + " is not a valid packet class");
171+
162172
if (annotation.id() == Packet.INVALID_PACKET) return Packet.INVALID_PACKET;
173+
163174
if (annotation.id() == Packet.DYNAMIC_PACKET) {
175+
164176
Field[] packetIDFields = Arrays.stream(packetClass.getDeclaredFields())
165177
.filter(f -> Modifier.isStatic(f.getModifiers()))
166178
.filter(f -> f.getType().equals(int.class))
167179
.filter(f -> f.isAnnotationPresent(PacketID.class))
168180
.toArray(Field[]::new);
169-
if (packetIDFields.length == 0) throw new IllegalStateException("Class " + packetClass.getName() + " is missing packet ID field");
170181
if (packetIDFields.length > 1) throw new IllegalStateException("Class " + packetClass.getName() + " has more than one packet ID field");
182+
if (packetIDFields.length == 1) {
183+
try {
184+
packetIDFields[0].setAccessible(true);
185+
return checkPacketID((int) packetIDFields[0].get(null));
186+
} catch (Exception exception) {
187+
throw new RuntimeException(exception);
188+
}
189+
}
190+
191+
Method[] packetIDMethods = Arrays.stream(packetClass.getDeclaredMethods())
192+
.filter(m -> Modifier.isStatic(m.getModifiers()))
193+
.filter(m -> m.getReturnType().equals(int.class))
194+
.filter(m -> m.getParameterTypes().length == 0)
195+
.filter(m -> m.isAnnotationPresent(PacketID.class))
196+
.toArray(Method[]::new);
197+
if (packetIDMethods.length == 0) throw new IllegalStateException("Class " + packetClass.getName() + " is missing packet ID field or method");
198+
if (packetIDMethods.length > 1) throw new IllegalStateException("Class " + packetClass.getName() + " has more than one packet ID method");
171199
try {
172-
packetIDFields[0].setAccessible(true);
173-
return (int) packetIDFields[0].get(null);
200+
packetIDMethods[0].setAccessible(true);
201+
Exchanger<Integer> idResolver = new Exchanger<>();
202+
Thread.ofVirtual().start(() -> {
203+
try {
204+
PacketRegistrationContext.threadLocal.set(new PacketRegistrationContext(group));
205+
idResolver.exchange((int) packetIDMethods[0].invoke(null));
206+
} catch (Throwable throwable) {
207+
try {
208+
idResolver.exchange(Packet.DYNAMIC_PACKET);
209+
} catch (InterruptedException exception) {
210+
throw new RuntimeException(exception);
211+
}
212+
}
213+
});
214+
int resolved = idResolver.exchange(null);
215+
return checkPacketID(resolved);
174216
} catch (Exception exception) {
175217
throw new RuntimeException(exception);
176218
}
177219
}
178-
return annotation.id();
220+
return checkPacketID(annotation.id());
221+
}
222+
223+
private int checkPacketID(int id) {
224+
if (id > 0 || id == Packet.INVALID_PACKET) return id;
225+
throw new RuntimeException("Invalid packet ID: " + id);
179226
}
180227

181228
static class PacketGroup {

paklet-core/src/test/java/org/machinemc/paklet/test/CollectionLengthTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public void doNotPrefixTest() {
2424
packet.signature[1] = 2;
2525
packet.signature[2] = 3;
2626

27-
factory.write(packet, visitor);
27+
factory.write(packet, Packet.DEFAULT, visitor);
2828

2929
assert visitor.writerIndex() == 257;
3030

paklet-core/src/test/java/org/machinemc/paklet/test/CustomSerializerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public void customSerializerTest() {
1919
CustomSerializerPacket packet = new CustomSerializerPacket();
2020
packet.content = "Hello World";
2121

22-
factory.write(packet, visitor);
22+
factory.write(packet, Packet.DEFAULT, visitor);
2323
CustomSerializerPacket packetClone = factory.create(Packet.DEFAULT, visitor);
2424

2525
assert packetClone.content.equals(packet.content);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.machinemc.paklet.test;
2+
3+
import io.netty.buffer.Unpooled;
4+
import org.junit.jupiter.api.Test;
5+
import org.machinemc.paklet.DataVisitor;
6+
import org.machinemc.paklet.PacketFactory;
7+
import org.machinemc.paklet.netty.NettyDataVisitor;
8+
import org.machinemc.paklet.serialization.VarIntSerializer;
9+
import org.machinemc.paklet.test.packet.DynamicPacket;
10+
11+
public class DynamicPacketTest {
12+
13+
@Test
14+
public void testDynamicPacketIDs() {
15+
PacketFactory factory = TestUtil.createFactory();
16+
17+
assert factory.getPacketID(DynamicPacket.class, "one") == 21;
18+
assert factory.getPacketID(DynamicPacket.class, "two") == 22;
19+
assert factory.getPacketID(DynamicPacket.class, "three") == 23;
20+
21+
DataVisitor visitor = new NettyDataVisitor(Unpooled.buffer());
22+
23+
DynamicPacket packet = new DynamicPacket();
24+
packet.value = 15;
25+
26+
factory.write(packet, "two", visitor);
27+
28+
assert visitor.read(null, new VarIntSerializer()) == 22;
29+
30+
visitor.readerIndex(0);
31+
32+
DynamicPacket packetClone = factory.create("two", visitor);
33+
34+
assert packetClone.value == packet.value;
35+
}
36+
37+
}

0 commit comments

Comments
 (0)