Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
## [2.0.0-pre.8] - 2023-03-30

### New features
* `MultiNetworkDriver` can then be used for client drivers too. The restriction on it accepting only listening drivers has been lifted, and it now offers a new `Connect` method to connect client drivers. This makes it easier to write networking code that can be shared between server and client.
* Added a new `ReliableUtility.SetMaximumResendTime` static method, allowing to modify the maximum resend time of the reliable pipeline at runtime (there's already a similar method for the minimum resend time). Increasing this value can improve bandwidth usage for poor connections (RTT greater than 200ms).
* Added the possibility of setting the minimum and maximum resend times of the reliable pipeline through `NetworkSettings` (with `WithReliableStageParameters`).

### Changes
* `NetworkEndpoint.TryParse` will now return false and log an error when attempting to parse an IPv6 address on platforms where IPv6 is not supported. The previous behavior was to throw an exception, but only in the editor. On the devices themselves, the address would be successfully parsed silently, which would lead to confusing socket errors down the line.
* The `SimulatorUtility.Context` structure has been made internal. It contained only implementation details, or values that appeared useful but were actually either misleading or broken.
* The `RelayMessageType` enum has been made internal. The only purpose of this type was to list the different messages of the Relay protocol, which is an implementation detail that should not be relevant to users.

### Fixes
* Fixed an issue where calling `ScheduleFlushSend` before the socket was bound would still result in socket system calls being made, resulting in errors being logged.
* No warning will be printed when attempting to send on a WebSocket connection that has been closed by the remote peer (would only happen if calling `ScheduleFlushSend`).
  • Loading branch information
Unity Technologies committed Mar 30, 2023
1 parent 6e1127d commit 5950d52
Show file tree
Hide file tree
Showing 43 changed files with 1,418 additions and 507 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Change log

## [2.0.0-pre.8] - 2023-03-30

### New features
* `MultiNetworkDriver` can then be used for client drivers too. The restriction on it accepting only listening drivers has been lifted, and it now offers a new `Connect` method to connect client drivers. This makes it easier to write networking code that can be shared between server and client.
* Added a new `ReliableUtility.SetMaximumResendTime` static method, allowing to modify the maximum resend time of the reliable pipeline at runtime (there's already a similar method for the minimum resend time). Increasing this value can improve bandwidth usage for poor connections (RTT greater than 200ms).
* Added the possibility of setting the minimum and maximum resend times of the reliable pipeline through `NetworkSettings` (with `WithReliableStageParameters`).

### Changes
* `NetworkEndpoint.TryParse` will now return false and log an error when attempting to parse an IPv6 address on platforms where IPv6 is not supported. The previous behavior was to throw an exception, but only in the editor. On the devices themselves, the address would be successfully parsed silently, which would lead to confusing socket errors down the line.
* The `SimulatorUtility.Context` structure has been made internal. It contained only implementation details, or values that appeared useful but were actually either misleading or broken.
* The `RelayMessageType` enum has been made internal. The only purpose of this type was to list the different messages of the Relay protocol, which is an implementation detail that should not be relevant to users.

### Fixes
* Fixed an issue where calling `ScheduleFlushSend` before the socket was bound would still result in socket system calls being made, resulting in errors being logged.
* No warning will be printed when attempting to send on a WebSocket connection that has been closed by the remote peer (would only happen if calling `ScheduleFlushSend`).

## [2.0.0-pre.7] - 2023-03-15

### New features
Expand Down
2 changes: 2 additions & 0 deletions Documentation~/client-server-simple.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ else if (cmd == NetworkEvent.Type.Disconnect)
}
```

**Note**: If you were to close the connection before popping the `Disconnect` event (say you're closing it in response to a `Data` event), make sure to pop all remaining events for that connection anyway. Otherwise an error will be printed on the next update about resetting the event queue while there were pending events.

## Putting it all together

To take this for a test run, you can add a new empty [GameObject](https://docs.unity3d.com/ScriptReference/GameObject.html) to our scene:
Expand Down
18 changes: 17 additions & 1 deletion Documentation~/cross-play.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,20 @@ writer.WriteInt(42);
multiDriver.EndSend(writer);
```

**Note**: Pipelines in all drivers added to a `MultiNetworkDriver` are expected to have symmetric functions. That is, if the first pipeline is a reliable one, then it must be so for _all_ drivers in the `MultiNetworkDriver`. This is why we create a "dummy" pipeline for the WebSocket driver in the example above. We still need _some_ pipeline to act as the reliable one, even if this pipeline does nothing.
**Note**: Pipelines in all drivers added to a `MultiNetworkDriver` are expected to have symmetric functions. That is, if the first pipeline is a reliable one, then it must be so for _all_ drivers in the `MultiNetworkDriver`. This is why we create a "dummy" pipeline for the WebSocket driver in the example above. We still need _some_ pipeline to act as the reliable one, even if this pipeline does nothing.

### Sharing code between server and client

You may have noticed that all examples above use `MultiNetworkDriver` in a server role. While this is indeed its main intended usage, using it only as a server can be problematic if you have code shared between your server and client builds. For example, if you had common networking code that used `NetworkDriver`, porting it to `MultiNetworkDriver` could be difficult since that code would then need to use a `MultiNetworkDriver` on the server, and a `NetworkDriver` on clients.

To address this problem, `MultiNetworkDriver` can also act as a container for client drivers. A non-listening driver can be added to a `MultiNetworkDriver` and connected to a server in the manner below:

```csharp
var clientDriver = NetworkDriver.Create();
var multiDriver = MultiNetworkDriver.Create();

var driverId = multiDriver.AddDriver(clientDriver);
var connection = multiDriver.Connect(driverId, serverEndpoint);
```

There is no real performance penalty from using a `MultiNetworkDriver` that contains a single `NetworkDriver`. So having both server and client builds rely on `MultiNetworkDriver` is a good way of writing code that can be shared between them. In a way, `MultiNetworkDriver` then serves the purpose of an hypothetical `INetworkDriver` interface for the functionality common between `NetworkDriver` and `MultiNetworkDriver` (the transport package does not actually provide such an interface because it would be impractical to use in Burst-compiled code).
19 changes: 19 additions & 0 deletions Documentation~/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ if (eventType == NetworkEvent.Type.Disconnect)

The obtained value is from the `Error.DisconnectReason` enum and indicates why the `Disconnect` event was generated.

## Why isn't the other end immediately notified of a disconnection?

If you call `NetworkDriver.Disconnect` on a connection and then immediately dispose of the driver, you might notice that the other end of the connection is not immediately notified of the disconnection. Instead of quickly popping a `Disconnect` event on the connection, it only does so after 30 seconds or so (the default disconnection timeout).

This is because calling `Disconnect` does not actually immediately send the disconnection request on the network (just like `EndSend` does not immediately send a message on the network). A driver update jobs needs to run for this to happen. So be sure to schedule and complete an update job before disposing of the driver if you just closed a connection:

```csharp
// Close the connection.
driver.Disconnect(connection);
// Schedule and complete an update job. This is required!
driver.ScheduleUpdate().Complete();
// Now it's okay to dispose of the driver.
driver.Dispose();
```

A good way of achieving the above is to always wait a frame before shutting down the `NetworkDriver`. It gives enough time to the driver to do all its cleanup work before being disposed of. This is the strategy implemented by Netcode for GameObjects, for example.

Note that it really is an update job that needs to run for the disconnection request to be sent. Calling `ScheduleFlushSend` is *not* sufficient, because disconnections require extra cleanup work that only occurs during update jobs.

## What's the largest message I can send?

By default, the size of messages is limited by the [MTU](https://en.wikipedia.org/wiki/Maximum_transmission_unit), which ensures messages are not larger than a single IP packet on most network configurations. Because different protocols and pipelines will have different overhead, the size of the maximum useful payload that can be written to a `DataStreamWriter` may vary. There are two ways to obtain this value:
Expand Down
5 changes: 2 additions & 3 deletions Runtime/BaselibNetworkInterface.deprecated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static ref NetworkSettings WithBaselibNetworkInterfaceParameters(
}
}

/// <summary>Obsolete. Set the receive/send queue capacities with <see cref="NetworkConfigParameter"/> instead.</summary>
[Obsolete("To set receiveQueueCapacity and sendQueueCapacity parameters use NetworkConfigParameter", false)]
public struct BaselibNetworkParameter : INetworkParameter
{
Expand All @@ -53,15 +54,13 @@ public bool Validate()
}
}

