Skip to content

Publish staged content #877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#### **WARNING**: Please make your PR against the `develop` branch.
- - -
**Select the type of change:**
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Small Changes - Typos, formatting, slight revisions
Expand All @@ -7,7 +9,5 @@
**Purpose of the Pull Request:**
<!-- Describe what the PR fixes or adds. -->


**Issue Number:**
<!-- Post the issue or ticket number addressed by the PR. -->

**Issue Number:**
<!-- Post the issue or ticket number addressed by the PR. -->
50 changes: 39 additions & 11 deletions docs/advanced-topics/custom-serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id: custom-serialization
title: Custom Serialization
---

When using `RPC`'s, `NetworkVariable`'s or any other Netcode for GameObjects (Netcode) related task that requires serialization. The Netcode uses a default serialization pipeline that looks like this:
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:

``
Custom Types => Built In Types => INetworkSerializable
Expand All @@ -13,7 +13,9 @@ That is, when Netcode first gets hold of a type, it will check for any custom ty

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 contains only these basic types.

With this flow, you can override **ALL** serialization for **ALL** types, even built in types, and with the API provided, it can even be done with types that you have not defined yourself, those who are behind a 3rd party wall, such as .NET types.
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 have not 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.

### For RPCs

To register a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`:

Expand All @@ -22,15 +24,15 @@ To register a custom type, or override an already handled type, you need to crea
// The class name doesn't matter here.
public static class SerializationExtensions
{
public static void ReadValueSafe(this FastBufferReader reader, out Url value)
public static void ReadValueSafe(this FastBufferReader reader, out Url url)
{
reader.ReadValueSafe(out string val);
value = new Url(val);
url = new Url(val);
}

public static void WriteValueSafe(this FastBufferWriter writer, in Url value)
public static void WriteValueSafe(this FastBufferWriter writer, in Url url)
{
writer.WriteValueSafe(instance.Value);
writer.WriteValueSafe(url.Value);
}
}
```
Expand All @@ -39,19 +41,45 @@ The code generation for RPCs will automatically pick up and use these functions,

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`](/advanced-topics/serialization/inetworkserializable.md) types:

```c#
```csharp
// The class name doesn't matter here.
public static class SerializationExtensions
{
public static void SerializeValue<TReaderWriter>(this BufferSerializer<TReaderWriter> serializer, ref Url value) where TReaderWriter: IReaderWriter
public static void SerializeValue<TReaderWriter>(this BufferSerializer<TReaderWriter> serializer, ref Url url) where TReaderWriter: IReaderWriter
{
if (serializer.IsReader)
{
value = new Url();
url = new Url();
}
serializer.SerializeValue(ref value.Value);
serializer.SerializeValue(ref url.Value);
}
}
```

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.
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.

### For NetworkVariable

`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.

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:

```csharp
UserNetworkVariableSerialization<Url>.WriteValue = SerializationExtensions.WriteValueSafe;
UserNetworkVariableSerialization<Url>.ReadValue = SerializationExtensions.ReadValueSafe;
```

You can also use lambda expressions here:

```csharp
UserNetworkVariableSerialization<Url>.WriteValue = (FastBufferWriter writer, in Url url) =>
{
writer.WriteValueSafe(url.Value);
};

UserNetworkVariableSerialization<Url>.ReadValue = (FastBufferReader reader, out Url url)
{
reader.ReadValueSafe(out string val);
url = new Url(val);
};
```
16 changes: 16 additions & 0 deletions docs/advanced-topics/serialization/fixedstrings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: fixedstrings
title: Fixed Strings
---

Netcode's serialization system natively supports Unity's Fixed String types (`FixedString32`, `FixedString64`, `FixedString128`, `FixedString512`, and `FixedString4096`). The serialization system intelligently understands these fixed string types and ensures that only the amount of the string in use is serialized, even for the larger types. This native support ensures Netcode uses no more bandwidth than is necessary.

```csharp
[ServerRpc]
void SetPlayerNameServerRpc(FixedString32 playerName) { /* ... */ }

