2525package net .labymod .serverapi .api ;
2626
2727import net .labymod .serverapi .api .packet .Direction ;
28+ import net .labymod .serverapi .api .packet .IdentifiablePacket ;
2829import net .labymod .serverapi .api .packet .OnlyWriteConstructor ;
2930import net .labymod .serverapi .api .packet .Packet ;
3031import net .labymod .serverapi .api .packet .PacketHandler ;
3132import net .labymod .serverapi .api .payload .PayloadChannelIdentifier ;
3233import net .labymod .serverapi .api .payload .io .PayloadReader ;
3334import net .labymod .serverapi .api .payload .io .PayloadWriter ;
35+ import org .jetbrains .annotations .ApiStatus ;
3436import org .jetbrains .annotations .NotNull ;
3537import org .jetbrains .annotations .Nullable ;
3638import sun .misc .Unsafe ;
@@ -49,6 +51,7 @@ public class Protocol {
4951 private final Set <ProtocolPacket > packets ;
5052 private final AbstractProtocolService protocolService ;
5153 private final AbstractProtocolService .Side protocolSide ;
54+ private final Set <AwaitingResponse > awaitingResponses ;
5255
5356 /**
5457 * Creates a new protocol.
@@ -67,6 +70,7 @@ public Protocol(
6770 this .protocolService = protocolService ;
6871 this .protocolSide = protocolService .getSide ();
6972 this .packets = new HashSet <>();
73+ this .awaitingResponses = new HashSet <>();
7074 }
7175
7276 /**
@@ -233,6 +237,29 @@ public void handlePacket(@NotNull UUID sender, @NotNull Packet packet) {
233237 return packet ;
234238 }
235239
240+ /**
241+ * Sends a packet to the recipient and waits for the provided response
242+ *
243+ * @param recipient the recipient of the packet
244+ * @param packet the packet to send
245+ * @param responseClass the class of the response packet
246+ * @param responseCallback the callback for the response packet. Return {@code true} to wait
247+ * for another packet or {@code false} remove the callback.
248+ * @param <R> the type of the response packet
249+ * @throws NullPointerException If the recipient, packet, response class or response callback is
250+ * null.
251+ */
252+ public <R extends IdentifiablePacket > void sendPacket (
253+ @ NotNull UUID recipient ,
254+ @ NotNull IdentifiablePacket packet ,
255+ @ NotNull Class <R > responseClass ,
256+ @ NotNull Predicate <R > responseCallback
257+ ) {
258+ Objects .requireNonNull (responseClass , "Response class cannot be null" );
259+ Objects .requireNonNull (responseCallback , "Response callback cannot be null" );
260+ this .sendPacketInternal (recipient , packet , responseClass , responseCallback );
261+ }
262+
236263 /**
237264 * Sends a packet to the recipient.
238265 *
@@ -241,24 +268,19 @@ public void handlePacket(@NotNull UUID sender, @NotNull Packet packet) {
241268 * @throws NullPointerException If the recipient or packet is null.
242269 */
243270 public void sendPacket (@ NotNull UUID recipient , @ NotNull Packet packet ) {
244- Objects .requireNonNull (recipient , "Recipient cannot be null" );
245- Objects .requireNonNull (packet , "Packet cannot be null" );
246- ProtocolPacket protocolPacket = this .getProtocolPacketByClass (packet .getClass ());
247- if (protocolPacket == null ) {
248- throw new IllegalArgumentException (
249- "No packet with the class " + packet .getClass ().getSimpleName () + " in protocol "
250- + this .identifier + "found" );
251- }
271+ this .sendPacketInternal (recipient , packet , null , null );
272+ }
252273
253- if (this .protocolSide .isAcceptingDirection (protocolPacket .direction )) {
254- return ;
274+ /**
275+ * Clears all awaiting responses for the recipient.
276+ *
277+ * @param recipient the recipient to clear the awaiting responses for
278+ */
279+ @ ApiStatus .Internal
280+ public void clearAwaitingResponsesFor (UUID recipient ) {
281+ synchronized (this .awaitingResponses ) {
282+ this .awaitingResponses .removeIf (response -> response .recipient .equals (recipient ));
255283 }
256-
257- PayloadWriter writer = new PayloadWriter ();
258- writer .writeVarInt (protocolPacket .id );
259- packet .write (writer );
260- this .protocolService .send (this .identifier , recipient , writer );
261- this .protocolService .afterPacketSent (this , packet , recipient );
262284 }
263285
264286 private ProtocolPacket getProtocolPacket (Predicate <ProtocolPacket > filter ) {
@@ -280,13 +302,84 @@ private ProtocolPacket getProtocolPacketById(int id) {
280302 }
281303
282304 private void handlePacket (ProtocolPacket protocolPacket , UUID sender , Packet packet ) {
305+ if (packet instanceof IdentifiablePacket identifiablePacket ) {
306+ AwaitingResponse responsePacket = null ;
307+ for (AwaitingResponse awaitingResponse : this .awaitingResponses ) {
308+ if (awaitingResponse .recipient .equals (sender )
309+ && awaitingResponse .responseClass .equals (protocolPacket .packet )
310+ && identifiablePacket .getIdentifier () == awaitingResponse .identifier ) {
311+ responsePacket = awaitingResponse ;
312+ break ;
313+ }
314+ }
315+
316+ if (responsePacket != null ) {
317+ try {
318+ if (!responsePacket .responseCallback .test (identifiablePacket )) {
319+ this .awaitingResponses .remove (responsePacket );
320+ }
321+ } catch (Exception e ) {
322+ e .printStackTrace ();
323+ }
324+ }
325+ }
326+
283327 for (PacketHandler handler : protocolPacket .handlers ) {
284328 handler .handle (sender , packet );
285329 }
286330
287331 this .protocolService .afterPacketHandled (this , packet , sender );
288332 }
289333
334+ private void sendPacketInternal (
335+ @ NotNull UUID recipient ,
336+ @ NotNull Packet packet ,
337+ @ Nullable Class <? extends IdentifiablePacket > responseClass ,
338+ @ Nullable Predicate <? extends IdentifiablePacket > responseCallback
339+ ) {
340+ Objects .requireNonNull (recipient , "Recipient cannot be null" );
341+ Objects .requireNonNull (packet , "Packet cannot be null" );
342+ ProtocolPacket protocolPacket = this .getProtocolPacketByClass (packet .getClass ());
343+ if (protocolPacket == null ) {
344+ throw new IllegalArgumentException (
345+ "No packet with the class " + packet .getClass ().getSimpleName () + " in protocol "
346+ + this .identifier + "found" );
347+ }
348+
349+ if (this .protocolSide .isAcceptingDirection (protocolPacket .direction )) {
350+ return ;
351+ }
352+
353+ PayloadWriter writer = new PayloadWriter ();
354+ writer .writeVarInt (protocolPacket .id );
355+ packet .write (writer );
356+ this .protocolService .send (this .identifier , recipient , writer );
357+ if (responseClass != null && responseCallback != null
358+ && packet instanceof IdentifiablePacket identifiablePacket ) {
359+ synchronized (this .awaitingResponses ) {
360+ this .awaitingResponses .add (new AwaitingResponse (
361+ recipient ,
362+ identifiablePacket ,
363+ identifiablePacket .getIdentifier (),
364+ responseClass ,
365+ responseCallback
366+ ));
367+ }
368+ }
369+
370+ this .protocolService .afterPacketSent (this , packet , recipient );
371+ }
372+
373+ private record AwaitingResponse (
374+ UUID recipient ,
375+ IdentifiablePacket initialPacket ,
376+ int identifier ,
377+ Class responseClass ,
378+ Predicate responseCallback
379+ ) {
380+
381+ }
382+
290383 private static class ProtocolPacket {
291384
292385 private static final UnsafeProvider UNSAFE_PROVIDER = new UnsafeProvider ();
0 commit comments