/// <summary>Obsolete. Use <see cref="UDPNetworkInterface"/> instead.</summary>
[Obsolete("BaselibNetworkInterface has been deprecated. Use UDPNetworkInterface instead (UnityUpgradable) -> UDPNetworkInterface")]
public struct BaselibNetworkInterface : INetworkInterface
{
public NetworkEndpoint LocalEndpoint
=> throw new System.NotImplementedException();

public bool IsCreated
=> throw new System.NotImplementedException();

public int Initialize(ref NetworkSettings settings, ref int packetPadding)
=> throw new System.NotImplementedException();

Expand Down
9 changes: 0 additions & 9 deletions Runtime/DebugLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,15 +299,6 @@ public static void ErrorFragmentationMaxPayloadTooLarge(int payloadCapacity, int
#endif
}

public static void ErrorReliableWindowSize(int windowSize)
{
#if USE_UNITY_LOGGING
Unity.Logging.Log.Error("WindowSize value ({WindowSize}) must be greater than 0 and smaller or equal to 32", windowSize);
#else
UnityEngine.Debug.LogError($"WindowSize value ({windowSize}) must be greater than 0 and smaller or equal to 32");
#endif
}

public static void SimulatorIncomingTooLarge(int incomingSize, int maxPacketSize)
{
#if USE_UNITY_LOGGING
Expand Down
67 changes: 51 additions & 16 deletions Runtime/INetworkInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

namespace Unity.Networking.Transport
{
/// <summary>Obsolete. Part of the old <c>INetworkInterface</c> API.</summary>
[Obsolete("Use ReceiveJobArguments.ReceiveQueue instead", true)]
public struct NetworkPacketReceiver
{
public IntPtr AllocateMemory(ref int dataLen)
=> throw new NotImplementedException();

/// <summary>Obsolete. Part of the old <c>INetworkInterface</c> API.</summary>
[Flags]
public enum AppendPacketMode
{
Expand Down Expand Up @@ -47,42 +49,75 @@ internal struct NetworkInterfaceSendHandle
public SendHandleFlags flags;
}

/// <summary>
/// <para>
/// Network interfaces are the lowest level of the Unity Transport library. They are responsible
/// for sending and receiving packets directly to/from the network. Conceptually, they act like
/// sockets. Users can provide their own network interfaces by implementing this interface and
/// passing a new instance of it to <see cref="NetworkDriver.Create"/>.
/// </para>
/// <para>
/// Note that network interfaces are expected to be unmanaged types compatible with Burst.
/// However, it is possible to write them using managed types and code. Simply wrap them with
/// <see cref="ManagedNetworkInterfaceExtensions.WrapToUnmanaged"/> before passing them to
/// <see cref="NetworkDriver.Create"/>. This comes at a small performance cost, but allows
/// writing network interfaces that interact with managed C# libraries.
/// </para>
/// </summary>
public interface INetworkInterface : IDisposable
{
/// <summary>
/// Gets the local endpoint that the interface will use to communicate on the network.
/// This call only makes sense after <see cref="Bind"/> has already been called, and
/// represents the endpoint the interface is actually bound to. This property serves the
/// same purpose as <c>getsockname</c> in the BSD socket world.
/// </summary>
/// <value>Local endpoint.</value>
NetworkEndpoint LocalEndpoint { get; }

/// <summary>Initialize the network interface with the given settings.</summary>
/// <param name="settings">Configuration settings provided to the driver.</param>
/// <param name="packetPadding">
/// Return value parameter for how much padding the interface adds to packets. Note that
/// this parameter is only concerned that padding that would be added directly in the
/// packets stored in the send and receive queues, not to padding that would be added by
/// lower levels of the network stack (e.g. IP headers).
/// </param>
/// <returns>0 on success, a negative number on error.</returns>
int Initialize(ref NetworkSettings settings, ref int packetPadding);

/// <summary>
/// Schedule a ReceiveJob. This is used to read data from your supported medium and pass it to the AppendData function
/// supplied by <see cref="NetworkDriver"/>
/// Schedule a receive job. This job's responsibility is to read data from the network and
/// enqueue it in <see cref="ReceiveJobArguments.ReceiveQueue"/>.
/// </summary>
/// <param name="arguments">A set of <see cref="ReceiveJobArguments"/> that can be used in the receive jobs.</param>
/// <param name="dep">A <see cref="JobHandle"/> to any dependency we might have.</param>
/// <returns>A <see cref="JobHandle"/> to our newly created ScheduleReceive Job.</returns>
/// <param name="arguments">Arguments to be passed to the receive job.</param>
/// <param name="dep">Handle to any dependency the receive job has (use default if none).</param>
/// <returns>Handle to the newly-schedule job.</returns>
JobHandle ScheduleReceive(ref ReceiveJobArguments arguments, JobHandle dep);

/// <summary>
/// Schedule a SendJob. This is used to flush send queues to your supported medium
/// Schedule a send job. This job's responsibility is to flush any data stored in
/// <see cref="SendJobArguments.SendQueue"/> to the network.
/// </summary>
/// <param name="arguments">A set of <see cref="SendJobArguments"/> that can be used in the send jobs.</param>
/// <param name="dep">A <see cref="JobHandle"/> to any dependency we might have.</param>
/// <returns>A <see cref="JobHandle"/> to our newly created ScheduleSend Job.</returns>
/// <param name="arguments">Arguments to be passed to the send job.</param>
/// <param name="dep">Handle to any dependency the send job has (use default if none).</param>
/// <returns>Handle to the newly-schedule job.</returns>
JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dep);

/// <summary>
/// Binds the medium to a specific endpoint.
/// Binds the network interface to an endpoint. This is meant to act the same way as the
/// <c>bind</c> call in the BSD socket world. One way to see it is that it "attaches" the
/// network interface to a specific local address on the machine.
/// </summary>
/// <param name="endpoint">
/// A valid <see cref="NetworkEndpoint"/>.
/// </param>
/// <returns>0 on Success</returns>
/// <param name="endpoint">Endpoint to bind to.</param>
/// <returns>0 on success, a negative number on error.</returns>
int Bind(NetworkEndpoint endpoint);

/// <summary>
/// Start listening for incoming connections. This is normally a no-op for real UDP sockets.
/// Start listening for incoming connections. Unlike <see cref="Bind"/> which will always be
/// called on clients and servers, this is only meant to be called on servers.
/// </summary>
/// <returns>0 on Success</returns>
/// <returns>0 on success, a negative number on error.</returns>
int Listen();
}
}
33 changes: 12 additions & 21 deletions Runtime/INetworkLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,40 +35,31 @@ internal interface INetworkLayer : IDisposable
JobHandle ScheduleSend(ref SendJobArguments arguments, JobHandle dependency);
}

/// <summary>
/// Arguments required by the ScheduleSend jobs.
/// </summary>
/// <summary>Arguments used by send jobs.</summary>
public struct SendJobArguments
{
/// <summary>
/// A queue containing all the packets to be sent.
/// </summary>
/// <summary>A queue containing all the packets to be sent.</summary>
/// <value>Queue of packets.</value>
public PacketsQueue SendQueue;

/// <summary>
/// The current update time value.
/// </summary>
/// <summary>The current update time value.</summary>
/// <value>Timestamp in milliseconds.</value>
public long Time;
}

/// <summary>
/// Arguments required by the ScheduleReceive jobs.
/// </summary>
/// <summary>Arguments used by receive jobs.</summary>
public struct ReceiveJobArguments
{
/// <summary>
/// A queue containing all the packets to be received.
/// </summary>
/// <summary>A queue containing all the packets to be received.</summary>
/// <value>Queue of packets.</value>
public PacketsQueue ReceiveQueue;

/// <summary>
/// The result of the receive operation.
/// </summary>
/// <summary>The result of the receive operation.</summary>
/// <value>Result of the receive operation.</value>
public OperationResult ReceiveResult;

/// <summary>
/// The current update time value.
/// </summary>
/// <summary>The current update time value.</summary>
/// <value>Timestamp in milliseconds.</value>
public long Time;

internal NetworkDriverReceiver DriverReceiver;
Expand Down
Loading

0 comments on commit 5950d52

Please sign in to comment.