void SetPlayerName(string name)
{
SetPlayerNameServerRpc(new FixedString32(name));
}
```
68 changes: 55 additions & 13 deletions docs/advanced-topics/serialization/inetworkserializable.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ sidebar_label: INetworkSerializable

The `INetworkSerializable` interface can be used to define custom serializable types.

:::caution
All examples provided will work with RPCs and custom messages but some examples will not work with `NetworkVariable` due to the unmanaged type restriction.<br/>
**NetworkVariable Type Litmus Test for INetworkSerializable Implementations:**
- If the implementation itself can be a null (i.e. a class), then it cannot be used
- If it contains any property that can be null (i.e. arrays), then it cannot be used

The alternative is to create your own `NetworkVariableBase` derived `type` specific class.
:::

```csharp
struct MyComplexStruct : INetworkSerializable
{
Expand Down Expand Up @@ -68,10 +59,13 @@ As you have more control over serialization of a struct, you might implement con
More advanced use-cases are explored in following examples.

### Example: Array
:::caution
The below `INetworkSerializable` implementation example works only with RPCs and/or custom messages. The below implementation uses an array within an `INetworkSerializable` implementation. Arrays can be `null` and are not supported by the `NetworkVariable` class. As an alternative, you can write your own `NetworkVariableBase` derived class that does support managed or unmanaged value types.<br/>
[Read More About Custom NetworkVariable Implementations](../../basics/networkvariable.md)
:::

You can use arrays in one of two ways:

1. Via C# arrays
2. Via Native Collections (that is, `NativeArray`)

The critical distinction between the two is that **C# arrays** convert any type that contains the arrays to a managed type. This results in garbage collection overhead and makes the arrays somewhat less optimized when you use them with `NetworkVariable`. On the other hand, `NativeArray` requires manual memory management.

```csharp
public struct MyCustomStruct : INetworkSerializable
Expand Down Expand Up @@ -103,6 +97,54 @@ public struct MyCustomStruct : INetworkSerializable
}
```

```csharp
public struct MyCustomNativeStruct : INetworkSerializable, IDisposable
{
public NativeArray<int> Array;

public void Dispose()
{
Array.Dispose();
}

public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
// Length
int length = 0;
if (!serializer.IsReader)
{
length = Array.Length;
}

serializer.SerializeValue(ref length);

// Array
if (serializer.IsReader)
{
if(Array.IsCreated)
{
// Make sure the existing array is disposed and not leaked
Array.Dispose();
}
Array = new NativeArray<int>(length, Allocator.Persistent);
}

for (int n = 0; n < length; ++n)
{
// NataveArray doesn't have a by-ref index operator
// so we have to read, serialize, write. This works in both
// reading and writing contexts - in reading, `val` gets overwritten
// so the current value doesn't matter; in writing, `val` is unchanged,
// so Array[n] = val is the same as Array[n] = Array[n].
// NativeList also exists which does have a by-ref `ElementAt()` method.
var val = Array[n];
serializer.SerializeValue(ref val);
Array[n] = val;
}
}
}
```

**Reading:**

- (De)serialize `length` back from the stream
Expand Down
29 changes: 29 additions & 0 deletions docs/advanced-topics/serialization/inetworkserializebymemcpy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
id: inetworkserializebymemcpy
title: INetworkSerializeByMemcpy
sidebar_label: INetworkSerializeByMemcpy
---

The `INetworkSerializeByMemcpy` interface is used to mark an unmanaged struct type as being trivially serializable over the network by directly copying the whole struct, byte-for-byte, as it appears in memory, into and out of the buffer. This can offer some benefits for performance compared to serializing one field at a time, especially if the struct has many fields in it, but it may be less efficient from a bandwidth-usage perspective, as fields will often be padded for memory alignment and you won't be able to "pack" any of the fields to optimize for space usage.

The interface itself has no methods in it - it's an empty interface that satisfies a constraint on methods that perform this type of serialization, primarily there to ensure that memcpy serialization isn't performed by accident on structs that don't support it.

```csharp
public struct MyStruct : INetworkSerializeByMemcpy
{
public int A;
public int B;
public float C;
public bool D;
}
```

If you have a type you wish to serialize that you know is compatible with this method of serialization, but don't have access to modify the struct to add this interface, you can wrap your values in `ForceNetworkSerializeByMemcpy` to enable it to be serialized this way. This works in both `RPC`s and `NetworkVariables`, as well as in other contexts such as `BufferSerializer<>`, `FastBufferReader`, and `FastBufferWriter`.

```csharp
public NetworkVariable<ForceNetworkSerializeByMemcpy<Guid>> GuidVar;
```

f:::caution
Take care with using `INetworkSerializeByMemcpy`, and especially `ForceNetworkSerializeByMemcpy`, because not all unmanaged structs are actually compatible with this type of serialization. Anything that includes pointer types (including Native Collections like `NativeArray<>`) will not function correctly when serialized this way, and will likely cause memory corruption or crashes on the receiving side.
:::
3 changes: 3 additions & 0 deletions docs/advanced-topics/serialization/serialization-arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ public class StringContainer : INetworkSerializable
}
```

### Native Containers

Native containers, like `NativeArray`, are not natively supported as `RPC` parameters. However, custom support can be added for them; see [Custom Serialization](../custom-serialization.md)
6 changes: 4 additions & 2 deletions docs/advanced-topics/serialization/serialization-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ Multiplayer framework has built-in serialization support for C# and Unity primit

See the following sections:

* [C# Primitives](cprimatives.md)
* [Unity Primitives](unity-primatives.md)
* [C# Primitives](cprimitives.md)
* [Unity Primitives](unity-primitives.md)
* [Enum Types](enum-types.md)
* [Arrays](serialization-arrays.md)
* [Fixed Strings](fixedstrings.md)
* [INetworkSerializable](inetworkserializable.md)
* [INetworkSerializeByMemcpy](inetworkserializebymemcpy.md)
* [NetworkObjects & NetworkBehaviours](networkobject-serialization.md)
Loading