diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSockets.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSockets.java new file mode 100644 index 0000000..dc83c1b --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSockets.java @@ -0,0 +1,436 @@ +package com.codedisaster.steamworks; + +import java.nio.ByteBuffer; + +public class SteamNetworkingSockets extends SteamInterface { + + public static final class Connection { + private final int handle; + + public Connection(int handle) { + this.handle = handle; + } + + public boolean isValid() { + return handle != 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Connection)) return false; + + Connection that = (Connection) o; + return handle == that.handle; + } + + @Override + public int hashCode() { + return handle; + } + + @Override + public String toString() { + return Integer.toHexString(handle); + } + } + + public static final class Socket { + private final int handle; + + public Socket(int handle) { + this.handle = handle; + } + + public boolean isValid() { + return handle != 0; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Connection)) return false; + + Connection that = (Connection) o; + return handle == that.handle; + } + + @Override + public int hashCode() { + return handle; + } + + @Override + public String toString() { + return Integer.toHexString(handle); + } + } + + public enum ConnectionState { + /** + * Dummy value used to indicate an error condition in the API. + * Specified connection doesn't exist or has already been closed. + */ + None(0), + + /** + * We are trying to establish whether peers can talk to each other, + * whether they WANT to talk to each other, perform basic auth, + * and exchange crypt keys. + * + *
+ * This state is similar to CLOSE_WAIT in the TCP state machine. + */ + ClosedByPeer(4), + + /** + * A disruption in the connection has been detected locally. (E.g. timeout, + * local internet connection disrupted, etc.) + *
+ * The connection still exists from an API perspective. You must close the + * handle to free up resources. + *
+ * Attempts to send further messages will fail. Any remaining received messages + * in the queue are available. + */ + ProblemDetectedLocally(5); + + private final int value; + private static final ConnectionState[] values = values(); + + ConnectionState(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ConnectionState byValue(int value) { + for (ConnectionState state : values) { + if (state.value == value) { + return state; + } + } + return None; + } + } + + /** + * Flags used to set options for message sending. + * This is not an enum, because flags are naturally combined with the or operator. + */ + public interface SendFlags { + /** + * Send the message unreliably. Can be lost. Messages *can* be larger than a + * single MTU (UDP packet), but there is no retransmission, so if any piece + * of the message is lost, the entire message will be dropped. + *
+ * The sending API does have some knowledge of the underlying connection, so + * if there is no NAT-traversal accomplished or there is a recognized adjustment + * happening on the connection, the packet will be batched until the connection + * is open again. + *
+ * Migration note: This is not exactly the same as k_EP2PSendUnreliable! You + * probably want k_ESteamNetworkingSendType_UnreliableNoNagle + */ + int Unreliable = 0; + + /** + * Disable Nagle's algorithm. + * By default, Nagle's algorithm is applied to all outbound messages. This means + * that the message will NOT be sent immediately, in case further messages are + * sent soon after you send this, which can be grouped together. Any time there + * is enough buffered data to fill a packet, the packets will be pushed out immediately, + * but partially-full packets not be sent until the Nagle timer expires. See + * ISteamNetworkingSockets::FlushMessagesOnConnection, ISteamNetworkingMessages::FlushMessagesToUser + *
+ * NOTE: Don't just send every message without Nagle because you want packets to get there + * quicker. Make sure you understand the problem that Nagle is solving before disabling it. + * If you are sending small messages, often many at the same time, then it is very likely that + * it will be more efficient to leave Nagle enabled. A typical proper use of this flag is + * when you are sending what you know will be the last message sent for a while (e.g. the last + * in the server simulation tick to a particular client), and you use this flag to flush all + * messages. + */ + int NoNagle = 1; + + /** + * Send a message unreliably, bypassing Nagle's algorithm for this message and any messages + * currently pending on the Nagle timer. This is equivalent to using k_ESteamNetworkingSend_Unreliable + * and then immediately flushing the messages using ISteamNetworkingSockets::FlushMessagesOnConnection + * or ISteamNetworkingMessages::FlushMessagesToUser. (But using this flag is more efficient since you + * only make one API call.) + */ + int UnreliableNoNagle = Unreliable | NoNagle; + + /** + * If the message cannot be sent very soon (because the connection is still doing some initial + * handshaking, route negotiations, etc), then just drop it. This is only applicable for unreliable + * messages. Using this flag on reliable messages is invalid. + */ + int NoDelay = 4; + + /** + * Send an unreliable message, but if it cannot be sent relatively quickly, just drop it instead of queuing it. + * This is useful for messages that are not useful if they are excessively delayed, such as voice data. + * NOTE: The Nagle algorithm is not used, and if the message is not dropped, any messages waiting on the + * Nagle timer are immediately flushed. + *
+ * A message will be dropped under the following circumstances: + * - the connection is not fully connected. (E.g. the "Connecting" or "FindingRoute" states) + * - there is a sufficiently large number of messages queued up already such that the current message + * will not be placed on the wire in the next ~200ms or so. + *
+ * If a message is dropped for these reasons, k_EResultIgnored will be returned. + */ + int UnreliableNoDelay = Unreliable | NoDelay | NoNagle; + + /** + * Reliable message send. Can send up to k_cbMaxSteamNetworkingSocketsMessageSizeSend bytes in a single message. + * Does fragmentation/re-assembly of messages under the hood, as well as a sliding window for + * efficient sends of large chunks of data. + *
+ * The Nagle algorithm is used. See notes on k_ESteamNetworkingSendType_Unreliable for more details. + * See k_ESteamNetworkingSendType_ReliableNoNagle, ISteamNetworkingSockets::FlushMessagesOnConnection, + * ISteamNetworkingMessages::FlushMessagesToUser + *
+ * Migration note: This is NOT the same as k_EP2PSendReliable, it's more like k_EP2PSendReliableWithBuffering + */ + int Reliable = 8; + + /** + * Send a message reliably, but bypass Nagle's algorithm. + *
+ * Migration note: This is equivalent to k_EP2PSendReliable + */ + int ReliableNoNagle = Reliable | NoNagle; + + /** + * By default, message sending is queued, and the work of encryption and talking to + * the operating system sockets, etc is done on a service thread. This is usually a + * a performance win when messages are sent from the "main thread". However, if this + * flag is set, and data is ready to be sent immediately (either from this message + * or earlier queued data), then that work will be done in the current thread, before + * the current call returns. If data is not ready to be sent (due to rate limiting + * or Nagle), then this flag has no effect. + *
+ * This is an advanced flag used to control performance at a very low level. For + * most applications running on modern hardware with more than one CPU core, doing + * the work of sending on a service thread will yield the best performance. Only + * use this flag if you have a really good reason and understand what you are doing. + * Otherwise you will probably just make performance worse. + */ + int UseCurrentThread = 16; + + /** + * When sending a message using ISteamNetworkingMessages, automatically re-establish + * a broken session, without returning k_EResultNoConnection. Without this flag, + * if you attempt to send a message, and the session was proactively closed by the + * peer, or an error occurred that disrupted communications, then you must close the + * session using ISteamNetworkingMessages::CloseSessionWithUser before attempting to + * send another message. (Or you can simply add this flag and retry.) In this way, + * the disruption cannot go unnoticed, and a more clear order of events can be + * ascertained. This is especially important when reliable messages are used, since + * if the connection is disrupted, some of those messages will not have been delivered, + * and it is in general not possible to know which. Although a + * SteamNetworkingMessagesSessionFailed_t callback will be posted when an error occurs + * to notify you that a failure has happened, callbacks are asynchronous, so it is not + * possible to tell exactly when it happened. And because the primary purpose of + * ISteamNetworkingMessages is to be like UDP, there is no notification when a peer closes + * the session. + *
+ * If you are not using any reliable messages (e.g. you are using ISteamNetworkingMessages + * exactly as a transport replacement for UDP-style datagrams only), you may not need to + * know when an underlying connection fails, and so you may not need this notification. + */ + int AutoRestartBrokenSession = 32; + } + + public SteamNetworkingSockets(SteamNetworkingSocketsCallback callback) { + super(SteamNetworkingSocketsNative.createCallback(new SteamNetworkingSocketsCallbackAdapter(callback))); + } + + public Connection connectP2P(SteamID steamID, int virtualPort){ + int result = SteamNetworkingSocketsNative.connectP2P(steamID.handle, virtualPort); + return new Connection(result); + } + + public Socket createListenSocketP2P(int virtualPort){ + int result = SteamNetworkingSocketsNative.createListenSocketP2P(virtualPort); + return new Socket(result); + } + + /** + * Accepts an incoming connection request. + * + * @param connection The handle of the connection to be accepted. + * @return {@link SteamResult} indicating the result of the operation: + *
+ * This method is non-blocking. If no message is currently available, it returns {@code 0}. + * At most one message is received per invocation. + *
+ * + *+ * The message payload is copied into the provided {@link ByteBuffer} starting at the buffer’s + * current {@link ByteBuffer#position()}, and up to {@link ByteBuffer#remaining()} bytes. + * The buffer must be a {@linkplain ByteBuffer#isDirect() direct buffer}. + *
+ * + *+ * If the buffer is too small to hold the incoming message, the message is dropped and a + * {@link SteamException} is thrown. The exception message includes the required buffer size. + *
+ * + *+ * On success, the buffer position is advanced by the number of bytes written. + *
+ * + * @param connection The connection handle to receive data from. + * @param data A direct {@link ByteBuffer} into which the message payload will be written. + * + * @return The number of bytes received in the buffer, or {@code 0} if no message is currently available. + * + * @throws SteamException + * If {@code data} is not a direct buffer, or if the incoming message exceeds + * the buffer's remaining capacity. + * + * @implNote + * The underlying native message is always released before this method returns. + * To fully drain the receive queue, callers should invoke this method repeatedly + * until it returns {@code 0}, optionally enforcing a per-frame processing limit. + */ + public int receiveMessageOnConnection(Connection connection, ByteBuffer data) throws SteamException { + if (!data.isDirect()) { + throw new SteamException("Direct buffer required!"); + } + + int bytesWritten = SteamNetworkingSocketsNative.receiveMessageOnConnection(connection.handle, data, data.position(), data.remaining()); + if (bytesWritten < 0) { + throw new SteamException("Buffer Overflow, bytes received: " + (-bytesWritten) + " bytes remaining: " + data.remaining()); + } + + return bytesWritten; + } + + /** + * Helper method to globally enable k_ESteamNetworkingConfig_SymmetricConnect + * Useful to avoid split brain scenarios for P2P matchmaking + */ + public void enableSymmetricConnect() { + SteamNetworkingSocketsNative.enableSymmetricConnect(); + } +} diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallback.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallback.java new file mode 100644 index 0000000..33d1c28 --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallback.java @@ -0,0 +1,10 @@ +package com.codedisaster.steamworks; + +import com.codedisaster.steamworks.SteamNetworkingSockets.Connection; +import com.codedisaster.steamworks.SteamNetworkingSockets.ConnectionState; + +public interface SteamNetworkingSocketsCallback { + + void onConnectionStatusChanged(Connection connection, SteamID steamID, ConnectionState state, ConnectionState prevState); + +} diff --git a/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallbackAdapter.java b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallbackAdapter.java new file mode 100644 index 0000000..b3723d1 --- /dev/null +++ b/java-wrapper/src/main/java/com/codedisaster/steamworks/SteamNetworkingSocketsCallbackAdapter.java @@ -0,0 +1,15 @@ +package com.codedisaster.steamworks; + +import com.codedisaster.steamworks.SteamNetworkingSockets.Connection; +import com.codedisaster.steamworks.SteamNetworkingSockets.ConnectionState; + +class SteamNetworkingSocketsCallbackAdapter extends SteamCallbackAdapter