Skip to content

Commit

Permalink
Develop into main (#1258)
Browse files Browse the repository at this point in the history
  • Loading branch information
jabbacakes authored Apr 29, 2024
2 parents 46bcd8e + 9051e4c commit 4090aa0
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 152 deletions.
9 changes: 2 additions & 7 deletions docs/advanced-topics/message-system/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,20 @@ title: Rpc
import ImageSwitcher from '@site/src/ImageSwitcher.js';


Any process can communicate with any other process by sending an RPC. Starting in version 1.8, the `Rpc` attribute encompasses Server to Client Rpcs, Client to Server Rpcs, and Client to Client Rpcs.
Any process can communicate with any other process by sending an RPC. Starting in version 1.8, the `Rpc` attribute encompasses server to client RPCs, client to server RPCs, and client to client RPCs.

<figure>
<ImageSwitcher
lightImageSrc="/sequence_diagrams/RPCs/ServerRPCs.png?text=LightMode"
darkImageSrc="/sequence_diagrams/RPCs/ServerRPCs_Dark.png?text=DarkMode"/>
</figure>



<figure>
<ImageSwitcher
lightImageSrc="/sequence_diagrams/RPCs/ClientRPCs.png?text=LightMode"
darkImageSrc="/sequence_diagrams/RPCs/ClientRPCs_Dark.png?text=DarkMode"/>
</figure>




## Declaring an RPC (Remote Procedure Call)

You can declare an RPC by marking a method with the `[Rpc]` attribute and including the `Rpc` suffix in the method name. RPCs have a number of possible targets that can be declared at both runtime and compile time, but a default must be passed to the `[Rpc]` attribute. For example, to create an RPC that will be executed on the server, you would declare it like this:
Expand Down Expand Up @@ -58,7 +53,7 @@ While client-to-client RPCs are supported, it is important to note that there ar

## Invoking an RPC

You can invoke an RPC by invoking the function directly with parameters:
You can invoke an RPC by invoking the function directly with parameters. The following code is a heavily simplified example of a server to client RPC and a client to server RPC, with additional logic removed to display only the most basic features of an RPC.

```csharp
[Rpc(SendTo.Server)]
Expand Down
4 changes: 3 additions & 1 deletion docs/advanced-topics/serialization/cprimitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ id: cprimitives
title: C# primitives
---

C# primitive types will be serialized by built-in serialization code. These types include `bool`, `char`, `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, and `string`.
C# primitive types are serialized by built-in serialization code. These types include `bool`, `char`, `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, and `string`.

You can also serialize [arrays](serialization-arrays.md) of C# primitive types, with the exception of arrays of `strings` (`string[]`) for [performance reasons](serialization-arrays.md#performance-considerations).

```csharp
[Rpc(SendTo.Server)]
Expand Down
30 changes: 20 additions & 10 deletions docs/advanced-topics/serialization/serialization-arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@ id: arrays
title: Arrays and native containers
---

Arrays of [C# primitive types](cprimatives.md), like `int[]`, and [Unity primitive types](unity-primitives.md), such as `Vector3`, are serialized by built-in serialization code. Otherwise, any array of types that aren't handled by the built-in serialization code, such as `string[]`, needs to be handled through a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface.
Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface.

## Performance considerations

Sending arrays and strings over the network has performance implications. An array incurs a garbage collected allocation, and a string also incurs a garbage collected allocation, so an array of strings results in an allocation for every element in the array, plus one more for the array itself.

For this reason, arrays of strings (`string[]`) aren't supported by the built-in serialization code. Instead, it's recommended to use `NativeArray<FixedString*>` or `NativeList<FixedString*>`, because they're more efficient and don't incur garbage collected memory allocation. Refer to [`NativeArray<T>`](#nativearrayt) and [`NativeList<T>`](#nativelistt) below for more details.

## Built-in primitive types example

Using built-in primitive types is fairly straightforward:

## Built-In Primitive Types Example
Using built-in primitive types is fairly straight forward:
```csharp
[Rpc(SendTo.Server)]
void HelloServerRpc(int[] scores, Color[] colors) { /* ... */ }
```

## INetworkSerializable Implementation Example
There are many ways to handle sending an array of managed types.
The below example is a simple `string` container class that implements `INetworkSerializable` and can be used as an array of "StringContainers":
## INetworkSerializable implementation example

There are many ways to handle sending an array of managed types. The following example is a simple `string` container class that implements `INetworkSerializable` and can be used as an array of "StringContainers":

```csharp
[Rpc(SendTo.ClientsAndHost)]
void SendMessagesClientRpc(StringContainer[] messages)
Expand Down Expand Up @@ -42,11 +51,12 @@ public class StringContainer : INetworkSerializable
}
```

## Native Containers
## Native containers

Netcode for GameObjects supports `NativeArray` and `NativeList` native containers with built-in serialization, RPCs, and NetworkVariables. However, you cannot nest either of these containers without causing a crash.
Netcode for GameObjects supports `NativeArray` and `NativeList` native containers with built-in serialization, RPCs, and NetworkVariables. However, you can't nest either of these containers without causing a crash.

A few examples of nesting that will cause a crash:

* `NativeArray<NativeList<T>>`
* `NativeList<NativeArray<T>>`
* `NativeArray<NativeArray<T>>`
Expand All @@ -59,6 +69,7 @@ To serialize a `NativeArray` container, use `serializer.SerializeValue(ref Array
### `NativeList<T>`

To serialize a `NativeList` container, you must:

1. Ensure your assemblies reference `Collections`.
2. You must add `UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT` to your **Scriptiong Define Symbols** list.
1. From the Unity Editor top bar menu, go to **Edit** > **Project Settings...** > **Player**.
Expand All @@ -71,8 +82,7 @@ To serialize a `NativeList` container, you must:
When using `NativeLists` within `INetworkSerializable`, the list `ref` value must be a valid, initialized `NativeList`.

NetworkVariables are similar that the value must be initialized before it can receive updates.
For example,
`public NetworkVariable<NativeList<byte>> ByteListVar = new NetworkVariable<NativeList<byte>>{Value = new NativeList<byte>(Allocator.Persistent)};`
For example, `public NetworkVariable<NativeList<byte>> ByteListVar = new NetworkVariable<NativeList<byte>>{Value = new NativeList<byte>(Allocator.Persistent)};`.

RPCs do this automatically.
:::
57 changes: 28 additions & 29 deletions docs/learn/rpcvnetvar.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,99 +5,99 @@ sidebar_label: RPC vs NetworkVariable
---
import ImageSwitcher from '@site/src/ImageSwitcher.js';

Choosing the wrong data syncing mecanism can create bugs, generate too much bandwidth and add too much complexity to your code.
Netcode for GameObjects (Netcode) has two main ways of syncing information between players. `RPC` ([Remote Procedure Call](../advanced-topics/messaging-system)) and replicated state [(NetworkVariable)](../basics/networkvariable). They both send messages over the network. The logic and your design around how they send messages is what will make you choose one over the other.
Choosing the wrong data syncing mechanism can create bugs, use too much bandwidth, and add too much complexity to your code.
Netcode for GameObjects (Netcode) has two main ways of syncing information between players: RPCs ([Remote Procedure Calls](../advanced-topics/messaging-system.md)) and replicated states [(`NetworkVariable`s)](../basics/networkvariable). They both send messages over the network. The logic and your design around how they send messages is what will make you choose one over the other.

## Choosing between NetworkVariables or RPCs
## Choosing between `NetworkVariable`s or RPCs

- Use `RPC`s for transient events, information only useful for a moment when it's received.
- Use RPCs for transient events, information only useful for a moment when it's received.
- Use `NetworkVariable`s for persistent states, for information that will be around more than a moment.

A quick way to choose which to use is to ask yourself: "Should a player joining mid-game get that information?"

<figure>
<ImageSwitcher
<ImageSwitcher
lightImageSrc="/sequence_diagrams/NetworkVariable/NetworkVariables_LateJoinClient.png?text=LightMode"
darkImageSrc="/sequence_diagrams/NetworkVariable/NetworkVariables_LateJoinClient_Dark.png?text=DarkMode"/>
<figcaption>Network Variables allow to seamlessly catch up late joining clients by sending the current state as soon as the tick happens.</figcaption>
</figure>

Using the Boss Room's door as an example. A player's client needs to receive the information that the door is open to play the right animations.

If we sent an `RPC` to all clients, then all players connecting mid game after that `RPC` are sent will miss that information and have the wrong visual on their clients.
If we sent an RPC to all clients, then all players connecting mid-game after that RPC is sent will miss that information and have the wrong visual on their clients.

<figure>
<ImageSwitcher
<ImageSwitcher
lightImageSrc="/sequence_diagrams/NetworkVariableVSRPCs/RPCsLateJoin.png?text=LightMode"
darkImageSrc="/sequence_diagrams/NetworkVariableVSRPCs/RPCsLateJoin_Dark.png?text=DarkMode"/>
<figcaption>Sending state with RPCs won't be transmitted to late joining clients.</figcaption>
</figure>


In that case, it's preferable to use `NetworkVariable`s like shown here.
In that case, it's preferable to use `NetworkVariable`s as show below:

```csharp reference
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Gameplay/GameplayObjects/SwitchedDoor.cs#L10-L26
```

It uses a `BoolNetworkVariable` to represent the "IsOpen" state. If I open the door and a player connects after this, the host will replicate all the world's information to that new player, including the door's state.
It uses a `BoolNetworkVariable` to represent the `IsOpen` state. If one player opens the door and a second player connects after this, the host replicates all the world's information to that new player, including the door's state.

`NetworkVariable`s are eventually consistent. This means not all value changes will be synced, contrary to RPCs, where five calls to an RPC will produce five RPC sends on the network.

NetworkVariables are eventually consistent. This means not all value changes will be synced, contrary to RPCs, where 5 calls to an RPC will produce 5 RPC sends on the network.
<figure>
<ImageSwitcher
<ImageSwitcher
lightImageSrc="/sequence_diagrams/NetworkVariable/NetworkVariables.png?text=LightMode"
darkImageSrc="/sequence_diagrams/NetworkVariable/NetworkVariables_Dark.png?text=DarkMode"/>
<figcaption>Network Variables can be updated multiple times between ticks, but only the latest will be synced to other peers.</figcaption>
</figure>

NetworkVariables will save on bandwidth for you, making sure to only send values when the data has changed. However, if you want all value changes, RPCs might be best.
`NetworkVariable`s will save on bandwidth for you, making sure to only send values when the data has changed. However, if you want all value changes, RPCs might be best.

## Why not use `NetworkVariable`s for everything?

RPCs are simpler.

## Why not use NetworkVariables for everything?
If you have a temporary event like an explosion, you don't need a replicated state for this. It would not make sense. You would have an "unexploded" state that would need to be synced every time a new player connected? From a design perspective, you might not want to represent these events as state.

`RPC`s are simpler.
An explosion can use an RPC for the event, but the effect of the explosion should be using `NetworkVariable`s (for example player's knockback and health decrease). A newly connected player doesn't care about an explosion that happened five seconds ago. They do care about the current health of the players around that explosion though.

If you have a temporary event like an explosion, you don't need a replicated state for this. It would not make sense. You would have an "unexploded" state that would need to be synced everytime a new player connected? From a design perspective, you might not want to represent these events as state.
Actions in Boss Room are a great example for this. The area of effect action (`AoeAction`) triggers an RPC when the action is activated (showing a VFX around the affected area). The imp's health (`NetworkVariable`s) is updated. If a new player connects, they will see the damaged imps. We would not care about the area of effect ability's VFX, which works great with a transient RPC.

An explosion can use an `RPC` for the event, but the effect of the explosion should be using `NetworkVariable`s ( for example player's knockback and health decrease). A newly connected player doesn't care about an explosion that happened 5 seconds ago. They do care about the current health of the players around that explosion though.

Actions in Boss Room are a great example for this. The area of effect action (`AoeAction`) triggers an `RPC` when the action is activated (showing a VFX around the affected area). The imp's health (`NetworkVariable`s) is updated. If a new player connects, they will see the damaged imps. We would not care about the area of effect ability's VFX, which works great with a transient `RPC`.

`AoeActionInput.cs` Shows the input being updated client side and not waiting for the server. It then calls an `RPC` when clicking on the area to affect.
`AoeActionInput.cs` Shows the input being updated client side and not waiting for the server. It then calls an RPC when clicking on the area to affect.

```csharp reference
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Gameplay/Action/Input/AoeActionInput.cs
```

`AOEAction.cs` Server side logic detecting enemies inside the area and applying damage. It then broadcasts an `RPC` to tell all clients to play the VFX at the appropriate position. Character's state will automatically update with their respective `NetworkVariable`s update (health and alive status for example).
`AOEAction.cs` has server-side logic detecting enemies inside the area and applying damage. It then broadcasts an RPC to tell all clients to play the VFX at the appropriate position. Character's state will automatically update with their respective `NetworkVariable`s update (health and alive status for example).


```csharp reference
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Gameplay/Action/ConcreteActions/AOEAction.cs#L8-L-40
```

The following snippet of code is triggered by an `RPC` coming from the server
The following snippet of code is triggered by an RPC coming from the server

```csharp reference
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/v2.2.0/Assets/Scripts/Gameplay/Action/ConcreteActions/AOEAction.cs#L77-L82
```

:::tip
If you want to make sure two variables are received at the same time, `RPC`s are great for this.
If you want to make sure two variables are received at the same time, RPCs are great for this.

If you change `NetworkVariables` "a" and "b", there is no guarantee they will both be received client side at the same time.
If you change `NetworkVariables` "a" and "b", there is no guarantee they will both be received client side at the same time.

<figure>
<ImageSwitcher
<ImageSwitcher
lightImageSrc="/sequence_diagrams/NetworkVariable/NetVarDataUpdates.png?text=LightMode"
darkImageSrc="/sequence_diagrams/NetworkVariable/NetVarDataUpdates_Dark.png?text=DarkMode"/>
<figcaption>Different Network Variables updated within the same tick aren't guranteed to be delivered to the clients at the same time. </figcaption>
</figure>

Sending them as two parameters in the same `RPC` allows to make sure they will be received at the same time client side.
Sending them as two parameters in the same RPC ensures they will be received at the same time client side.

<figure>
<ImageSwitcher
<ImageSwitcher
lightImageSrc="/sequence_diagrams/NetworkVariableVSRPCs/ManagingNetVarData_RPCs.png?text=LightMode"
darkImageSrc="/sequence_diagrams/NetworkVariableVSRPCs/ManagingNetVarData_RPCs_Dark.png?text=DarkMode"/>
<figcaption>To ensure that several different Network Variables are all synchronized at the same exact time we can use client RPC to join these value changes together.</figcaption>
Expand All @@ -112,5 +112,4 @@ darkImageSrc="/sequence_diagrams/NetworkVariableVSRPCs/ManagingNetVarData_RPCs_D

`NetworkVariable`s are great for managing state, to make sure everyone has the latest value. Use them when you want to make sure newly connected players get an up to date world state.

`RPC`s are great for sending transient events. Use them when transmiting short lived events.

RPCs are great for sending transient events. Use them when transmitting short-lived events.
Loading

0 comments on commit 4090aa0

Please sign in to comment.