|
1 | 1 | # Custom serialization |
2 | 2 |
|
3 | | -Netcode uses a default serialization pipeline when using `RPC`s, `NetworkVariable`s, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this: |
| 3 | +> [!NOTE] |
| 4 | +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to customize serialization. |
4 | 5 |
|
5 | | -`` |
6 | | -Custom Types => Built In Types => INetworkSerializable |
7 | | -`` |
| 6 | +Netcode for GameObjects provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. |
8 | 7 |
|
9 | | -That is, when Netcode first gets hold of a type, it will check for any custom types that the user have registered for serialization, after that it will check if it's a built in type, such as a Vector3, float etc. These are handled by default. If not, it will check if the type inherits `INetworkSerializable`, if it does, it will call it's write methods. |
| 8 | +Netcode for GameObjects supports custom serialization of unsupported types, including those you haven't defined yourself, such as third-party .NET types. You can also use custom serialization to override how an existing supported type is serialized. |
10 | 9 |
|
11 | | -By default, any type that satisfies the `unmanaged` generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, etc) as well as any structs that has only these basic types. |
| 10 | +Custom serialization is implemented slightly differently for RPCs and NetworkVariables. The examples on this page provide different ways to serialization a custom health struct. |
12 | 11 |
|
13 | | -With this flow, you can provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. |
| 12 | +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#HealthStruct)] |
14 | 13 |
|
15 | | -### Serialize a type in a Remote Procedure Call (RPC) |
| 14 | +## FastBufferReader and FastBufferWriter |
16 | 15 |
|
17 | | -> [!NOTE] |
18 | | -> From versioln 1.7.0 Remote Procedure Calls (RPCs) can also use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when RPCs serialize the type. Unity selects the RPC flow if you implement both the RPC and Network variable flows. When a type is used by both NetworkVariables and RPCs you can use the NetworkVariable flow to lower maintenance requirements. |
| 16 | +[`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md) are the main serialization tools in Netcode for GameObjects. To register serialization for a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`. `FastBufferReader` and `FastBufferWriter` can read and write primitive types, and you can extend this functionality to serialize your custom type. |
19 | 17 |
|
20 | | -To register a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`: |
| 18 | +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#FastBuffer)] |
21 | 19 |
|
22 | | -```csharp |
23 | | -// Tells the Netcode how to serialize and deserialize Url in the future. |
24 | | -// The class name doesn't matter here. |
25 | | -public static class SerializationExtensions |
26 | | -{ |
27 | | - public static void ReadValueSafe(this FastBufferReader reader, out Url url) |
28 | | - { |
29 | | - reader.ReadValueSafe(out string val); |
30 | | - url = new Url(val); |
31 | | - } |
32 | | - |
33 | | - public static void WriteValueSafe(this FastBufferWriter writer, in Url url) |
34 | | - { |
35 | | - writer.WriteValueSafe(url.Value); |
36 | | - } |
37 | | -} |
38 | | -``` |
| 20 | +You may also need to add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()` if you want to serialize without [bounds checking](./fastbufferwriter-fastbufferreader.md#bounds-checking) |
39 | 21 |
|
40 | | -The code generation for RPCs will automatically pick up and use these functions, and they'll become available via `FastBufferWriter` and `FastBufferReader` directly. |
| 22 | +## BufferSerializer |
41 | 23 |
|
42 | | -You can also optionally use the same method to add support for `BufferSerializer<TReaderWriter>.SerializeValue()`, if you wish, which will make this type readily available within [`INetworkSerializable`](serialization/inetworkserializable.md) types: |
| 24 | +You can also add custom serialization support to the bi-directional [`BufferSerializer`](./bufferserializer.md). This makes your custom type readily available within [`INetworkSerializable`](serialization/inetworkserializable.md) types and in the [`NetworkBehaviour.OnSynchronize()` method](../components/core/networkbehaviour-synchronize.md#prespawn-synchronization-with-onsynchronize): |
43 | 25 |
|
44 | | -```csharp |
45 | | -// The class name doesn't matter here. |
46 | | -public static class SerializationExtensions |
47 | | -{ |
48 | | - public static void SerializeValue<TReaderWriter>(this BufferSerializer<TReaderWriter> serializer, ref Url url) where TReaderWriter: IReaderWriter |
49 | | - { |
50 | | - if (serializer.IsReader) |
51 | | - { |
52 | | - url = new Url(); |
53 | | - } |
54 | | - serializer.SerializeValue(ref url.Value); |
55 | | - } |
56 | | -} |
57 | | -``` |
| 26 | +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#BufferSerializer)] |
58 | 27 |
|
59 | | -Additionally, you can also add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()`, and `BufferSerializer<TReaderWriter>.SerializeValuePreChecked()` to provide more optimal implementations for manual serialization using `FastBufferReader.TryBeginRead()`, `FastBufferWriter.TryBeginWrite()`, and `BufferSerializer<TReaderWriter>.PreCheck()`, respectively. However, none of these will be used for serializing RPCs - only `ReadValueSafe` and `WriteValueSafe` are used. |
| 28 | +## Remote procedure call (RPCs) |
60 | 29 |
|
61 | | -### For NetworkVariable |
| 30 | +> [!NOTE] |
| 31 | +> RPCs can use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when only RPCs need to serialize the type. When a type is used by both NetworkVariables and RPCs, you can implement just the NetworkVariable flow to lower maintenance requirements. Unity will select the RPC flow for RPCs if you have implemented both flows. |
62 | 32 |
|
63 | | -`NetworkVariable` goes through a slightly different pipeline than `RPC`s and relies on a different process for determining how to serialize its types. As a result, making a custom type available to the `RPC` pipeline doesn't automatically make it available to the `NetworkVariable` pipeline, and vice-versa. The same method can be used for both, but currently, `NetworkVariable` requires an additional runtime step to make it aware of the methods. |
| 33 | +To serialize a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()` as [outlined above](#fastbufferreader-and-fastbufferwriter). |
64 | 34 |
|
65 | | -To add custom serialization support in `NetworkVariable`, follow the steps from the "For RPCs" section to write extension methods for `FastBufferReader` and `FastBufferWriter`; then, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized) add the following: |
| 35 | +The code generation for RPCs will automatically pick up and use these functions, as they'll become available via `FastBufferWriter` and `FastBufferReader` directly. |
66 | 36 |
|
67 | | -```csharp |
68 | | -UserNetworkVariableSerialization<Url>.WriteValue = SerializationExtensions.WriteValueSafe; |
69 | | -UserNetworkVariableSerialization<Url>.ReadValue = SerializationExtensions.ReadValueSafe; |
70 | | -``` |
| 37 | +## NetworkVariable |
| 38 | + |
| 39 | +Implementing [`INetworkSerializable`](./serialization/inetworkserializable.md) is the cleanest and most straightforward way to customize the serialization of a type within a [`NetworkVariable`](../basics/networkvariable.md). `UserNetworkVariableSerialization` provides runtime configuration to further override serialization of a type. |
71 | 40 |
|
72 | | -You can also use lambda expressions here: |
| 41 | +First you will need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()` as [outlined above](#fastbufferreader-and-fastbufferwriter). |
| 42 | + |
| 43 | +Secondly, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized), add the following: |
73 | 44 |
|
74 | 45 | ```csharp |
75 | | -UserNetworkVariableSerialization<Url>.WriteValue = (FastBufferWriter writer, in Url url) => |
76 | | -{ |
77 | | - writer.WriteValueSafe(url.Value); |
78 | | -}; |
79 | | - |
80 | | -UserNetworkVariableSerialization<Url>.ReadValue = (FastBufferReader reader, out Url url) |
81 | | -{ |
82 | | - reader.ReadValueSafe(out string val); |
83 | | - url = new Url(val); |
84 | | -}; |
| 46 | +UserNetworkVariableSerialization<Health>.WriteValue = SerializationExtensions.WriteValueSafe; |
| 47 | +UserNetworkVariableSerialization<Health>.ReadValue = SerializationExtensions.ReadValueSafe; |
| 48 | +UserNetworkVariableSerialization<Health>.DuplicateValue = (in Health value, ref Health duplicatedValue) => duplicatedValue = value; |
85 | 49 | ``` |
86 | 50 |
|
87 | | -When you create an extension method in `NetworkVariable<T>` you need to implement the following values: |
| 51 | +`DuplicateValue` should return a complete deep copy of the value that `NetworkVariable<T>` compares to a previous value, which is used to check whether the value has changed. `DuplicateValue` avoids re-serializing it over the network every frame when it hasn't changed. |
| 52 | + |
| 53 | +> [!NOTE] |
| 54 | +> `WriteValue`, `ReadValue` and `DuplicateValue` all need to be defined to customize your serialization. |
| 55 | +
|
| 56 | +> [!NOTE] |
| 57 | +> `WriteValue` and `ReadValue` will not be used if a type implements `INetworkSerializable` or [`INetworkSerializeByMemcpy`](./serialization/inetworkserializebymemcpy.md). |
| 58 | +
|
| 59 | +### Serializing delta updates |
| 60 | + |
| 61 | +Reading and writing a value provides the minimal amount of `NetworkVariable` functionality. This will synchronize your whole type whenever any value within the type value changes. To support sending delta updates rather than a full update whenever your type has changed, implement the following functions: |
| 62 | + |
| 63 | +- `WriteDelta` |
| 64 | +- `ReadDelta` |
| 65 | + |
| 66 | +> [!NOTE] |
| 67 | +> Both `WriteDelta` and `ReadDelta` need to be defined for either to be used. |
88 | 68 |
|
89 | | -- `WriteValue` |
90 | | -- `ReadValue` |
91 | | -- `DuplicateValue` |
| 69 | +Here is a full implementation of a custom type with the methods needed for `UserNetworkVariableSerialization` |
92 | 70 |
|
93 | | -`DuplicateValue` returns a complete deep copy of the value that `NetworkVariable<T>` compares to a previous value to check whether or not that values has changed. This avoids reserializing it over the network every frame when it hasn't changed. |
| 71 | +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/NetworkVariableSerialization.cs#HealthExample)] |
0 commit comments