diff --git a/static/img/transport/console-2.png b/static/img/transport/console-2.png new file mode 100644 index 000000000..407d5759b Binary files /dev/null and b/static/img/transport/console-2.png differ diff --git a/static/img/transport/fragmentation-2.png b/static/img/transport/fragmentation-2.png new file mode 100644 index 000000000..04422bf5c Binary files /dev/null and b/static/img/transport/fragmentation-2.png differ diff --git a/static/img/transport/gameobject-2.png b/static/img/transport/gameobject-2.png new file mode 100644 index 000000000..032b4d70b Binary files /dev/null and b/static/img/transport/gameobject-2.png differ diff --git a/static/img/transport/inspector-2.png b/static/img/transport/inspector-2.png new file mode 100644 index 000000000..39375d6eb Binary files /dev/null and b/static/img/transport/inspector-2.png differ diff --git a/static/img/transport/layercake-2.png b/static/img/transport/layercake-2.png new file mode 100644 index 000000000..d969dace9 Binary files /dev/null and b/static/img/transport/layercake-2.png differ diff --git a/static/img/transport/pipeline-stages-2.png b/static/img/transport/pipeline-stages-2.png new file mode 100644 index 000000000..892e5ea54 Binary files /dev/null and b/static/img/transport/pipeline-stages-2.png differ diff --git a/transport_versioned_docs/version-2.0.0/about.md b/transport_versioned_docs/version-2.0.0/about.md index f62448d7b..1f1af52df 100644 --- a/transport_versioned_docs/version-2.0.0/about.md +++ b/transport_versioned_docs/version-2.0.0/about.md @@ -2,35 +2,19 @@ id: about title: About Unity Transport --- -:::note -Need an update before releasing UTP 2.0.0 -::: -Unity Transport provides the `com.unity.transport` package, used to add multiplayer and network features to your project. +The Unity Transport package (`com.unity.transport`) is a low-level networking library for multiplayer game development. -:::note -This package should not be confused with Unity Netcode's `NetworkTransport` abstraction. Please see Netcode's [Transports](/netcode/current/advanced-topics/transports) section for more information. -::: +It’s the underlying protocol of both [Netcode for GameObjects](../../docs/about.md) and [Netcode for Entities](https://docs.unity3d.com/Packages/com.unity.netcode@latest), but you can also use it with a custom solution. -:::unity Content Licenses -All Transport code and documentation is covered by Unity Companion License. See [Licenses](/reference/license) for more information. -::: +Unity Transport seamlessly supports all platforms the Unity Engine supports thanks to a connection-based abstraction layer (built-in network driver) provided over UDP and WebSockets. -## Overview +You can set up both UDP and WebSockets with or without encryption. The following illustration shows encrypted connections with a closed padlock and unencrypted connections with an open padlock. -![Transport Overview](/img/transport/layercake.png) +![Block diagram](../../static/img/transport/layercake-2.png) -## Installing Transport +You can also use [pipelines](pipelines-usage.md) to leverage additional functionality, such as reliability, packet ordering, and packet fragmentation. -See the [installation guide](install.md) to install the `com.unity.transport` package. +## Get started with Unity Transport -## Using Transport - -To learn how to use the `com.unity.transport` package in your own project, review the client and server workflows, additional information, and sample code available in this guide. - -## Requirements - -This version of `com.unity.transport` is compatible with the following Unity versions and platforms: - -* 2020.1.2 and later. -* All platforms supported by Unity are supported, except WebGL. +See [Getting started](getting-started.md). diff --git a/transport_versioned_docs/version-2.0.0/custom-pipeline.md b/transport_versioned_docs/version-2.0.0/custom-pipeline.md index bf29ac08b..41a6499eb 100644 --- a/transport_versioned_docs/version-2.0.0/custom-pipeline.md +++ b/transport_versioned_docs/version-2.0.0/custom-pipeline.md @@ -4,4 +4,3 @@ title: Custom Network interface --- COMING SOON. - \ No newline at end of file diff --git a/transport_versioned_docs/version-2.0.0/getting-started.md b/transport_versioned_docs/version-2.0.0/getting-started.md index 78a0eb2ac..a523e28af 100644 --- a/transport_versioned_docs/version-2.0.0/getting-started.md +++ b/transport_versioned_docs/version-2.0.0/getting-started.md @@ -3,4 +3,19 @@ id: getting-started title: Getting Started --- -#PlaceHolder +See the [Installation](install.md) guide if you’re new to Unity Transport. If you’re upgrading from a earlier version of Unity Transport, see Migrating from 1.X. + +Next, check out [Create a simple client and server](workflow-client-server-udp.md) and the Unity Transport [sample projects](using-sample.md). + +## Requirements + +- Install Unity Editor version 2022.2 or later. +- Install the `com.unity.transport` package. + +:::note +WebGL only supports Unity Transport WebSocket connections in client mode. +::: + +:::warning +Unity Transport (the `com.unity.transport` package) is separate from the NetworkTransport abstraction in Netcode for GameObjects. +::: diff --git a/transport_versioned_docs/version-2.0.0/install.md b/transport_versioned_docs/version-2.0.0/install.md index 07927a143..2b530d081 100644 --- a/transport_versioned_docs/version-2.0.0/install.md +++ b/transport_versioned_docs/version-2.0.0/install.md @@ -4,22 +4,26 @@ title: Install Unity Transport description: Install Unity Transport, the com.unity.transport package, using the Package Manager. --- -:::note -Need an update before releasing UTP 2.0.0 -::: - Follow these instructions to install Unity Transport. ## Prerequisites -You need Unity Editor version 2020.1.2f1 or later. +Unity Transport 2.0 requires Unity Editor version 2022.2 or later. + +:::note +WebGL only supports Unity Transport WebSocket connections in client mode. +::: + +:::warning +Unity Transport (the com.unity.transport package) is separate from the NetworkTransport abstraction in Netcode for GameObjects. +::: ## Install Transport -1. Open the **Unity Hub**. -1. Create a **New Project** or open an existing Project you want to include Transport. -2. Open the **Unity Package Manager** by navigating to **Window** > **Package Manager** along the top bar. -3. Click ![Add](/img/add.png) in the status bar. -4. Select **Add package by name** -5. In the **Name**, enter `com.unity.transport`. It may take a moment to load. -6. Under **Packages** in the **Package Manager**, you should now see **Unity Transport** with it's current version number. \ No newline at end of file +You can install the Unity Transport package using the Package Manager in the Unity Editor: + +1. From the Unity Editor, select **Window** > **Package Manager**. +2. Select **Add (+)** > **Add package by name**. +3. In the **Name** field, enter com.unity.transport. + +After the import completes, you should see Unity Transport under **Packages** in the **Package Manager**. diff --git a/transport_versioned_docs/version-2.0.0/integrate-with-unity-logging.md b/transport_versioned_docs/version-2.0.0/integrate-with-unity-logging.md new file mode 100644 index 000000000..837c22178 --- /dev/null +++ b/transport_versioned_docs/version-2.0.0/integrate-with-unity-logging.md @@ -0,0 +1,8 @@ +--- +id: integrate-logging +title: Integrate with Unity Logging +--- + +Unity Transport integrates well with the Unity Logging package (`com.unity.logging`), an efficient alternative to the standard Unity logger. Normally, log messages go through `UnityEngine.Debug.Log`. However, if you include both packages in a project, Unity Transport automatically uses `Unity.Loggin`g with the default logger settings. + +Check the [`Unity.Logging` documentation site](https://docs.unity3d.com/Packages/com.unity.logging@latest) for more information on how to adjust specific log settings. diff --git a/transport_versioned_docs/version-2.0.0/migration.md b/transport_versioned_docs/version-2.0.0/migration.md index 41332ccd6..2b5cf89e8 100644 --- a/transport_versioned_docs/version-2.0.0/migration.md +++ b/transport_versioned_docs/version-2.0.0/migration.md @@ -1,65 +1,69 @@ --- id: migration -title: Migration from 1.X +title: Migrate from 1.X description: How to deal with breaking changes introduced in version 2.0 of Unity Transport. --- This section describes the breaking changes introduced in version 2.0 of the Unity Transport Package (UTP). It also explains how to update projects written for version 1.X. -**Note**: In most use cases, there’s no need to perform any additional steps to migrate from 1.X to 2.0. The core APIs like `NetworkDriver` remain the same, and the most significant changes are limited to specialized scenarios, such as [custom network interfaces](custom-network-interface.md). +:::note +In most use cases, there’s no need to perform any additional steps to migrate from 1.X to 2.0. The core APIs (like `NetworkDriver`) remain the same, and the most significant changes are limited to specialized scenarios, such as [custom network interfaces](#custom-network-interfaces). +::: ## Editor version support -UTP 1.X supports Unity Editor 2020.3 and up, but 2.0 only supports 2022.2 and up to keep the `Collections` package dependency up to date. Unity Editor 2022.2 brings many changes to the core engine runtime, allowing more code to be Burst-compiled. UTP benefits from this through increased performance. +UTP 1.X supports Unity Editor 2020.3 and up, but 2.0 only supports 2022.2 and up to keep the Collections package dependency up to date. Unity Editor 2022.2 brings changes to the core Unity Engine runtime, allowing more code to be Burst-compiled. UTP benefits from this through increased performance. -**Note**: UTP 1.X remains fully supported on LTS versions 2020.3 and 2021.3, and Unity will continue to release bug fixes and improvements. However, some features (like WebSocket support) will only be available in UTP 2.0 and up. +:::note +UTP 1.X remains fully supported on LTS versions 2020.3 and 2021.3, and Unity will continue to release bug fixes and improvements. However, some features (like [WebSocket](workflow-client-server-ws.md) support) will only be available in UTP 2.0 and up. +::: -## `DataStreamReader`/`DataStreamWriter` moved to `Collections` +## DataStreamReader/DataStreamWriter moved to Collections -UTP 2.0 moves the `DataStreamReader` and `DataStreamWriter` APIs to the `Collections` package to make them more widely available outside UTP. Consequently, updating to UTP 2.0 might require you to add a `Unity.Collections` directive at the top of files using the `DataStreamReader` and `DataStreamWriter` APIs. +UTP 2.0 moves the `DataStreamReader` and `DataStreamWriter` APIs to the Collections package to make them more widely available outside UTP. As a result, updating to UTP 2.0 might require you to add a using `Unity.Collections` directive at the top of files using the `DataStreamReader` and `DataStreamWriter` APIs. -The APIs are mostly unchanged, except for raw pointers. `Unity.Collections.LowLevel.Unsafe` namespace provides the raw pointer methods with `Unsafe` appended to their names. For example, the method `WriteBytes(byte*, int)` is now `WriteBytesUnsafe(byte*, int)`. +The APIs are mostly unchanged, except for raw pointers. `Unity.Collections.LowLevel.Unsafe` namespace now provides the raw pointer methods with Unsafe appended to their names. For example, the method `WriteBytes(byte*, int)` is now `WriteBytesUnsafe(byte*, int)`. ## Protocol incompatibility -The custom communication protocol UTP uses to implement connections over UDP has changed in a backward-incompatible manner, which means clients running UTP 2.0 or later can't connect to servers running 1.X, and vice versa. If you attempt to establish a connection between UTP 2.0 and UTP 1.X, it returns a connection failure after the usual timeout. +The custom communication protocol UTP uses to implement connections over UDP has changed in a backward-incompatible manner, which means clients running UTP 2.0 or later can't connect to servers running 1.X, and vice versa. If you try to establish a connection between UTP 2.0 and UTP 1.X, UTP returns a connection failure after the usual timeout. -Due to the incompatibility between UTP 2.0 and UTP 1.X, you must either ensure that you either update clients and servers simultaneously or disallow older clients from connecting to updated servers. Alternatively, you can provide different endpoints for UTP 1.X and 2.0 servers to smooth the transition while older clients are updated. +Due to the incompatibility between UTP 2.0 and UTP 1.X, you must either ensure that you update clients and servers simultaneously or disallow older clients from connecting to updated servers. Alternatively, you can create different endpoints for UTP 1.X and 2.0 servers to smooth the transition while you update older clients. -These breaking changes introduced in UTP 2.0 improve bandwidth efficiency, simplify the protocol, and lay the foundations for better forward compatibility. +These breaking changes improve bandwidth efficiency, simplify the protocol, and lay the foundations for better forward compatibility. ## Custom network interfaces The updates in UTP 2.0 heavily modify the `INetworkInterface` API used to implement custom network interfaces (the low-level layer to send and receive packets) to simplify it. -UTP 2.0 introduces the following breaking changes to custom network interfaces: +UTP 2.0 introduces the following breaking changes to custom network interfaces: -* UTP 2.0 no longer has the concept of `NetworkInterfaceEndPoint`; the more general `NetworkEndpoint` replaces `NetworkInterfaceEndPoint`. As a result, you do not need to implement conversion logic between `NetworkEndpoint` and `NetworkInterfaceEndPoint` anymore, so `INetworkInterface` omits `CreateInterfaceEndPoint` and `GetGenericEndPoint`. -* You no longer need to provide a `NetworkSendInterface` through `CreateSendInterface`. `ScheduleSend` handles `Send` operations, which get passed a `PacketsQueue` containing the packets to send. -* The `ScheduleReceive` method doesn't use the (now obsolete) `NetworkPacketReceiver` to propagate received packets to the rest of UTP. Instead, implementations of `ScheduleReceive` should fill the `PacketsQueue` passed in with the received packets. -* Implementations of `INetworkInterface` must now be fully compatible with Burst. If an implementation is incompatible with Burst, you can wrap it into a compatible implementation with the new `WrapToUnmanaged` extension method. -* You must now create `NetworkDriver`s using a custom network interface using the static `NetworkDriver.Create` method. For example, `NetworkDriver.Create(new MyCustomInterface())` creates a `NetworkDriver `named `MyCustomInterface()`. Directly constructing a `NetworkDriver` with `new` is deprecated. -* `INetworkInterface.Initialize` now takes another parameter: a reference to the packet padding, and you can increase this value to reserve space for headers. +- UTP 2.0 no longer has the idea of `NetworkInterfaceEndPoint`; the more general `NetworkEndpoint` replaces `NetworkInterfaceEndPoint`. As a result, you don't need to implement conversion logic between `NetworkEndpoint` and `NetworkInterfaceEndPoint` anymore, so INetworkInterface omits `CreateInterfaceEndPoint` and `GetGenericEndPoint`. +- You no longer need to create a `NetworkSendInterface` through `CreateSendInterface`. `ScheduleSend` handles send operations, which get passed a `PacketsQueue` containing the packets to send. +- The `ScheduleReceive` method doesn't use the (now obsolete) `NetworkPacketReceiver` to propagate received packets to the rest of UTP. Instead, implementations of `ScheduleReceive` should fill the `PacketsQueue` passed in with the received packets. +- Implementations of `INetworkInterface` must now be fully compatible with Burst. If an implementation is incompatible with Burst, you can wrap it into a compatible implementation with the new `WrapToUnmanaged` extension method. +- You must now create NetworkDrivers using a custom network interface using the static `NetworkDriver.Create` method. For example, `NetworkDriver.Create(new MyCustomInterface())` creates a `NetworkDrivernamed MyCustomInterface()`. Directly constructing a NetworkDriver with new is deprecated. +- `INetworkInterface.Initialize` now takes another parameter: a reference to the packet padding, and you can increase this value to reserve space for headers. -These breaking changes simplify and increase the flexibility of the interface. See [Custom network interfaces](custom-network-interface.md) for more details on creating custom network interfaces. +These breaking changes simplify and increase the flexibility of the interface, making it easier to create custom network interfaces. ## Pipeline stages -UTP 2.0 does not introduce changes to how you write custom pipeline stages. However, the mechanisms you should use to register and get the identifier of a pipeline stage have changed: +UTP 2.0 don't introduce changes to how you write custom pipeline stages. However, the mechanisms you should use to register and get the identifier of a pipeline stage have changed: -* Instead of registering a custom pipeline stage with `NetworkPipelineStageCollection.RegisterPipelineStage`, use `NetworkDriver.RegisterPipelineStage`. Note: You must perform this change on every instance of `NetworkDriver` that uses the custom pipeline stage. -* Instead of retrieving the ID of a pipeline stage with `NetworkPipelineStageCollection.GetStageId`, use the static `NetworkPipelineStageId.Get` method. +- Instead of registering a custom pipeline stage with `NetworkPipelineStageCollection.RegisterPipelineStage`, use `NetworkDriver.RegisterPipelineStage`. You must do this on every instance of `NetworkDriver` that uses the custom pipeline stage. +- Instead of retrieving the ID of a pipeline stage with `NetworkPipelineStageCollection.GetStageId`, use the static `NetworkPipelineStageId.Get` method. -These breaking changes remove Burst-incompatible APIs, allowing you to use more of UTP with Burst-compiled code. +These breaking changes remove Burst-incompatible APIs, allowing you to use more of UTP with Burst-compiled code. -For more information, see [Creating a custom pipeline stage](custom-pipeline.md) and [Pipeline usage](pipelines-usage.md). +For more information, see the [section on using pipelines](https://docs.unity3d.com/Packages/com.unity.transport@2.0/manual/pipelines-usage.html). ## Other breaking changes -In addition to the changes around data streams, custom network interfaces, and pipeline stages, UTP 2.0 also introduces the following breaking changes: +UTP 2.0 also introduces the following breaking changes: -* You must now complete a `NetworkDriver.ScheduleUpdate` job when notifying the remote peer of the disconnection after calling `NetworkDriver.Disconnect`. In 1.X, `NetworkDriver.ScheduleFlushSend` was sufficient to notify a remote peer of a disconnection, but this is not the case with UTP 2.0. This change supports new protocols, such as WebSockets, where disconnecting might involve more work than simply sending a message. -* Using `SimulatorPipelineStageInSend` is now deprecated. Instead, use `SimulatorPipelineStage` and configure the new `ApplyMode` parameter to the direction (for example, `send`, `receive`, or both). +* You must now complete a `NetworkDriver.ScheduleUpdate` job when notifying the remote peer of a disconnection after calling `NetworkDriver.Disconnect`. In 1.X, `NetworkDriver.ScheduleFlushSend` was enough to tell a remote peer of a disconnection, but this isn't the case with UTP 2.0. This change supports new protocols, such as WebSockets, where disconnecting might involve more work than simply sending a message. +* Using `SimulatorPipelineStageInSend` is now deprecated. Instead, use SimulatorPipelineStage and configure the new `ApplyMode` parameter to the direction desired (send, receive, or both). * `NetworkSettings.WithBaselibNetworkInterfaceParameters` is now deprecated. You can no longer configure the maximum payload size; UTP handles the payload size automatically. However, you can configure the receive and send queue sizes with `NetworkSettings.WithNetworkConfigParameters`. * UTP 2.0 removes `NetworkSettings.WithDataStreamParameters` and `NetworkSettings.WithPipelineParameters`. You no longer need to manually configure either of these parameters, so the methods are unnecessary. You can safely delete calls to these methods. -* You no longer need to configure `Read` and handshake timeouts through `NetworkSettings.WithSecureClientParameters` and `NetworkSettings.WithSecureServerParameters`. Instead, UTP derives the values automatically from other configuration parameters. You can safely remove these settings from these calls. +* You no longer need to configure read and handshake timeouts through `NetworkSettings.WithSecureClientParameters` and `NetworkSettings.WithSecureServerParameters`. Instead, UTP derives the values automatically from other configuration parameters. You can safely remove these settings from these calls. diff --git a/transport_versioned_docs/version-2.0.0/network-settings.md b/transport_versioned_docs/version-2.0.0/network-settings.md index 11254b139..911742b42 100644 --- a/transport_versioned_docs/version-2.0.0/network-settings.md +++ b/transport_versioned_docs/version-2.0.0/network-settings.md @@ -1,24 +1,29 @@ --- id: network-settings -title: network settings +title: Network settings --- The configuration of the `NetworkDriver` is defined using the `NetworkSettings` API. It might be required to change some of the default values of the parameters, that can be done as in the following example. -```markdown title="NetworkSettings example" + +```csharp title="NetworkSettings example" var settings = new NetworkSettings(); settings.WithNetworkConfigParameters(disconnectTimeoutMS: 1000); var driver = NetworkDriver.Create(settings); ``` + ## Extending the NetworkSettings -When extending Unity Transport it is often useful to add custom parameters or settings that users can set to define the configuration of the `NetworkDriver`. -That can be done by extending the `NetworkSettings` API so the custom Network Interfaces or Pipelines can capture the parameter values inside the Initialization method as detailed in the following sections. + +When extending Unity Transport it's often useful to add custom parameters or settings that users can set to define the configuration of the `NetworkDriver`. +S +That can be done by extending the `NetworkSettings` API so the custom Network Interfaces or Pipelines can capture the parameter values inside the `Initialization` method as detailed in the following sections. ### INetworkParameter + By implementing the `INetworkParameter` interface, any unmanaged struct can be identified as a network parameter that can be assigned to the `NetworkDriver` settings. The `NetworkSettings` will automatically call the `Validate()` method and will throw an exception if `false` is returned. -```markdown title="INetworkParameter example" +```csharp title="INetworkParameter example" public struct MyCustomParameter : INetworkParameter { public int MyCustomInt; @@ -37,12 +42,13 @@ public struct MyCustomParameter : INetworkParameter } ``` -### NetworkSettings extension methods +### `NetworkSettings` extension methods + Every new custom `INetworkParameter` requires of at least one extension method to the `NetworkSettings` API. These extension methods must receive and return a `ref NetworkSettings` to ensure the proper functioning of the fluent interface. Those constraints are checked by the Roslyn Analyzers provided in the Unity Transport package. -```markdown title="NetworkSettings extension methods example" +```csharp title="NetworkSettings extension methods example" public static class MyCustomParameterExtensions { private const int k_MyCustomIntDefault = 1024; @@ -76,4 +82,4 @@ public static class MyCustomParameterExtensions return parameter; } } -``` \ No newline at end of file +``` diff --git a/transport_versioned_docs/version-2.0.0/pipelines-usage.md b/transport_versioned_docs/version-2.0.0/pipelines-usage.md index ac33468a9..d26285357 100644 --- a/transport_versioned_docs/version-2.0.0/pipelines-usage.md +++ b/transport_versioned_docs/version-2.0.0/pipelines-usage.md @@ -3,150 +3,136 @@ id: pipelines title: Use pipelines --- -Pipelines are a feature which offers layers of functionality on top of the default socket implementation behaviour. In the case of the UDP socket this makes it possible to have additional functionality on top of the standard unreliable datagram, such as Quality of Service features like sequencing, reliability, fragmentation and so on. +Pipelines are a core functionality of Unity Transport that allows selectively adding layers of functionality on top of the standard unreliable datagrams that UTP provides by default. -## How it works +You can use pipelines to add sequencing, reliability, and fragmentation features. -The way it works is that you can add any number of pipelines (each with any number of stages) to your transport driver. The pipelines are chained together in the order defined by the user. Each pipeline input will the output of the previous pipeline. In other terms, when you send a packet it will go to the first stage, then the second one and so on until it's sent on the wire. On the receiving side the stages are then processed in reverse order, so the packet is correctly "unpacked" by the stages. +Pipelines are a sequence of one or more stages. When you **send** a message through a pipeline, Unity Transport runs through the stages in order, piping the output of the first stage into the second stage. As a result, if the first stage adds a header to the packet, the second stage processes the entire packet, including the header added by the first stage. When you **receive** a message, it goes through the same chain of stages in reverse order. -For example the first stage might compress a packet and a second stage could add a sequence number (just the packets header). When receiving the packet is first passed through the sequence stage and then decompressed. The sequence stage could drop the packet if it's out of order in which case it leaves the pipeline and doesn't continue to the decompression. +![Pipeline stages](../../static/img/transport/pipeline-stages-2.png) -:::note -* The order in which the pipelines are defined is extremely important. For instance, a pipeline created with ```CreatePipeline(typeof(ReliableSequencedPipelineStage), typeof(SimulatorPipelineStage))``` is different from a pipeline created with the same stages but in the reverse order. - In that example, putting the simulator pipeline stage first would result in packets being dropped/delayed before there can be any reliability added to them, which is likely not the desired outcome. -* It is highly recommended to use the same pipelines, in the same order for both the client and the server. -::: - -![PipelineStagesDiagram](/img/transport/pipeline-stages.png) +`FragmentationPipelineStage` allows breaking large messages into smaller pieces, and `ReliableSequencedPipelineStage` allows sending messages with guaranteed order and delivery. The following example shows how to create a pipeline with both functionalities: -The pipeline stages are gathered together in a collection. This is the interface between the pipeline processor in the driver to the pipeline stages you might be using. Here the pipeline stages are initialized and so on. There is a default collection provided in the driver which has all the built in pipeline stages already configured. It's possible to just use that and use a custom collection if you have your own pipeline stage you need to add to the collection. +```csharp +// In initialization code, before any connections are made. +var myPipeline = driver.CreatePipeline( + typeof(FragmentationPipelineStage), typeof(ReliableSequencedPipelineStage)); +``` +This creates a pipeline where Unity Transport first fragments messages into smaller pieces (that each fit in a packet), then delivers the individual packets reliably and in the correct order. +![Block diagram](../../static/img/transport/fragmentation-2.png) -## Example usage +This illustration shows the process of fragmentation and delivering packets in order. The small orange pieces on the fragments represent sequence numbers and other information added by the reliable stage. -The example below shows how the driver can create a new pipeline with 2 pipeline stages present (sequencer and simulator). The driver is created with the default pipeline collection and the pipeline parameters can be passed to the collection there. Multiple pipeline parameters can be passed in this way and the collection itself takes care of assigning them to the right pipeline stage. +:::note +The order of the stages in a pipeline is important. If you reverse the order, UTP only adds the reliability information to the larger unfragmented message. Doing so is less bandwidth-efficient because losing any single fragment would result in needing to resend the entire unfragmented message. By ordering the reliable stage after fragmentation, you only need to resend a single fragment if you lose a fragment. +::: -When sending packets the pipeline can then be specified as a parameter, so the packet is passed through it, it's then automatically processed the right way on the receiving end. It is therefore important both the client and server set up their pipelines in exactly the same way. One exception is with pipeline stages which do not manipulate the packet payload or header, these do not need to be symmetric. For example, the simulator stage here is only keeping packets on hold for a certain time and then releases them unmodified or drops them altogether, it can therefore be set up to only run on the client. +You can pass the pipeline to `BeginSend` to send a message on this new pipeline: ```csharp -using Unity.Collections; -using Unity.Networking.Transport; -using Unity.Networking.Transport.Utilities; +driver.BeginSend(myPipeline, connection, out var writer); +``` + +You can use the last argument of `PopEvent`/`PopEventForConnection` to know which pipeline you received a message on: -public class Client +```csharp +var eventType = driver.PopEvent(out _, out _, out var receivePipeline); +if (eventType == NetworkEvent.Type.Data) { - NetworkDriver m_DriverHandle; - NetworkPipeline m_Pipeline; - - const int k_PacketSize = 256; - - // Connection establishment omitted - public NetworkConnection m_ConnectionToServer; - - public void Configure() - { - // Driver can be used as normal - m_DriverHandle = NetworkDriver.Create(new SimulatorUtility.Parameters {MaxPacketSize = k_PacketSize, MaxPacketCount = 30, PacketDelayMs = 100}); - // Driver now knows about this pipeline and can explicitly be asked to send packets through it (by default it sends directly) - m_Pipeline = m_DriverHandle.CreatePipeline(typeof(UnreliableSequencedPipelineStage), typeof(SimulatorPipelineStage)); - } - - public void SendMessage(NativeArray someData) - { - // Send using the pipeline created in Configure() - m_DriverHandle.BeginSend(m_Pipeline, m_ConnectionToServer, out var writer); - writer.WriteBytes(someData); - m_DriverHandle.EndSend(writer); - } + // Data message was received on the receivePipeline pipeline. } ``` -## Simulator Pipeline +:::note +You should always configure pipelines the same way on the server and client. The CreatePipeline calls (and their order) must match on both ends of a connection. Unity Transport doesn't protect you against mismatched pipelines between servers and clients. +::: -The simulator pipeline stage could be added on either the client or server to simulate bad network conditions. It is best to add it as the last stage in the pipeline, then it will either drop the packet or add a delay right before it would go on the wire. +## The fragmentation pipeline stage -### Use the simulator +By default, Unity Transport can only send messages that fit into the [MTU](https://en.wikipedia.org/wiki/Maximum_transmission_unit) (roughly 1400 bytes). You must split larger messages into smaller pieces. This process is called message fragmentation. -No further configuration is needed after configuring the pipeline. It can be set up when the driver is created, as follows: +Pipelines configured with a `FragmentationPipelineStage` automatically fragment messages for you. You can configure the maximum pre-fragmentation payload size when creating a `NetworkDriver`: ```csharp -m_DriverHandle = NetworkDriver.Create(new SimulatorUtility.Parameters {MaxPacketSize = NetworkParameterConstants.MTU, MaxPacketCount = 30, PacketDelayMs = 25, PacketDropPercentage = 10}); -m_Pipeline = m_DriverHandle.CreatePipeline(typeof(SimulatorPipelineStage)); +var settings = new NetworkSettings(); +settings.WithFragmentationStageParameters(payloadCapacity: 10000); + +var driver = NetworkDriver.Create(settings); +var fragmentedPipeline = driver.CreatePipeline(typeof(FragmentationPipelineStage)); ``` -This would create a simulator pipeline stage which can delay up to 30 packets of a size up to the MTU size constant. Each packets gets a 25 ms delay applied and 10% of packets received will be dropped. SimulatorPipelineStage processes packets on the Receive stage of the pipeline. +The maximum value is about ~20 megabytes. However, the fragmentation pipeline stage is only optimized for payloads of a few kilobytes (the default value is 4096 bytes). We don't recommend sending messages much larger than a few kilobytes unless it’s a one-time transmission at initialization. -### Debug information +Furthermore, if you use this pipeline stage with the `ReliableSequencedPipelineStage`, the maximum value is even lower at around 88 kilobytes. -To get information about internal state in the simulator, check the `SimulatorUtility.Context` structure, stored in the pipeline stage shared buffer. This tracks how many packets have been set, `PacketCount`, and how many of those were dropped, `PacketDropCount`. `ReadyPackets` and `WaitingPackets` shows what packets are now ready to be sent (delay time expired) and how many are stored by the simulator. `StatsTime` and `NextPacketTime` show the last time the simulator ran and when the next packet is due to be released. +:::note +Most pipeline stages don't support packets larger than the MTU (maximum transmission unit). As a result, in most cases, you should use `FragmentationPipelineStage` as the first stage in pipelines with multiple stages. +::: -```csharp -public unsafe void DumpSimulatorStatistics() -{ - var simulatorStageId = NetworkPipelineStageCollection.GetStageId(typeof(SimulatorPipelineStage)); - driver.GetPipelineBuffers(pipeline, simulatorStageId, connection[0], out var receiveBuffer, out var sendBuffer, out var sharedBuffer); - var simCtx = (SimulatorUtility.Context*)sharedBuffer.GetUnsafeReadOnlyPtr(); - UnityEngine.Debug.Log("Simulator stats\n" + - "PacketCount: " + simCtx->PacketCount + "\n" + - "PacketDropCount: " + simCtx->PacketDropCount + "\n" + - "ReadyPackets: " + simCtx->ReadyPackets + "\n" + - "WaitingPackets: " + simCtx->WaitingPackets + "\n" + - "NextPacketTime: " + simCtx->NextPacketTime + "\n" + - "StatsTime: " + simCtx->StatsTime); -} -``` +## The reliable pipeline stage + +Pipelines configured with a `ReliableSequencedPipelineStage` guarantee the delivery and order of their packets, similar to how a [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol) connection would. The `ReliableSequencedPipelineStage` tags packets with a sequence number and peers acknowledge the reception of these numbers. If a peer doesn't acknowledge a packet, UTP resends it until it’s acknowledged. If a packet arrives out of order, UTP buffers it until it receives all earlier packets. + +While reliability is useful, you should use it sparingly in a multiplayer game context. Reliable data streams can suffer from [head-of-line blocking](https://en.wikipedia.org/wiki/Head-of-line_blocking), which can cause increased latency and delay packet processing. We recommend using the `ReliableSequencedPipelineStage` pipeline stage only for important traffic you can't afford to lose (like RPCs and character actions). -## Reliability pipeline +### Maximum in-flight packets -The reliability pipeline makes sure all packets are delivered and in order. It adds header information to all packets sent and tracks their state internally to make this happen. Whenever a packet is sent, it is given a sequence ID and then stored in the send processing buffer along with timing information (send time). The packet is then sent with that sequence ID added to the packet header. All packet headers also include information about what remote sequence IDs have been seen, so the receiver of the packet can know the delivery state of the packets it sent. This way there is always information about delivery state flowing between the two endpoints who make up a connection. If a certain time interval expires without an acknowledgement for a particular sequence ID the packet is resent and the timers reset. +The reliable stage limits the number of packets in-flight at any given time. The default limit is 32, but you can increase the limit up to 64. The in-flight packet limit is per connection and per pipeline; it's not shared across connections. -Reliability packet header looks like this: +Due to the in-flight package limitation, we recommend batching reliable messages together as much as possible. For example, instead of sending two reliable messages of 20 bytes each, concatenate them and send a single message of 40 bytes. + +If you try to send a new reliable message while the maximum number of packets is already in-flight, `EndSend` returns the error code `NetworkSendQueueFull` (value `-5`). If you encounter this situation, we recommend storing the message in a queue until it's possible to send it again: ```csharp -public struct PacketHeader +driver.BeginSend(myReliablePipeline, connection, out var writer); +// Write your message to the writer. +if (driver.EndSend(writer) == (int)Error.StatusCode.NetworkSendQueueFull)) { - public ushort Type; - public ushort ProcessingTime; - public ushort SequenceId; - public ushort AckedSequenceId; - public uint AckMask; + // Copy your message to a queue, and try resending later. } ``` -Where the type could be either a payload or ack packet, which is an empty packet with only this header. Processing time is time which passed between receiving a particular sequence ID and sending an acknowledgement for it, this is used for Round Trip Time (RTT) calculations. Then there is the sequence ID of this packet (not used in ack packets) and what remote sequence ID is being acknowledged. The AckMask is the history of acknowledgements we know about (up to the window size) so you can acknowledge multiple packets in a single header. - -The ack packet type is used when a certain amount of time has passed and nothing has been sent to the remote endpoint. We then check if we need to send a pending acknowledgement to him, or else the last packet will be assumed lost and a resend will take place. If a message is sent on every update call these kinds of packets never need to be sent. +### Increasing the limit -### Use the reliability pipeline - -The following creates a pipeline with just the reliability pipeline stage present, and initialize it to a window size of 32 (so it can keep track of 32 reliable packets at a one time). The maximum value for this is 32. Note this is a 32 packet limit per connection, and you may create multiple pipelines. +You can change the in-flight package limit when creating a `NetworkDriver`: ```csharp -m_ServerDriver = NetworkDriver.Create(new ReliableUtility.Parameters { WindowSize = 32 }); -m_Pipeline = m_ServerDriver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); +var settings = new NetworkSettings(); +settings.WithReliableStageParameters(windowSize: 64); + +var driver = NetworkDriver.Create(settings); +var reliablePipeline = driver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); ``` -Because only 32 packets can be tracked at a time there can't be more than 32 packets in flight at any one time, trying to send a 33rd packet will result in an error. It's possible to check for such errors by checking the error code in the reliability internal state: +The default limit is 32, and the maximum is 64. Any value higher than 32 results in slightly large (4 bytes) headers, leaving much less space for actual data in the packets. + +## The simulator pipeline stage + +The `SimulatorPipelineStage` is meant to be used when testing your application. It allows you to simulate network conditions like packet loss, delay, and jitter. You can use this stage to know how a game might behave in a real-world environment. + +You can configure the network conditions when creating a `NetworkDriver`: ```csharp -// Get a reference to the internal state or shared context of the reliability -var reliableStageId = NetworkPipelineStageCollection.GetStageId(typeof(ReliableSequencedPipelineStage)); -m_ServerDriver.GetPipelineBuffers(serverPipe, reliableStageId, serverToClient, out var tmpReceiveBuffer, out var tmpSendBuffer, out var serverReliableBuffer); -var serverReliableCtx = (ReliableUtility.SharedContext*) serverReliableBuffer.GetUnsafePtr(); - -m_ServerDriver.BeginSend(serverPipe, serverToClient, out var strm); -m_ServerDriver.EndSend(strm); -if (serverReliableCtx->errorCode != 0) -{ - // Failed to send with reliability, error code will be ReliableUtility.ErrorCodes.OutgoingQueueIsFull if no buffer space is left to store the packet -} +var settings = new NetworkSettings(); +settings.WithSimulatorStageParameters( + maxPacketCount: 100, + mode: ApplyMode.AllPackets, + packetDelayMs: 50); + +var driver = NetworkDriver.Create(settings); +var simulatorPipeline = driver.CreatePipeline(typeof(SimulatorPipelineStage)); ``` -It is possible to run into an `OutgoingQueueIsFull` error when packets are being sent too frequently for the latency and quality of the connection. High packet loss means packets need to stay for multiple Round Trip Times (RTTs) in the queue and if the RTT is high then that time can end up being longer than the send rate + window size permit. For example, with 60 packets sent per second, a packet will go out every 16 ms. If the RTT is 250ms, about 16 packets will be in the queue at any one time. With a packet drop, the total time will go up to 500 ms and the packet will be in the last slot when it is finally freed. - -It is best suited to use the reliability pipeline for event type messages (door opened), Remote Procedure Calls (RPCs), or slow frequency messages like chat. +The following list has some important parameters of the `SimulatorPipelineStage`. -### Debug information +* `maxPacketCount` is the maximum number of simultaneously delayed packets. Past that, packets go through without any added delay. +* mode In which direction the simulator should apply the network conditions (send, receive, or both). +* `packetDelayMs` is the delay in milliseconds to apply to packets. Good values range between `20` (for a good broadband connection) and `200` (for a bad mobile connection). +* `packetJitterMs` is the deviation around the packet delay in milliseconds. This value is typically half the packet delay or slightly less. +* `packetDropPercentage` is the percentage of packets to drop. You should use values above `3`, even for bad mobile connections. -More internal state information can be gathered using `GetPipelineBuffers` as shown above. The soaker test gathers statistics as seen in the *SoakCommon.cs* file, in the `GatherReliabilityStats` function. There it checks the internally used RTT and how many packets have been sent, received, dropped, duplicated and resent. +:::note +SimulatorPipelineStage should normally be the last in the chain when creating a pipeline with multiple stages. Otherwise, packets might drop before other stages can process them (which isn't useful for a reliable pipeline, for example). +::: diff --git a/transport_versioned_docs/version-2.0.0/supported-platforms.md b/transport_versioned_docs/version-2.0.0/supported-platforms.md index 300bd0604..b3cf6106f 100644 --- a/transport_versioned_docs/version-2.0.0/supported-platforms.md +++ b/transport_versioned_docs/version-2.0.0/supported-platforms.md @@ -2,4 +2,5 @@ id: supported-platforms title: Supported Platforms --- -COMING SOON. \ No newline at end of file + +Unity Transport seamlessly supports all platforms the Unity Engine supports thanks to a connection-based abstraction layer (built-in network driver) provided over UDP and WebSockets. \ No newline at end of file diff --git a/transport_versioned_docs/version-2.0.0/using-sample.md b/transport_versioned_docs/version-2.0.0/using-sample.md index 05331bb4d..0c4467eda 100644 --- a/transport_versioned_docs/version-2.0.0/using-sample.md +++ b/transport_versioned_docs/version-2.0.0/using-sample.md @@ -1,6 +1,29 @@ --- id: using-sample -title: Using Sample +title: Sample projects --- -COMING SOON. +The Unity Transport package comes with a `Samples` folder containing simple assembly definitions and associated scenes that illustrate the basic functionality of the Unity Transport package library. These sample projects include: + +- [Ping](#ping) +- [Pipeline](#pipeline) +- [RelayPing](#relayping) + +## Ping + +The Ping sample project implements a simple ping-pong service and supplies client and server behaviors for two scenarios. + +- The first scenario uses `PingMainThreadServerBehaviour` and `PingMainThreadClientBehaviour` to implement peers to process messages only in the main thread. +- The second scenario shows how to use the Burst compiler and the job system with `PingServerBehaviour` and `PingClientBehaviour`. + +## Pipeline + +The Pipeline sample shows how to define [pipeline](pipelines-usage.md) stages as described in [Pipelines](pipelines-usage.md). The code demonstrates a pipeline definition for unreliable sequenced delivery based on a default `UDPNetworkInterface`. + +:::note +Consider using pipeline stages carefully. Some pipeline configurations can't add value to the quality of service and might be harmful, depending on the underlying `NetworkInterface` you use. For example, it makes sense to have a pipeline stage for unreliable sequenced delivery over a `UDPNetworkInterface`. The same [pipeline](pipelines-usage.md) stage over a `WebSocketNetworkInterface` incurs unnecessary overhead because the underlying network interface already provides unreliable sequenced delivery. +::: + +## RelayPing + +The RelayPing sample project extends the [Ping](#ping) sample project to use the [Unity Relay Service](https://unity.com/products/relay) to connect players. It shows how to manipulate and pass custom [Network Settings](network-settings.md) to a `NetworkDriver`. diff --git a/transport_versioned_docs/version-2.0.0/workflow-client-server-jobs.md b/transport_versioned_docs/version-2.0.0/workflow-client-server-jobs.md index 93c44e371..2c787137a 100644 --- a/transport_versioned_docs/version-2.0.0/workflow-client-server-jobs.md +++ b/transport_versioned_docs/version-2.0.0/workflow-client-server-jobs.md @@ -3,20 +3,15 @@ id: jobs title: Efficient client and server --- -In the workflow [Creating a minimal client and server](workflow-client-server.md), the client should look like this [code example](samples/clientbehaviour.cs.md). +This section guides you through creating a jobified client and server. It extends and modifies the [minimal client and server example](workflow-client-server-udp.md) to use jobs to leverage parallel code execution. -## Prerequisites - -1. Before reading and using this workflow, you should understand how the [C# Job System](https://docs.unity3d.com/Manual/JobSystem.html) works. Review that information, then continue. -2. Install the jobs package to your `manifest.json` file, inside the */Packages* folder. To do this: - 1. Open your **Unity Editor** and create a new Project. - 2. Go to **Window** > **Package Manager** from the main menu to open the **Unity Package Manager**. - 3. Click on the ![Add](/img/add.png) (plus symbol) in the status bar and select **Add package from git URL...** from the dropdown. - 4. Enter `com.unity.jobs` for the latest version of the jobs package. +:::warning +Before reading this tutorial, you should understand how the [C# Job System](https://docs.unity3d.com/Manual/JobSystem.html) works. +::: -## Create a Jobified Client +## Create a jobified client -Create a client job to handle your inputs from the network. As you only handle one client at a time, use [IJob](https://docs.unity3d.com/ScriptReference/Unity.Jobs.IJob.html) as your job type. You need to pass the driver and the connection to the job to handle updates within the `Execute` method of the job. +This section shows how to create a jobified client to handle inputs from the network. Because you only handle one client at a time, use [`IJob`](https://docs.unity3d.com/ScriptReference/Unity.Jobs.IJob.html) as your job type. You need to pass the driver and the connection to the job to handle updates within the job’s Execute method. ```csharp struct ClientUpdateJob: IJob @@ -30,18 +25,18 @@ struct ClientUpdateJob: IJob ``` :::note -The data inside the `ClientUpdateJob` is **copied**. If you want to use the data after the job is completed, you need to have your data in a shared container, such as a [NativeContainer](https://docs.unity3d.com/Manual/JobSystemNativeContainer.html). +The data inside the `ClientUpdateJob` is copied. If you want to use the data after completing the job, you must have your data in a shared container, such as a [`NativeContainer`](https://docs.unity3d.com/Manual/JobSystemNativeContainer.html). ::: -You may want to update the `NetworkConnection` and the `done` variables inside your job as you may receive a disconnect message. Verify you can share the data between the job and the caller. In this case, use a [NativeArray](https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeArray_1.html). +You should update the `NetworkConnection` and the done variables inside the job because you might receive a disconnect message. Verify you can share the data between the job and the caller. In this case, use a [`NativeArray`](https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeArray_1.html). :::note -You can only use [blittable types](https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types) in a `NativeContainer`. In this case, instead of a `bool` you need to use a `byte`, as its a blittable type. +You can only use [blittable types](https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types) in a NativeContainer. In this case, because `bool` isn't blittable, you must use a `byte` instead of `bool`. ::: -In your `Execute` method, move over your code from the `Update` method that you have already in place from [_ClientBehaviour.cs_](samples/clientbehaviour.cs.md) and you are done. +Move the code from the Update method (from [`ClientBehaviour.cs`](samples/clientbehaviour.cs.md)) to the Execute method. -You need to change any call to `m_Connection` to `connection[0]` to refer to the first element inside your `NativeArray`. The same goes for your `done` variable, you need to call `done[0]` when you refer to the `done` variable. See the following: +You need to change any call to `m_Connection` to `connection[0]` to refer to the first element inside the `NativeArray`. The same goes for your done variable; you must call `done[0]` when you refer to the done variable. See the following code snippet: ```csharp public void Execute() @@ -87,12 +82,10 @@ public void Execute() ### Update the client MonoBehaviour -When you have a job, you need to verify that you can execute the job. +When you have a job, you must verify that you can execute the job. The following code sample shows the changes to `ClientBehaviour`: -Complete changes to `ClientBehaviour`: - -* Change `m_Done` and `m_Connection` to type `NativeArray` -* Add a [JobHandle](https://docs.unity3d.com/Manual/JobSystemJobDependencies.html) to track ongoing jobs +* Change `m_Done` and `m_Connection` to type `NativeArray`. +* Add a [JobHandle](https://docs.unity3d.com/Manual/JobSystemJobDependencies.html) to track ongoing jobs. ```csharp public class JobifiedClientBehaviour : MonoBehaviour @@ -110,6 +103,8 @@ public class JobifiedClientBehaviour : MonoBehaviour #### Start method +The Start method looks similar to the one in the simple client example; the most significant update is that the jobified version verifies that you create a `NativeArray`. + ```csharp void Start () { m_Driver = NetworkDriver.Create(); @@ -123,9 +118,9 @@ void Start () { } ``` -The `Start` method looks pretty similar to before, the major update here is to verify you create your `NativeArray`. +#### `OnDestroy` method -#### OnDestroy method +In the `OnDestroy` method, dispose of all `NativeArray` objects. Then add a `ClientJobHandle.Complete()` call to ensure the jobs complete before you clean up and destroy the data they might be using. ```csharp public void OnDestroy() @@ -138,11 +133,9 @@ public void OnDestroy() } ``` -For the `OnDestroy` method, dispose all `NativeArray` objects. Add a `ClientJobHandle.Complete()` call. This ensures your jobs complete before cleaning up and destroying the data they might be using. +#### Client `Update` loop -#### Client Update loop - -Finally update your core game loop: +Update the core game loop. Ensure the last frame completes before running the new frame. Instead of calling `m_Driver.ScheduleUpdate().Complete()`, use the `JobHandle`, then call `ClientJobHandle.Complete()`. ```csharp void Update() @@ -152,9 +145,7 @@ void Update() } ``` -Before you start running your new frame, check that the last frame has completed. Instead of calling `m_Driver.ScheduleUpdate().Complete()`, use the `JobHandle` and call `ClientJobHandle.Complete()`. - -To chain your job, start by creating a job struct: +Next, create a job struct to chain the job: ```csharp var job = new ClientUpdateJob @@ -165,25 +156,25 @@ var job = new ClientUpdateJob }; ``` - To schedule the job, pass the `JobHandle` dependency that was returned from the `m_Driver.ScheduleUpdate` call in the `Schedule` function of your `IJob`. Start by invoking the `m_Driver.ScheduleUpdate` without a call to `Complete`, and pass the returning `JobHandle` to your saved `ClientJobHandle`. +To schedule the job, pass the `JobHandle` dependency returned from the `m_Driver.ScheduleUpdate` call in the Schedule function of your `IJob`. Start by invoking the `m_Driver.ScheduleUpdate` without a call to `Complete`, and pass the returning `JobHandle` to the saved `ClientJobHandle`. -Pass the returned `ClientJobHandle` to your own job, returning a newly updated `ClientJobHandle`. +Pass the returned `ClientJobHandle` to the job you created, returning a newly updated `ClientJobHandle`. ```csharp ClientJobHandle = m_Driver.ScheduleUpdate(); ClientJobHandle = job.Schedule(ClientJobHandle); ``` -You now have a *JobifiedClientBehaviour* that looks like [this](samples/jobifiedclientbehaviour.cs.md). +You now have a `JobifiedClientBehaviour` that looks like [this](samples/jobifiedclientbehaviour.cs.md). -## Create a Jobified Server +## Create a jobified server -The server side is pretty similar to start with. You create the jobs you need and then you update the usage code. +The jobified server is similar to the simple server in the earlier example. The only necessary modification is to create the jobs you need, then update the usage code. -Consider this: you know that the `NetworkDriver` has a `ScheduleUpdate` method that returns a `JobHandle`. The job as you saw above populates the internal buffers of the `NetworkDriver` and lets us call `PopEvent`/`PopEventForConnection` method. What if you create a job that will fan out and run the processing code for all connected clients in parallel? If you look at the documentation for the C# Job System, you can see that there is a [IJobParallelFor](https://docs.unity3d.com/Manual/JobSystemParallelForJobs.html) job type that can handle this scenario +The `NetworkDriver` has a `ScheduleUpdate` method that returns a `JobHandle` that populates the internal buffers of the `NetworkDriver` and provides the `PopEvent`/`PopEventForConnection` method. You can use the [`IJobParallelFor`](https://docs.unity3d.com/Manual/JobSystemParallelForJobs.html) job type to create a job to run the processing code for all connected clients in parallel. :::note -Because you do not know how many requests you may receive or how many connections you may need to process at any one time, there is another `IJobPrarallelFor` job type that you can use namely: `IJobParallelForDefer`. +There’s another `IJobPrarallelFor` job type you can use: `IJobParallelForDefer`. This job type suits the jobified server because you don’t know the exact number of requests the server might receive or the number of connections the server might need to process. ::: ```csharp @@ -196,11 +187,11 @@ struct ServerUpdateJob : IJobParallelForDefer } ``` -However, you cannot run all of your code in parallel. +However, you can only run some of your code in parallel. -In the client example above, you begin by cleaning up closed connections and accepting new ones, which cannot be done in parallel. You need to create a connection job. +The jobified client begins by cleaning up closed connections and accepting new ones, which you can't do in parallel. Instead, you must create a connection job. -Start by creating a `ServerUpdateConnectionJob` job. Pass both the `driver` and `connections` to the connection job. Then you want your job to "Clean up connections" and "Accept new connections": +Create a `ServerUpdateConnectionJob` job, then pass both the driver and connections to the connection job. This job should clean up connections and accept new connections: ```csharp struct ServerUpdateConnectionsJob : IJob @@ -230,7 +221,7 @@ struct ServerUpdateConnectionsJob : IJob } ``` -The code above should be almost identical to your old non-jobified code. +The code above is nearly the same as the non-jobified server code from the simple server example. With the `ServerUpdateConnectionsJob` done, implement the `ServerUpdateJob` using `IJobParallelFor`: @@ -247,14 +238,20 @@ struct ServerUpdateJob : IJobParallelForDefer } ``` -There are two major differences compared with the other `job`: +The `ServerUpdateJob` has two significant differences compared with the `ServerUpdateConnectionJob` job: -* You are using the `NetworkDriver.Concurrent` type, this allows you to call the `NetworkDriver` from multiple threads, precisely what you need for the `IParallelForJobDefer`. Secondly, -* You are now passing a `NativeArray` of type `NetworkConnection` instead of a `NativeList`. The `IParallelForJobDefer` does not accept any other `Unity.Collections` type than a `NativeArray` (more on this later). +* The `ServerUpdateJob` uses the `NetworkDriver.Concurrent` type, which allows you to call the `NetworkDriver` from multiple threads. This is precisely what you need for the `IParallelForJobDefer`. +* The `ServerUpdateJob` passes a `NativeArray` of type `NetworkConnection` instead of a `NativeList`. The `IParallelForJobDefer` doesn't accept any other `Unity.Collections` type than a `NativeArray`. ### Execute method -The only difference between the old code and the jobified example is that you remove the top level `for` loop that you had in your code: `for (int i = 0; i < m_Connections.Length; i++)`. This is removed because the `Execute` function on this job will be called for each connection, and the `index` to that a available connection will be passed in. +The only difference in the Execute method between the simple server code and the jobified server code is that you remove the top-level for loop in the jobified server: + +```csharp +for (int i = 0; i < m_Connections.Length; i++) +``` + +The jobified server doesn’t need this top-level for loop because it calls the Execute function for each connection, and passes in the index to that available connection. ```csharp public void Execute(int index) @@ -286,26 +283,24 @@ public void Execute(int index) } ``` -You can see this `index` in use in the top level `while` loop: +The top-level `while` loop uses the index of the available connection: -``` +```csharp while ((cmd = driver.PopEventForConnection(connections[index], out stream)) != NetworkEvent.Type.Empty` ``` :::note -You are using the `index` that was passed into your `Execute` method to iterate over all the `connections`. +Use the index of the connection (passed into the `Execute` method) to iterate over all the connections. ::: You now have two jobs: -* The first job is to update your connection status: - * Add new connections. - * Remove old or stale connections. -* The second job is to parse `NetworkEvent` on each connected client. +* The first job, `ServerUpdateConnectionJob`, updates the connection status by adding new connections and removing old connections. +* The second job, `ServerUpdateJob`, parses the `NetworkEvent` for each connected client. -### Update the server MonoBehaviour +### Update the server `MonoBehaviour` -Access your [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and start updating the server. +Next, access the jobified server [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and start updating the server. ```csharp public class JobifiedServerBehaviour : MonoBehaviour @@ -322,11 +317,11 @@ public class JobifiedServerBehaviour : MonoBehaviour } ``` -The only change made in your variable declaration is adding a `JobHandle` to keep track of your ongoing jobs. +The only change between the simple server and the jobified server variable declaration is that the jobified server adds a JobHandle to keep track of ongoing jobs. -#### Start method +#### Server `Start` method -You do not need to change your `Start` method as it should look the same: +You don’t need to change the `Start` method from the simple server example: ```csharp void Start () @@ -343,9 +338,9 @@ void Start () } ``` -#### OnDestroy method +#### Server `OnDestroy` method -You need to remember to call `ServerJobHandle.Complete` in your `OnDestroy` method so you can properly clean up code: +Remember to call `ServerJobHandle.Complete` in the `OnDestroy` method to clean up the code: ```csharp public void OnDestroy() @@ -360,9 +355,9 @@ public void OnDestroy() } ``` -#### Server update loop +#### Server `Update` loop -In your `Update` method, call `Complete` on the `JobHandle`. This forces the jobs to complete before you start a new frame: +Call `Complete` on the `JobHandle` in the Update method to force the jobs to complete before you start a new frame: ```csharp void Update () @@ -387,10 +382,13 @@ void Update () } ``` -To chain the jobs, you want to follow this process: -`NetworkDriver.Update` -> `ServerUpdateConnectionsJob` -> `ServerUpdateJob`. +Use the following process to chain the jobs: + +1. `NetworkDriver.Update` +2. `ServerUpdateConnectionsJob` +3. `ServerUpdateJob` -Start by populating your `ServerUpdateConnectionsJob`: +Start by populating the `ServerUpdateConnectionsJob` with the `NetworkDriver` and the connections: ```csharp var connectionJob = new ServerUpdateConnectionsJob @@ -400,7 +398,7 @@ var connectionJob = new ServerUpdateConnectionsJob }; ``` -Then create your `ServerUpdateJob`. Remember to use the `ToConcurrent` call on your driver, to verify you are using a concurrent driver for the `IParallelForJobDefer`: +Next, create the `ServerUpdateJob`. Remember to call `ToConcurrent` on the `NetworkDriver` to ensure you’re using a concurrent driver for the `IParallelForJobDefer`: ```csharp var serverUpdateJob = new ServerUpdateJob @@ -410,23 +408,40 @@ var serverUpdateJob = new ServerUpdateJob }; ``` -The final step is to verify the `NativeArray` is populated to the correct size. This -can be done using a `DeferredJobArray`. When executed, it verifies the connections array is populated with the correct number of items that you have in your list. When runnning `ServerUpdateConnectionsJob` first, this may change the **size** of the list. +Verify the `NativeArray` is populated with the correct size. You can verify the `NativeArray` size using a `DeferredJobArray`. `DeferredJobArray` verifies that the connections array is populated with the same number of items in the connections list. -Create your job chain and call `Scheduele` as follows: +:::note +The size of the connections list might change when you first run `ServerUpdateConnectionsJob`. +::: -``` +Create the job chain and call `Scheduele` as follows: + +```csharp ServerJobHandle = m_Driver.ScheduleUpdate(); ServerJobHandle = connectionJob.Schedule(ServerJobHandle); ServerJobHandle = serverUpdateJob.Schedule(m_Connections, 1, ServerJobHandle); ``` -In the code above, you have: +In the code above, you: * Scheduled the `NetworkDriver` job. -* `JobHandle` returned as a dependency on the `ServerUpdateConnectionJob`. -* The final link in the chain is the `ServerUpdateJob` that needs to run after `ServerUpdateConnectionsJob`. In this line of code, there is a trick to invoke the `IJobParallelForDeferExtensions`. `m_Connections` `NativeList` is passed to the `Schedule` method, which updates the count of connections before starting the job. It will fan out and run all `ServerUpdateConnectionJobs` in parallel. +* `JobHandle` is returned as a dependency on the `ServerUpdateConnectionJob`. +* The final link in the chain is the `ServerUpdateJob`. It must run after `ServerUpdateConnectionsJob` to invoke the `IJobParallelForDeferExtensions`. `m_Connections` `NativeList` passed to the `Schedule` method, which updates the connections count before starting the job. It fans out and runs all `ServerUpdateConnectionJobs` in parallel. + +You now have a fully functional [jobified server](https://docs.unity3d.com/Packages/com.unity.transport@2.0/manual/samples/jobifiedserverbehaviour.cs.html). + +## Use Burst for extra performance -You should now have a fully functional [jobified server](samples/jobifiedserverbehaviour.cs.md). +All the jobs in the example code adhere to [the subset of C# supported by Burst](https://docs.unity3d.com/Packages/com.unity.burst@1.7/manual/docs/CSharpLanguageSupport_Types.html). Burst is a compiler that pre-compiles Unity jobs into highly-performant native code. Unity Transport takes advantage of Burst and uses mostly Burst-friendly data structures (like `NetworkDriver`). +You can make a job Burst-compiled by adding the [`BurstCompile`] attribute to its definition. For example: + +```csharp +[BurstCompile] +struct ClientUpdateJob : IJob +{ + ... +} +``` +Refer to the [Burst documentation](https://docs.unity3d.com/Packages/com.unity.burst@latest) for more details on how to use it. diff --git a/transport_versioned_docs/version-2.0.0/workflow-client-server-secure.md b/transport_versioned_docs/version-2.0.0/workflow-client-server-secure.md index 16f1c8745..6da435396 100644 --- a/transport_versioned_docs/version-2.0.0/workflow-client-server-secure.md +++ b/transport_versioned_docs/version-2.0.0/workflow-client-server-secure.md @@ -1,82 +1,92 @@ --- id: secure-connection -title: Create secure client and server +title: Create a secure client and server --- -The Unity Transport package can be configure to encrypt the connection between the server and the client while ensuring the server's/client's authenticity. +You can configure the Unity Transport package to encrypt the connection between the server and clients while ensuring the authenticity of both. -Secure connections are available with editor versions 2020.3 (starting at 2020.3.34), 2021.3, and 2022.1 and above. +:::note +Secure connections are available in Unity Editor versions 2020.3 (starting at 2020.3.34), 2021.3, and 2022.1 and above. +::: ## Server authentication -:::warning Warning -This example uses hardcoded certificates to make understanding the process easier, but in a real deployment the server certificates should be kept separate from client builds. One way to achieve this is to put them on a separate assembly, or load them from a file on the server. +This section demonstrates using encrypted communications with Unity Transport. + +:::warning +This example uses hardcoded certificates to make understanding the process easier. In a real deployment, you should keep the server certificates separate from client builds. You can separate server and client certificates by using separate assemblies or loading them from a file on the server. ::: -### High level authentication process +### High-level authentication process -In this configuration, the server will provide a certificate to the client (`certificate`) to prove its identity. The client will validate the certificate against its own root certificate (`caCertificate`) to validate its identity. +In this configuration, the server provides a certificate to the client (certificate) to prove its identity. The client compares the server’s certificate against its own root certificate (`caCertificate`) to validate the server’s identity. :::note Root certificates are also sometimes referred to as CA certificates. ::: -Once its identity confirmed, the server will then use the private key (`privateKey`) to establish the secure communication channel. +After the client confirms the server's identity, the server uses the private key (`privateKey`) to establish a secure communication channel. ### Requirements -To use the client/server secure workflow, you need a valid certificate and the root certificate that was used to sign it. You also need the private key that has been used to create the certificate. If you don't have these, they can be generated using OpenSSL. The procedure is detailed hereafter. +To use the secure communication workflow, you need the following: + +- A valid certificate +- The root certificate used to sign the certificate +- The private key used to create the certificate -### Generating the required keys and certificates with OpenSSL +If you don't have these, you can use OpenSSL to generate them. The following section explains how to use OpenSSL to generate certificates. -It is assumed that you have [OpenSSL](https://www.openssl.org/) installed on your machine. +### Generate keys and certificates + +This section explains how to use OpenSSL to generate a valid certificate (a requirement for using encrypted connections). Before continuing, ensure you have [OpenSSL](https://www.openssl.org/) installed on your machine. #### Generate the root certificate -First thing first is to generate a root private key. We will use it later on to generate the root certificate. +Generate a root private key. You need the root private key to generate the root certificate. -```shell +```csharp openssl genrsa -out clientPrivateKeyForRootCA.pem 2048 ``` -Now that you have a root private key, you can now generate the root certificate. +Use the root private key to generate the root certificate. -```shell +```csharp openssl req -x509 -new -nodes -key clientPrivateKeyForRootCA.pem -sha256 -days 1095 -out myGameClientCA.pem ``` -You will be prompted to answer several questions. Most of the answers are not that important within the present context. -It is however useful to use a common name that makes sense for you to identify this certificate amongst others. Ideally, you would want to use your domain name if you have one. +OpenSSL will prompt you to answer several questions. Most of the answers aren't that important within the present context. It's useful to use a common name that makes sense for you to identify this certificate, amongst others. Ideally, you want to use a domain name you own (if you have one). #### Generate the root-signed certificate to use with the server -Create now a private key for the server. +Create a private key for the server. -```shell +```csharp openssl genrsa -out myGameServerPrivateKey.pem 2048 ``` -From this private key, you can generate a certificate signing request. +Use the private key to generate a certificate signing request. -```shell +```csharp openssl req -new -key myGameServerPrivateKey.pem -out myGameServerCertificateSigningRequest.pem ``` -You'll be prompted with the same questions as for generating the root certificate. The answers are no more important, except for the common name: it is recommended to use the server's hostname. +OpenSSL will prompt you with the same questions you answered when you generated the root certificate. The answers are no more important. However, we recommend you use the server's hostname. -Finally, using the different files generated, we can create the certificate file the server will use to authenticate itself: +Using the different files generated, create the certificate file the server will use to authenticate itself: -```shell +```csharp openssl.exe x509 -req -in myGameServerCertificateSigningRequest.pem -CA myGameClientCA.pem -CAkey clientPrivateKeyForRootCA.pem -CAcreateserial -out myGameServerCertificate.pem -days 365 -sha256 ``` -You should have now generated a total of five files. Out of these, only three will be used later on: -* The content of the `myGameClientCA.pem` file will be used client-side as the `caCertificate` parameter. -* On the server end, the contents of `myGameServerCertificate.pem` will be used for the `certificate` parameter. -* On the server end, the contents of `myGameServerPrivateKey.pem` will be used for the `privateKey` parameter. +You should now have five generated files. Out of these, you only need the following three: + +- `myGameClientCA.pem` - You need the content of the client CA to use client-side as the `caCertificate` parameter. +- `myGameServerCertificate.pem` - You need the server certificate to use server-side for the certificate parameter. +- `myGameServerPrivateKey.pem` - You need the server private key to use server-side for the `privateKey` parameter. ### Boilerplate file holding the secure parameters -Create a `SecureParameters.cs` script file to hold your certificates and the private key. Place it in the same folder as the minimal server and minimal client scripts. Then declare the `SecureParameters` static class and the boilerplate code that will hold your secure information: +When you have all the requirements, create a `SecureParameters.cs` script file to hold your certificates and the private key. Place it in the same folder as the minimal server and minimal client scripts. Then declare the `SecureParameters` static class and the boilerplate code to hold the secure information: ```csharp public static class SecureParameters @@ -99,11 +109,13 @@ public static class SecureParameters *** Contents of myGameServerPrivateKey.pem *** -----END RSA PRIVATE KEY-----"; } -``` +``` + +### Create a secure server -### Creating the secure server +This section demonstrates creating a secure server. It uses the simple server code example as a starting point. -Starting from the minimal server sample code, create a `NetworkSettings` object in the `Start` method and configure it as follows: +Start by creating a `NetworkSettings` object in the `Start` method and configure it as follows: ```csharp void Start () @@ -123,11 +135,13 @@ When creating the `NetworkDriver`, pass in this `NetworkSettings` object: m_Driver = NetworkDriver.Create(settings); ``` -That's it for the server! +That’s all you need to do to enable secure communication server-side. + +### Create a secure client -### Creating a secure client +This section demonstrates creating a secure client. It uses the simple client code example as a starting point. -The secure client is very similar to the secure server. The only difference is in how the `NetworkSettings` object is configured. +The secure client is similar to the secure server. The only difference is in how you configure the NetworkSettings object. ```csharp void Start () @@ -144,6 +158,6 @@ void Start () You should now have a secure connection between the server and its clients! -:::note -If you create clients for multiple platforms, it is important for all of these to still use the same root certificate if they communicate with the same server. +:::warning +If you create clients for multiple platforms, all clients must use the same root certificate if they communicate with the same server. ::: diff --git a/transport_versioned_docs/version-2.0.0/workflow-client-server-udp.md b/transport_versioned_docs/version-2.0.0/workflow-client-server-udp.md index 3b5908914..08f7b930a 100644 --- a/transport_versioned_docs/version-2.0.0/workflow-client-server-udp.md +++ b/transport_versioned_docs/version-2.0.0/workflow-client-server-udp.md @@ -1,33 +1,36 @@ --- id: minimal-workflow-udp -title: Client and Server over UDP +title: Create a simple client and server --- -:::note -Need an update before releasing UTP 2.0.0 -::: +This guide walks you through using the Unity Transport package to create a simple client and server that use a remote function over a UDP connection to add two numbers with the following flow: -This Transport workflow covers all aspects of the `Unity.Networking.Transport` package and helps you create a sample project that highlights how to use the `com.unity.transport` API to: +1. The client connects to the server. +2. The client sends a number to the server. +3. The server receives the number and adds it to another number. +4. The server sends the sum of the two numbers to the client. +5. The client receives the sum. +6. The client disconnects from the server and quits. -* Configure -* Connect -* Send data -* Receive data -* Close a connection -* Disconnect -* Timeout a connection +It demonstrates using the Unity Transport API to: -The goal is to make a remote `add` function. The flow will be: a client connects to the server, and sends a number, this number is then received by the server that adds another number to it and sends it back to the client. The client, upon receiving the number, disconnects and quits. +- Configure +- Connect +- Send data +- Receive data +- Close a connection +- Disconnect +- Timeout a connection -Using the `NetworkDriver` to write client and server code is similar between clients and servers; there are a few subtle differences demonstrated in this guide. +The client-server workflow in this guide demonstrates the subtle differences between using the `NetworkDriver` for clients and servers. -## Creating a Server +## Create a server -A server is an endpoint that listens for incoming connection requests and sends and receives messages. +A server is an endpoint that listens for incoming connection requests and sends and receives messages. This section demonstrates creating a simple server with UTP 2.0. -Start by creating a C# script in the Unity Editor. +Start by creating a C# script in the Unity Editor. Name the script ServerBehaviour.cs. -Filename: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) +**Filename**: [`ServerBehaviour.cs`](samples/serverbehaviour.cs.md) ```csharp using System.Collections; @@ -48,17 +51,15 @@ public class ServerBehaviour : MonoBehaviour { } ``` -### Boilerplate code - -As the `com.unity.transport` package is a low level API, there is a bit of boiler plate code you might want to setup. This is an architecture design Unity chose to make sure that you always have full control. +The `com.unity.transport` package is a low-level API, and, as a result, there is a bit of boilerplate code you should set up. The necessity of the boilerplate code is due to an architecture design to ensure you always have full control. :::note -As development on the `com.unity.transport` package evolves, more abstractions may be created to reduce your workload on a day-to-day basis. +As development on the com.unity.transport package evolves, Unity might create more abstractions to reduce your workload on a day-to-day basis. ::: -The next step is to clean up the dependencies and add our boilerplate code: +In the `ServerBehaviour.cs` script, clean up the dependencies and add the boilerplate code: -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) +**Filename**: [`ServerBehaviour.cs`](samples/serverbehaviour.cs.md) ```csharp using UnityEngine; @@ -70,13 +71,9 @@ using Unity.Networking.Transport; ... ``` -#### Code walkthrough - -### ServerBehaviour.cs +The following code demonstrates the necessary boilerplate code and creates empty bodies for the `Start`, `OnDestroy`, and `Update` methods. It also declares a `NetworkDriver` and creates a `NativeList` to hold all connections between the client and server. -Adding the members we need the following code: - -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) +**Filename**: [`ServerBehaviour.cs`](samples/serverbehaviour.cs.md) ```csharp using ... @@ -95,20 +92,17 @@ public class ServerBehaviour : MonoBehaviour { void Update () { } -``` - -#### Code walkthrough - -``` public NetworkDriver m_Driver; private NativeList m_Connections; ``` -You need to declare a `NetworkDriver`. You also need to create a NativeList to hold our connections. +Next, expand the `Start`, `OnDestroy`, and `Update` methods. + +### `Start` method -### Start method +First, define the logic in the [`MonoBehaviour.Start` method](https://docs.unity3d.com/ScriptReference/MonoBehaviour.Start.html). -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) +**Filename**: [`ServerBehaviour.cs`](samples/serverbehaviour.cs.md) ```csharp void Start () @@ -125,34 +119,34 @@ void Start () } ``` -#### Code walkthrough +The first line of code, `m_Driver = NetworkDriver.Create()`, creates a `NetworkDriver` instance without any parameters. -The first line of code, `m_Driver = NetworkDriver.Create();` , just makes sure you are creating your driver without any parameters. +Next, `m_Driver.Bind` binds the `NetworkDriver` instance to a specific network address and port, and if that doesn't fail, it calls the `Listen` method. ```csharp - if (m_Driver.Bind(endpoint) != 0) + if (m_Driver.Bind(endpoint) != 0) Debug.Log("Failed to bind to port 9000"); else m_Driver.Listen(); ``` -Then we try to bind our driver to a specific network address and port, and if that does not fail, we call the `Listen` method. - :::important -the call to the `Listen` method sets the `NetworkDriver` to the `Listen` state. This means that the `NetworkDriver` will now actively listen for incoming connections. +The call to the `Listen` method sets the `NetworkDriver` to the `Listen` state, which means the `NetworkDriver` actively listens for incoming connections. ::: -` m_Connections = new NativeList(16, Allocator.Persistent);` +`m_Connections` creates a `NativeList` to hold all the connections. -Finally we create a `NativeList` to hold all the connections. +```csharp +m_Connections = new NativeList(16, Allocator.Persistent); +``` -### OnDestroy method +### `OnDestroy` method -Both `NetworkDriver` and `NativeList` allocate unmanaged memory and need to be disposed. To make sure this happens we can simply call the `Dispose` method when we are done with both of them. +You must dispose of both `NetworkDriver` and `NativeList` because they allocate unmanaged memory. To ensure proper disposal, call the `Dispose` method when you no longer need them. -Add the following code to the `OnDestroy` method on your [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html): +Add the following code to the OnDestroy method on [`MonoBehaviour`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html): -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) +**Filename**: [`ServerBehaviour.cs`](samples/serverbehaviour.cs.md) ```csharp public void OnDestroy() @@ -165,11 +159,11 @@ public void OnDestroy() } ``` -The check for `m_Driver.IsCreated` ensures we don't dispose of the memory if it hasn't been allocated (e.g. if the component is disabled). +The check for `m_Driver.IsCreated` ensures you don't dispose of unallocated memory. For example, UTP doesn’t allocate memory for disabled components. -### Server Update loop +### Server `Update` loop -As the `com.unity.transport` package uses the [Unity C# Job System](https://docs.unity3d.com/Manual/JobSystem.html) internally, the `m_Driver` has a `ScheduleUpdate` method call. Inside our `Update` loop you need to make sure to call the `Complete` method on the [JobHandle](https://docs.unity3d.com/Manual/JobSystemJobDependencies.html) that is returned, in order to know when you are ready to process any updates. +The `com.unity.transport` package uses the [Unity C# Job System](https://docs.unity3d.com/Manual/JobSystem.html) internally. As a result, the `m_Driver` has a `ScheduleUpdate` method call. Call the `Complete` method on the returned [JobHandle](https://docs.unity3d.com/Manual/JobSystemJobDependencies.html) inside the `Update` loop to ensure you know when to process updates. ```csharp void Update () { @@ -178,16 +172,15 @@ void Update () { ``` :::note -In this example, we are forcing a synchronization on the main thread in order to update and handle our data later in the `MonoBehaviour::Update` call. The workflow [Creating a jobified client and server](workflow-client-server-jobs.md) shows you how to use the Transport package with the C# Job System. +This example forces synchronization on the main thread to update and handle the data later in the `MonoBehaviour::Update` call. The workflow [Create a jobified client and server](workflow-client-server-jobs.md) shows how to use the Transport package with the C# Job System. ::: +After updating `m_Driver`, the first thing you must do is handle the connections. Start by cleaning up any stale connections from the list before processing new ones. Cleaning up stale connections ensures you don't have any old connections lying around when you iterate through the list to check for new events. -The first thing we want to do, after you have updated your `m_Driver`, is to handle your connections. Start by cleaning up any old stale connections from the list before processing any new ones. This cleanup ensures that, when we iterate through the list to check what new events we have gotten, we dont have any old connections laying around. - -Inside the "Clean up connections" block below, we iterate through our connection list and just simply remove any stale connections. +The following code iterates through the connection list and removes any stale connections. ```csharp - // Clean up connections + // Clean up connections for (int i = 0; i < m_Connections.Length; i++) { if (!m_Connections[i].IsCreated) @@ -198,10 +191,10 @@ Inside the "Clean up connections" block below, we iterate through our connection } ``` -Under "Accept new connections" below, we add a connection while there are new connections to accept. +The following code adds a connection while there are new connections to accept. ```csharp - // Accept new connections + // Accept new connections NetworkConnection c; while ((c = m_Driver.Accept()) != default(NetworkConnection)) { @@ -210,50 +203,50 @@ Under "Accept new connections" below, we add a connection while there are new co } ``` -Now we have an up-to-date connection list. You can now start querying the driver for events that might have happened since the last update. +You now have an up-to-date connection list and can start querying the driver for events that might have happened since the last update. ```csharp - DataStreamReader stream; + DataStreamReader stream; for (int i = 0; i < m_Connections.Length; i++) { if (!m_Connections[i].IsCreated) continue; ``` -Begin by defining a `DataStreamReader`. This will be used in case any `Data` event was received. Then we just start looping through all our connections. +Begin by defining a `DataStreamReader`, which you’ll use to process received Data events. Next, loop through the connections. -For each connection we want to call `PopEventForConnection` while there are more events still needing to get processed. +Call `PopEventForConnection` for each connection while unprocessed events exist. ```csharp - NetworkEvent.Type cmd; + NetworkEvent.Type cmd; while ((cmd = m_Driver.PopEventForConnection(m_Connections[i], out stream)) != NetworkEvent.Type.Empty) { ``` :::note -There is also the `NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader slice)` method call, that returns the first available event, the `NetworkConnection` that its for and possibly a `DataStreamReader`. +You can also use the `NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader slice)` method call, which returns the first available event, the NetworkConnection that it's for, and possibly a `DataStreamReader`. ::: -We are now ready to process events. Lets start with the `Data` event. +Now it’s time to process events. Start with the `Data` event. ```csharp - if (cmd == NetworkEvent.Type.Data) + if (cmd == NetworkEvent.Type.Data) { ``` -Next, we try to read a `uint` from the stream and output what we have received: +Next, try to read a `uint` from the stream and output the received data: ```csharp - uint number = stream.ReadUInt(); + uint number = stream.ReadUInt(); Debug.Log("Got " + number + " from the Client adding + 2 to it."); ``` -When this is done we simply add two to the number we received and send it back. To send anything with the `NetworkDriver` we need a instance of a `DataStreamWriter`. A `DataStreamWriter` is a new type that comes with the `com.unity.transport` package. You get a `DataStreamWriter` when you start sending a message by calling `BeginSend`. +After outputting the received data, add the received numbers and send the sum back to the client. To send anything with the `NetworkDriver`, you need an instance of a `DataStreamWriter`. A `DataStreamWriter` is a new type that comes with the `com.unity.transport` package. You get a `DataStreamWriter` when you start sending a message by calling `BeginSend`. -After you have written your updated number to your stream, you call the `EndSend` method on the driver and off it goes: +After you’ve written the sum of the two numbers to the stream, call the `EndSend` method on the driver. Off it goes! ```csharp - number +=2; + number +=2; m_Driver.BeginSend(NetworkPipeline.Null, m_Connections[i], out var writer); writer.WriteUInt(number); @@ -262,13 +255,13 @@ After you have written your updated number to your stream, you call the `EndSend ``` :::note -We are passing `NetworkPipeline.Null` to the `BeginSend` function. This way we say to the driver to use the unreliable pipeline to send our data. It is also possible to not specify a pipeline. +This example passes `NetworkPipeline.Null` to the `BeginSend` function to tell the driver to use the unreliable pipeline to send the data. However, it’s also possible to choose not to specify a pipeline. ::: -Finally, you need to handle the disconnect case. This is pretty straight forward, if you receive a disconnect message you need to reset that connection to a `default(NetworkConnection)`. As you might remember, the next time the `Update` loop runs you will clean up after yourself. +You must handle the disconnect case. Handling the disconnect case is pretty straightforward: if you receive a disconnect message, reset that connection to a `default(NetworkConnection)`. The next time the `Update` loop runs, you should clean up the stale connections.. ```csharp - else if (cmd == NetworkEvent.Type.Disconnect) + else if (cmd == NetworkEvent.Type.Disconnect) { Debug.Log("Client disconnected from server"); m_Connections[i] = default(NetworkConnection); @@ -276,20 +269,26 @@ Finally, you need to handle the disconnect case. This is pretty straight forward } } } - ``` -That is the whole server. See [_ServerBehaviour.cs_](samples/serverbehaviour.cs.md) for the full source code. +You’ve now created the server, and you’re ready to create a client. Here’s a summary of the server logic: -## Creating a Client +1. Add any necessary boilerplate code. +2. Define the `Start` method logic, which includes declaring (and binding to) a `NetworkDriver` and `creating` a NativeList to hold the connections. +3. Define the `OnDestroy` method logic, which includes disposing of the `NetworkDriver` and the NativeList that holds the connections. +4. Define the `Update` loop logic, which includes listening for connections, adding connections, processing data, handling disconnections, and cleaning up stale connections. -The client code looks pretty similar to the server code at first glance, but there are a few subtle differences. This part of the workflow covers the differences between them, and not so much the similarities. +See [`ServerBehaviour.cs`](samples/serverbehaviour.cs.md) for the full source code. -### ClientBehaviour.cs +## Create a Client -You still define a `NetworkDriver` but instead of having a list of connections we now only have one. There is a `Done` flag to indicate when we are done, or in case you have issues with a connection, you can exit quickly. +This section demonstrates creating a simple client with UTP 2.0. The client code looks pretty similar to the server code at first glance, but there are subtle differences. This part of the workflow covers the differences between them and not so much the similarities. -**Filename**: [_Assets\Scripts\ClientBehaviour.cs_](samples/clientbehaviour.cs.md) +Start by creating a C# script in the Unity Editor. Name the script `ClientBehaviour.cs`. + +Similar to the server behavior, you still need to define a `NetworkDriver`. However, the client only has one instead of a list of connections. There’s also a Done flag to indicate when the client is finished with the connection. You can also use the Done flag to exit if you have connection issues. + +**Filename**: [`Assets\Scripts\ClientBehaviour.cs`](samples/clientbehaviour.cs.md) ```csharp using ... @@ -306,9 +305,9 @@ public class ClientBehaviour : MonoBehaviour { } ``` -### Creating and Connecting a Client +### Create and connect a client -Start by creating a driver for the client and an address for the server. +Start by creating a `NetworkDriver` for the client and an address for the server. ```csharp void Start () { @@ -321,9 +320,9 @@ void Start () { } ``` -Then call the `Connect` method on your driver. +Then call the `Connect` method on the `NetworkDriver` you created. -Cleaning up this time is a bit easier because you don’t need a `NativeList` to hold your connections, so it simply just becomes: +Cleaning up the client is easier than cleaning up the server because you don’t need a `NativeList` to hold the connections. You only need to dispose of the `NetworkDriver`. ```csharp public void OnDestroy() @@ -332,9 +331,9 @@ public void OnDestroy() } ``` -### Client Update loop +### Client `Update` loop -You start the same way as you did in the server by calling `m_Driver.ScheduleUpdate().Complete();` and make sure that the connection worked. +You start the client Update loop the same way as the server: by calling `m_Driver.ScheduleUpdate().Complete()`, then ensuring the connection succeeded. ```csharp void Update() @@ -349,24 +348,23 @@ void Update() } ``` -You should recognize the code below, but if you look closely you can see that the call to `m_Driver.PopEventForConnection` was switched out with a call to `m_Connection.PopEvent`. This is technically the same method, it just makes it a bit clearer that you are handling a single connection. +The following code is similar to the server Update loop code. However, if you look closely, you can see the call to `m_Driver.PopEventForConnection` is replaced with a call to `m_Connection.PopEvent`. This is technically the same method; it just makes it clearer that you are handling a single connection. ```csharp - DataStreamReader stream; + DataStreamReader stream; NetworkEvent.Type cmd; while ((cmd = m_Connection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty) { ``` -Now you encounter a new event you have not seen yet: a `NetworkEvent.Type.Connect` event. -This event tells you that you have received a `ConnectionAccept` message and you are now connected to the remote peer. +Now you encounter a new event you haven't seen yet: a `NetworkEvent.Type.Connect` event. The `NetworkEvent.Type.Connect` event tells you that you received a `ConnectionAccept` message and are now connected to the remote peer. :::note -In this case, the server that is listening on port `9000` on `NetworkEndPoint.LoopbackIpv4` is more commonly known as `127.0.0.1`. +In this case, the server listens on port 9000 on `NetworkEndPoint.LoopbackIpv4` (more commonly known as 127.0.0.1). ::: ```csharp - if (cmd == NetworkEvent.Type.Connect) + if (cmd == NetworkEvent.Type.Connect) { Debug.Log("We are now connected to the server"); @@ -377,16 +375,16 @@ In this case, the server that is listening on port `9000` on `NetworkEndPoint.Lo } ``` -When you establish a connection between the client and the server, you send a number (that you want the server to increment by two). The use of the `BeginSend` / `EndSend` pattern together with the `DataStreamWriter`, where we set `value` to one, write it into the stream, and finally send it out on the network. +Upon establishing a connection between the client and the server, the client sends a number to the server (that the server increments by two). Using the `BeginSend` / `EndSend` pattern with the `DataStreamWriter`, set value to one, write it into the stream, and send it out on the network. -When the `NetworkEvent` type is `Data`, as below, you read the `value` back that you received from the server and then call the `Disconnect` method. +When the `NetworkEvent` type is Data, read the value you received back from the server, then call the `Disconnect` method. :::note -A good pattern is to always set your `NetworkConnection` to `default(NetworkConnection)` to avoid stale references. +The recommended best practice is to set `NetworkConnection` to `default(NetworkConnection)` to avoid stale references. ::: ```csharp - else if (cmd == NetworkEvent.Type.Data) + else if (cmd == NetworkEvent.Type.Data) { uint value = stream.ReadUInt(); Debug.Log("Got the value = " + value + " back from the server"); @@ -394,13 +392,11 @@ A good pattern is to always set your `NetworkConnection` to `default(NetworkConn m_Connection.Disconnect(m_Driver); m_Connection = default(NetworkConnection); } - ``` -Lastly, we need to handle potential server disconnects: +You must handle potential server disconnects: ```csharp - else if (cmd == NetworkEvent.Type.Disconnect) { Debug.Log("Client got disconnected from server"); @@ -410,18 +406,30 @@ Lastly, we need to handle potential server disconnects: } ``` -See [_ClientBehaviour.cs_](samples/clientbehaviour.cs.md) for the full source code. +You’ve now created the server and client, and you’re ready to put it all together. Here’s a summary of the client logic: + +1. Define the `Start` method logic, which includes declaring (and binding to) a `NetworkDriver`. +2. Define the `OnDestroy` method logic, which includes disposing of the `NetworkDriver`. +3. Define the `Update` loop logic, which includes connecting to the server, handling the Connect event, sending data, receiving data, and handling disconnections. + +See [`ClientBehaviour.cs`](samples/clientbehaviour.cs.md) for the full source code. + +## Test the server and client + +If you’ve been following along with the tutorial and using the sample code, you now have a functional server and client. This section shows how to put it together and test it in the Unity Editor. + +Add a new empty [GameObject](https://docs.unity3d.com/ScriptReference/GameObject.html) to your **Scene**. -## Putting it all together +![GameObject](../../static/img/transport/gameobject-2.png) -To take this for a test run, you can add a new empty [GameObject](https://docs.unity3d.com/ScriptReference/GameObject.html) to our **Scene**. +Add both the `ServerBehaviour` and the `ClientBehaviour` scripts to the GameObject. -![GameObject Added](/img/transport/game-object.PNG) +![Inspector](../../static/img/transport/inspector-2.png) -Add add both of our behaviours to it: +Select **Play** to enter Play mode. You should see five log messages in the **Console** window: -![Inspector](/img/transport/inspector.PNG) +![Console output](../../static/img/transport/console-2.png) -Click **Play**. Five log messages should load in your **Console** window: +## WebSocket -![Console](/img/transport/console-view.PNG) + See [Client and server over WebSocket](workflow-client-server-ws.md). diff --git a/transport_versioned_docs/version-2.0.0/workflow-client-server-ws.md b/transport_versioned_docs/version-2.0.0/workflow-client-server-ws.md index 3a31b029a..24363db07 100644 --- a/transport_versioned_docs/version-2.0.0/workflow-client-server-ws.md +++ b/transport_versioned_docs/version-2.0.0/workflow-client-server-ws.md @@ -1,427 +1,38 @@ --- id: minimal-workflow-ws -title: Client and Server over Websocket +title: Client and server over WebSocket --- -:::note -Need an update before releasing UTP 2.0.0 -::: - -This Transport workflow covers all aspects of the `Unity.Networking.Transport` package and helps you create a sample project that highlights how to use the `com.unity.transport` API to: - -* Configure -* Connect -* Send data -* Receive data -* Close a connection -* Disconnect -* Timeout a connection - -The goal is to make a remote `add` function. The flow will be: a client connects to the server, and sends a number, this number is then received by the server that adds another number to it and sends it back to the client. The client, upon receiving the number, disconnects and quits. - -Using the `NetworkDriver` to write client and server code is similar between clients and servers; there are a few subtle differences demonstrated in this guide. - -## Creating a Server - -A server is an endpoint that listens for incoming connection requests and sends and receives messages. - -Start by creating a C# script in the Unity Editor. - -Filename: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) - -```csharp -using System.Collections; -using System.Collections.Generic; -using UnityEngine; - -public class ServerBehaviour : MonoBehaviour { - - // Use this for initialization - void Start () { - - } - - // Update is called once per frame - void Update () { - - } -} -``` - -### Boilerplate code - -As the `com.unity.transport` package is a low level API, there is a bit of boiler plate code you might want to setup. This is an architecture design Unity chose to make sure that you always have full control. - -:::note -As development on the `com.unity.transport` package evolves, more abstractions may be created to reduce your workload on a day-to-day basis. -::: - -The next step is to clean up the dependencies and add our boilerplate code: - -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) - -```csharp -using UnityEngine; -using UnityEngine.Assertions; - -using Unity.Collections; -using Unity.Networking.Transport; - -... -``` - -#### Code walkthrough - -### ServerBehaviour.cs - -Adding the members we need the following code: - -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) - -```csharp -using ... - -public class ServerBehaviour : MonoBehaviour { - - public NetworkDriver m_Driver; - private NativeList m_Connections; - - void Start () { - } - - void OnDestroy() { - } - - void Update () { - } - -``` - -#### Code walkthrough - -``` -public NetworkDriver m_Driver; -private NativeList m_Connections; -``` - -You need to declare a `NetworkDriver`. You also need to create a NativeList to hold our connections. - -### Start method - -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) - -```csharp -void Start () -{ - m_Driver = NetworkDriver.Create(); - var endpoint = NetworkEndPoint.AnyIpv4; - endpoint.Port = 9000; - if (m_Driver.Bind(endpoint) != 0) - Debug.Log("Failed to bind to port 9000"); - else - m_Driver.Listen(); - - m_Connections = new NativeList(16, Allocator.Persistent); -} -``` - -#### Code walkthrough - -The first line of code, `m_Driver = NetworkDriver.Create();` , just makes sure you are creating your driver without any parameters. - -```csharp - if (m_Driver.Bind(endpoint) != 0) - Debug.Log("Failed to bind to port 9000"); - else - m_Driver.Listen(); -``` - -Then we try to bind our driver to a specific network address and port, and if that does not fail, we call the `Listen` method. - -:::important -the call to the `Listen` method sets the `NetworkDriver` to the `Listen` state. This means that the `NetworkDriver` will now actively listen for incoming connections. -::: - -` m_Connections = new NativeList(16, Allocator.Persistent);` - -Finally we create a `NativeList` to hold all the connections. - -### OnDestroy method - -Both `NetworkDriver` and `NativeList` allocate unmanaged memory and need to be disposed. To make sure this happens we can simply call the `Dispose` method when we are done with both of them. - -Add the following code to the `OnDestroy` method on your [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html): - -**Filename**: [_Assets\Scripts\ServerBehaviour.cs_](samples/serverbehaviour.cs.md) - -```csharp -public void OnDestroy() -{ - if (m_Driver.IsCreated) - { - m_Driver.Dispose(); - m_Connections.Dispose(); - } -} -``` - -The check for `m_Driver.IsCreated` ensures we don't dispose of the memory if it hasn't been allocated (e.g. if the component is disabled). - -### Server Update loop - -As the `com.unity.transport` package uses the [Unity C# Job System](https://docs.unity3d.com/Manual/JobSystem.html) internally, the `m_Driver` has a `ScheduleUpdate` method call. Inside our `Update` loop you need to make sure to call the `Complete` method on the [JobHandle](https://docs.unity3d.com/Manual/JobSystemJobDependencies.html) that is returned, in order to know when you are ready to process any updates. - -```csharp -void Update () { - - m_Driver.ScheduleUpdate().Complete(); -``` - -:::note -In this example, we are forcing a synchronization on the main thread in order to update and handle our data later in the `MonoBehaviour::Update` call. The workflow [Creating a jobified client and server](workflow-client-server-jobs.md) shows you how to use the Transport package with the C# Job System. -::: - - -The first thing we want to do, after you have updated your `m_Driver`, is to handle your connections. Start by cleaning up any old stale connections from the list before processing any new ones. This cleanup ensures that, when we iterate through the list to check what new events we have gotten, we dont have any old connections laying around. - -Inside the "Clean up connections" block below, we iterate through our connection list and just simply remove any stale connections. - -```csharp - // Clean up connections - for (int i = 0; i < m_Connections.Length; i++) - { - if (!m_Connections[i].IsCreated) - { - m_Connections.RemoveAtSwapBack(i); - --i; - } - } -``` - -Under "Accept new connections" below, we add a connection while there are new connections to accept. - -```csharp - // Accept new connections - NetworkConnection c; - while ((c = m_Driver.Accept()) != default(NetworkConnection)) - { - m_Connections.Add(c); - Debug.Log("Accepted a connection"); - } -``` - -Now we have an up-to-date connection list. You can now start querying the driver for events that might have happened since the last update. - -```csharp - DataStreamReader stream; - for (int i = 0; i < m_Connections.Length; i++) - { - if (!m_Connections[i].IsCreated) - continue; -``` - -Begin by defining a `DataStreamReader`. This will be used in case any `Data` event was received. Then we just start looping through all our connections. - -For each connection we want to call `PopEventForConnection` while there are more events still needing to get processed. - -```csharp - NetworkEvent.Type cmd; - while ((cmd = m_Driver.PopEventForConnection(m_Connections[i], out stream)) != NetworkEvent.Type.Empty) - { -``` - -:::note -There is also the `NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader slice)` method call, that returns the first available event, the `NetworkConnection` that its for and possibly a `DataStreamReader`. -::: - -We are now ready to process events. Lets start with the `Data` event. - -```csharp - if (cmd == NetworkEvent.Type.Data) - { -``` - -Next, we try to read a `uint` from the stream and output what we have received: - -```csharp - uint number = stream.ReadUInt(); - Debug.Log("Got " + number + " from the Client adding + 2 to it."); -``` - -When this is done we simply add two to the number we received and send it back. To send anything with the `NetworkDriver` we need a instance of a `DataStreamWriter`. A `DataStreamWriter` is a new type that comes with the `com.unity.transport` package. You get a `DataStreamWriter` when you start sending a message by calling `BeginSend`. - -After you have written your updated number to your stream, you call the `EndSend` method on the driver and off it goes: - -```csharp - number +=2; - - m_Driver.BeginSend(NetworkPipeline.Null, m_Connections[i], out var writer); - writer.WriteUInt(number); - m_Driver.EndSend(writer); - } -``` - -:::note -We are passing `NetworkPipeline.Null` to the `BeginSend` function. This way we say to the driver to use the unreliable pipeline to send our data. It is also possible to not specify a pipeline. -::: - -Finally, you need to handle the disconnect case. This is pretty straight forward, if you receive a disconnect message you need to reset that connection to a `default(NetworkConnection)`. As you might remember, the next time the `Update` loop runs you will clean up after yourself. - -```csharp - else if (cmd == NetworkEvent.Type.Disconnect) - { - Debug.Log("Client disconnected from server"); - m_Connections[i] = default(NetworkConnection); - } - } - } - } - -``` - -That is the whole server. See [_ServerBehaviour.cs_](samples/serverbehaviour.cs.md) for the full source code. - -## Creating a Client - -The client code looks pretty similar to the server code at first glance, but there are a few subtle differences. This part of the workflow covers the differences between them, and not so much the similarities. - -### ClientBehaviour.cs +One thing that isn’t evident in the [simple client-serve example](workflow-client-server-udp.md) is that the `NetworkDriver` instantiates a `NetworkInterface` object internally. However, you might still need to request a particular `NetworkInterface` object. -You still define a `NetworkDriver` but instead of having a list of connections we now only have one. There is a `Done` flag to indicate when we are done, or in case you have issues with a connection, you can exit quickly. +The `NetworkInterface` defines the operations a `NetworkDriver` requires to establish and coordinate connections. By default, in most platforms, the `NetworkInterface` object is an instance of the `UDPNetworkInterface`, which, as implied by the name, encapsulates a UDP socket. However, you can't normally open a UDP socket in a Web browser. As a result, the default network interface in the WebGL player is the `WebSocketNetworkInterface`, which encapsulates a TCP socket using the WebSocket protocol. -**Filename**: [_Assets\Scripts\ClientBehaviour.cs_](samples/clientbehaviour.cs.md) +The distinction between the UDPNetworkInterface and the `WebSocketNetworkInterface` is important because of the fundamental network constraint that a client can only directly connect to a server with the same underlying socket type. In other words, a TCP socket can only connect to another TCP socket, and the same applies to UDP. If you plan to create a server for WebGL players to connect to, you have to tell the network driver to use the `WebSocketNetworInterface` explicitly: ```csharp -using ... - -public class ClientBehaviour : MonoBehaviour { - - public NetworkDriver m_Driver; - public NetworkConnection m_Connection; - public bool Done; - - void Start () { ... } - public void OnDestroy() { ... } - void Update() { ... } -} -``` - -### Creating and Connecting a Client - -Start by creating a driver for the client and an address for the server. - -```csharp -void Start () { - m_Driver = NetworkDriver.Create(); - m_Connection = default(NetworkConnection); - - var endpoint = NetworkEndPoint.LoopbackIpv4; - endpoint.Port = 9000; - m_Connection = m_Driver.Connect(endpoint); -} -``` - -Then call the `Connect` method on your driver. - -Cleaning up this time is a bit easier because you don’t need a `NativeList` to hold your connections, so it simply just becomes: - -```csharp -public void OnDestroy() -{ - m_Driver.Dispose(); -} + m_Driver = NetworkDriver.Create(new WebSocketNetworkInterface()); ``` -### Client Update loop +If you plan to share networking code between clients for multiple platforms, including WebGL, you might opt to have a WebSocket server for all platforms. In this case, ensure you assign the `WebSocketNetworkInterface` to the non-WebGL clients. -You start the same way as you did in the server by calling `m_Driver.ScheduleUpdate().Complete();` and make sure that the connection worked. +Alternatively, suppose you plan to have a server dedicated to browsers and another for other platforms. In that case, you can specify a different `NetworkDriver` instantiation with [compiler definitions](https://docs.unity3d.com/Manual/PlatformDependentCompilation.html) that depend on the platforms your project supports. ```csharp -void Update() -{ - m_Driver.ScheduleUpdate().Complete(); - - if (!m_Connection.IsCreated) - { - if (!Done) - Debug.Log("Something went wrong during connect"); - return; - } + #if UNITY_WEBGL + m_Driver = NetworkDriver.Create(new WebSocketNetworkInterface()); + #else + m_Driver = NetworkDriver.Create(new UDPNetworkInterface()); + #endif ``` -You should recognize the code below, but if you look closely you can see that the call to `m_Driver.PopEventForConnection` was switched out with a call to `m_Connection.PopEvent`. This is technically the same method, it just makes it a bit clearer that you are handling a single connection. - -```csharp - DataStreamReader stream; - NetworkEvent.Type cmd; - while ((cmd = m_Connection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty) - { -``` - -Now you encounter a new event you have not seen yet: a `NetworkEvent.Type.Connect` event. -This event tells you that you have received a `ConnectionAccept` message and you are now connected to the remote peer. - :::note -In this case, the server that is listening on port `9000` on `NetworkEndPoint.LoopbackIpv4` is more commonly known as `127.0.0.1`. +Because the WebGL player is constrained by browser capabilities, it’s currently impossible to start a server in a WebGL player (even with the `WebSocketNetworkInterface`). Web browsers (to date) don't permit applications to open sockets for incoming connections, and trying to do so results in an exception. On the other hand, creating a server while playing in the Unity Editor is still perfectly valid. In some cases, you might want to use the [UNITY_EDITOR compiler definition](https://docs.unity3d.com/Manual/PlatformDependentCompilation.html) to create a `NetworkDriver` with the `WebSocketNetworkInterface` only when using the Unity Editor. ::: ```csharp - if (cmd == NetworkEvent.Type.Connect) - { - Debug.Log("We are now connected to the server"); - - uint value = 1; - m_Driver.BeginSend(m_Connection, out var writer); - writer.WriteUInt(value); - m_Driver.EndSend(writer); - } + #if UNITY_WEBGL && !UNITY_EDITOR + m_Driver = NetworkDriver.Create(new WebSocketNetworkInterface()); + #else + m_Driver = NetworkDriver.Create(new UDPNetworkInterface()); + #endif ``` - -When you establish a connection between the client and the server, you send a number (that you want the server to increment by two). The use of the `BeginSend` / `EndSend` pattern together with the `DataStreamWriter`, where we set `value` to one, write it into the stream, and finally send it out on the network. - -When the `NetworkEvent` type is `Data`, as below, you read the `value` back that you received from the server and then call the `Disconnect` method. - -:::note -A good pattern is to always set your `NetworkConnection` to `default(NetworkConnection)` to avoid stale references. -::: - -```csharp - else if (cmd == NetworkEvent.Type.Data) - { - uint value = stream.ReadUInt(); - Debug.Log("Got the value = " + value + " back from the server"); - Done = true; - m_Connection.Disconnect(m_Driver); - m_Connection = default(NetworkConnection); - } - -``` - -Lastly, we need to handle potential server disconnects: - -```csharp - - else if (cmd == NetworkEvent.Type.Disconnect) - { - Debug.Log("Client got disconnected from server"); - m_Connection = default(NetworkConnection); - } - } - } -``` - -See [_ClientBehaviour.cs_](samples/clientbehaviour.cs.md) for the full source code. - -## 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**. - -![GameObject Added](/img/transport/game-object.PNG) - -Add add both of our behaviours to it: - -![Inspector](/img/transport/inspector.PNG) - -Click **Play**. Five log messages should load in your **Console** window: - -![Console](/img/transport/console-view.PNG) diff --git a/transport_versioned_sidebars/version-2.0.0-sidebars.json b/transport_versioned_sidebars/version-2.0.0-sidebars.json index 9f2d06083..ff71fe775 100644 --- a/transport_versioned_sidebars/version-2.0.0-sidebars.json +++ b/transport_versioned_sidebars/version-2.0.0-sidebars.json @@ -15,8 +15,12 @@ "items": [ { "type": "doc", - "id": "version-2.0.0/install" + "id": "version-2.0.0/getting-started" }, + { + "type": "doc", + "id": "version-2.0.0/install" + }, { "type": "doc", "id": "version-2.0.0/using-sample" @@ -59,26 +63,12 @@ { "type": "doc", "id": "version-2.0.0/secure-connection" - }] - } - ] - }, - { - "collapsed": true, - "type": "category", - "label": "Playing across platforms", - "items": [ - { - "type": "doc", - "id": "version-2.0.0/supported-platforms" - }, - { - "type": "doc", - "id": "version-2.0.0/cross-play-without-relay" - }, - { - "type": "doc", - "id": "version-2.0.0/cross-play-relay" + }, + { + "type": "doc", + "id": "version-2.0.0/integrate-logging" + } + ] } ] }, @@ -87,14 +77,6 @@ "type": "category", "label": "Extending functionalities", "items": [ - { - "type": "doc", - "id": "version-2.0.0/custom-pipeline" - }, - { - "type": "doc", - "id": "version-2.0.0/custom-networkinterface" - }, { "type": "doc", "id": "version-2.0.0/network-settings"