Skip to main content

Custom Serialization

Netcode uses a default serialization pipeline when using RPCs, NetworkVariables, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this:

Custom Types => Built In Types => INetworkSerializable

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.

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.

With this flow, you can offer 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.

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():

// Tells the Netcode how to serialize and deserialize Url in the future.
// The class name doesn't matter here.
public static class SerializationExtensions
{
public static void ReadValueSafe(this FastBufferReader reader, out Url url)
{
reader.ReadValueSafe(out string val);
url = new Url(val);
}

public static void WriteValueSafe(this FastBufferWriter writer, in Url url)
{
writer.WriteValueSafe(url.Value);
}
}

The code generation for RPCs will automatically pick up and use these functions, and they'll become available via FastBufferWriter and FastBufferReader directly.

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 types:

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

Additionally, you can also add extensions for FastBufferReader.ReadValue(), FastBufferWriter.WriteValue(), and BufferSerializer<TReaderWriter>.SerializeValuePreChecked() to offer 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 RPCs 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 NetworkVariables using the affected types will be serialized) add the following:

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

You can also use lambda expressions here:

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);
};